基层档案网站建设,创网科技有限公司怎么样,安卓网站开发平台,wordpress手机站h5优化1.操作系统 任何计算机系统都包含一个基本的程序合集#xff0c;称为操作系统(Operator System)。笼统的理解#xff0c;操作系统包括#xff1a; 内核(进程管理#xff0c;内存管理#xff0c;文件管理#xff0c;驱动管理) 其他程序(函数库#xff0c;shell程序) OS的…1.操作系统 任何计算机系统都包含一个基本的程序合集称为操作系统(Operator System)。笼统的理解操作系统包括 内核(进程管理内存管理文件管理驱动管理) 其他程序(函数库shell程序) OS的本质是一种进行软硬件资源管理的软件并为用户提供一个良好的使用环境。 从操作系统向下看下三层描述了操作系统管理硬件的原理。每一个硬件都是一个独立的个体操作系统无法直接向硬件去获取或者发送信息于是操作系统和硬件之间就出现了一个“驱动程序”来将它们二者联系起来通过某个硬件的驱动程序操作系统就可以控制这个硬件。 硬件通过它的驱动将自己的各种信息传给操作系统操作系统获取到这些硬件信息这个过程叫描述硬件。之后操作系统把这些硬件通过链表或者别的数据结构穿起来进行管理这个过程叫做组织硬件。于是操作系统管理的本质就是 先描述在组织 还剩下上三层这三层讲述的是用户使用操作系统的过程。首先操作系统为了防止用户在使用自己的时候乱搞把自己搞崩溃了因此首先在自己上层封装了一层接口也就是系统调用接口用户或者程序员只能通过这些接口来使用操作系统的服务比如通过某个系统调用接口让操作系统去磁盘上读10个字符进内存中。 但是这些系统调用接口还是太不好用了于是又在上面又封装了一层将各种系统调用接口也封装起来方便使用系统调用接口。比如c语言库图形化界面fopen中就封装了系统调用接口open。最后用户就可以通过图形化界面来看视频或者打游戏而不是命令系统调用去进行什么操作。 2. 进程 进程是程序的一个运行实例或正在执行的程序 具体来说当我要点开一个exe可执行程序首先操作系统会帮我把这个可执行程序从磁盘拷贝到内存中去然后创建一个结构体(PCB)来描述这个程序。 PCB全称 process control bolck 进程控制块具体到Linux操作系统中进程控制块叫做task_struct 任务结构体这里面包含了这个任务进程的id执行或闲置的状态这个任务所处的地址等等信息。操作系统中肯定管理了不止一个任务也就是说操作系统中有若干个PCB然后这些PCB需要用一个链表串起来形成内核数据结构。也就是说操作系统中的进程管理也是一个先描述再组织的过程。 本节我们将会涉及到PCB结构体中的如下几个成员 标识符、状态、优先级、程序计数器、内存指针、上下文数据、I/O状态信息、记账信息 那么一个进程精确的讲是由内核数据结构(PCB) 与 程序的代码和数据 共同组成的。 2.1 查看Linux下的进程 首先我们写一个死循环程序test.c make出可执行文件myproc 我们让这个程序跑起来然后再开一个新终端使用指令 ps axj 查看当先机器上正在跑着的所有进程 这个进程太多了我们可以用grep筛选一下 单纯的筛选因为没有表头所以不太好看我们可以使用 head -1 命令把表头打印出来分号是为了在一行中执行两个命令。 可以看到目前正在执行的关于myproc的进程有两个 ./myproc就是我们正在执行的那个程序所创造的进程。 出现有grep字的进程是因为grep指令本身也是一个程序因此在使用grep指令的时候自然也创造了一个grep的进程。 如果不想看到这个grep进程我们可以使用反向筛选 grep -v grep 把程序运行起来本质上就是在操作系统中启动了一个关于这个程序的进程。包括ls pwd grep 这些命令它们在命令的时候也创建了一个进程只不过这个进程运行的很快交给CPU之后很快就运算完了这种进程叫瞬时进程。还有一种进程比如浏览器我们刚刚写的myproc文件用户不主动退出它们就会一直运行下去这种进程我们称为常驻进程。 2.2 pid (标识符) pid (process id)是每个进程的唯一标识符 在代码中想查看pid可以通过系统调用 getpid() 想要使用这个函数需要包含头文件 sys/types.h ,这个函数参数为空返回值类型是 pid_t 其实就是一个整形。 这里我们改造一下刚才的代码让它把自己的pid也打印出来。 2.3 kill 终止进程 kill命令可以向进程发送信号这个信号有很多种我们目前只需要知道 9 号信号就行。 下面我们直接选择9号选项然后选择要干掉的进程id值就可以终止这个进程了。 事实上我们之前用的 ctrlc 的指令也是在杀进程而终止这个程序的。 2.2 proc目录 刚才我们说过使用 ps ax j指令可以查看所有正在执行的进程和它们的属性。但是这个方法所能查看到的属性还是太少了。 在Linux系统的根目录下有一个proc目录 这里面包含着当前所有正在执行的进程文件。 左侧的这些数字就是每一个进程的pid。 我们再启动一下myproc文件可以看到pid31266出现在了proc目录下说明这个进程确确实实启动了。 我们可以进到31266进程目录下可以看到这个进程有诸多属性 我们讲解一下其中的cwd和exe属性 2.2.1 exe属性 这个exe属性就是表明当前进程链接到可执行程序的一个链接路径。 我们沿着这个路径把可执行文件删掉之后在回来看这个进程的属性就会发现这个exe属性报警了因为可执行文件已经没了。但是这个进程还能正常运行因为这个可执行文件被加载到内存中去了现在它是在内存中在跑而磁盘中这个文件已经没了如果此时结束这个进程那就真的无法再运行了。 2.2.2 cwd属性 cwd (current work dir)当前工作目录我们之前学习C语言的时候fopen新建一个文件时都是建在与源代码同级的目录下原因就是这个cwd属性。 cwd当前进程的工作目录因此如果不指定新的路径所有新建的内容都会被存放到这个工作目录下。 如果想更改这个工作目录可以调用系统接口 int chdir(const char* path) 可以使用 man chdir 查看这个接口的基本介绍如果查不到需要 yum install -y man-pages 安装一下 其头文件为 unistd.h 比如我们这里把工作目录改成上级目录然后重新make再执行 myproc 文件。 此时再去看该进程的cwd属性 可以看到工作目录已经变了同时我们可以在这个工作目录下看到新建的text.txt文件。 2.4 ppid (标识符) ppid和pid很像不过pid指的是本进程的idppid是父进程的id值。 ppid可以用系统调用 getppid() 取到 我们给test.c代码改造一下然后编译运行 我们多启动几次这个进程发现pid一直在变化因为一个进程的id值是根据它所创建的时间和顺序递增增加的。 而ppid却一直是固定的因为这个父进程是bash 在命令行中执行命令或者程序本质上都是bash创建了子进程然后由创建的子进程来执行我们的代码。 bash是针对Linux操作系统的命令行解释器而shell是对于命令行解释器的一个统称。 3. 使用系统调用创建进程 fork() 这里我们需要用到一个系统调用函数 fork 它的作用就是船舰一个子进程。 fork()会给父进程返回子进程的pid给子进程返回0 我们可以写这样一个程序 运行出来的效果如下 这里我们关注全局变量val的值父进程中val值是只读的子进程中val值每次会加1 然后我们观察这个运行结果发现val值虽然作为全局变量但是子进程上的自增并没有影响到父进程的val值。 这是因为父子继承之间只是代码共用同一份但是它们的数据是各自有一份。 这也能解释为什么 fork() 函数可以有两个返回值因为fork新增了一个进程在原进程中返回子进程的pid在新进程中返回0 进程之间有很强的独立性即使是父子进程多个进程之间运行互不影响。比如我们在windows的使用中可能会遇到有些程序崩溃了但是这个程序的崩溃并不会影响到它的shell子进程崩溃了但是shell和其他的进程还在好好的跑着。 我们还可以尝试一次性创建多个进程 看上面这段代码其内容就是从一个父进程中创建出了10个子进程并且将子进程的pid全部都由父进程中的一个顺序表管理起来这样可以方便后续父进程对子进程的一个控制。 绿框中使用10次for循环创建了10个子进程之后红框中让每一个子进程陷入循环防止其结束黄框中将子进程的pid记录到父进程中的一个顺序表中。 我们看一下程序运行起来的效果 明显可以看出父进程的pid是763后面又10个子进程从父进程中分离出来子进程的pid逐个递增并且父进程也知道自己的子进程的pid都是什么。 可以看到我通过sleep函数控制住了父进程和子进程的打印顺序但是实际上fork出来的进程之间谁先运行的顺序是不确定的这个是由OS的调度器自主决定的。 ps这里要说明一下上面这段代码是用C编写的同时使用了C11的auto_for循环因此在编译这段代码的时候要声明使用C11标准编译 4. PCB 中的状态 一个进程肯定不可能一直无脑的在被CPU调度否则别的进程都别执行了这个进程可能还会因为一些特殊的原因导致自己一直不被调度。就比如scanf函数就会让这个进程停下来等待用户输入那此时CPU就不会调度这个进程直到用户输入之后才会将这个进程唤醒。 上图就是一个进程的状态变化图我们跟着图捋一下进程从创建到终止的整个生命周期。 首先进程被加载到内存的过程就是进程处于创建的状态当进程加载完毕就处于就绪状态就绪状态下进程随时可以被提交到CPU中进行计算当进程被提交到CPU中计算的时候就是运行状态 (这里对于运行状态的描述不是官方教材中的描述后面马上会说教材中的运行状态是怎么描述的) 运行的时候遇到scanf需要等待用户输入此时进程就处于阻塞状态同时被从CPU中踢出停止计算该进程用户输入完之后这个进程又可以被继续计算了此时的进程重新回到就绪状态最后这个进程被计算完了就会被释放掉此时为结束状态。 以上过程时最简单的一个进程声明周期中会经理的状态梳理但是真实情况与刚才的描述还是有很多不同的。在此我们要补充一些概念 1. 进程的并行和并发 CPU执行进程的时候不是把一个进程代码执行完才去执行下一个进程而是会给每个进程分配一段很短的时间比如1ms这段很短的时间我们叫做时间片基于时间片的限制给每个进程进行轮转调度。 比如即使电脑配了一个单核的CPU我们也可以在这台电脑上同时进行听歌、写代码、听网课、玩金铲铲。这是因为CPU会先调用一个时间片长度的歌曲进程再调用一个时间片长度的代码进程再调用一个时间片长度的网课进程以此类推。因为CPU的运算速度非常快而且每个时间片又特别短最后导致我们人类根本感知不到歌曲暂停了而是会认为这四个进程是在同时运行着的。 并发多个进程在一个CPU下采用进程切换的方式在一段时间内让多个进程都得以推进。 并行多个进程在多个CPU下分别、同时的运行这种运行是真正的多个线程在同时跑。 2. 时间片 刚才我们已经提过时间片是什么了也是因为有时间片和并发逻辑的存在就能解释上图中进程在运行状态中可能会因为超时而返回就绪状态的原因了说白了就是这个进程的时间片用完了于是CPU就把他剥离回内存去计算其他进程了。 如Linux/Windows这种民用级操作系统都是分时操作系统这种操作系统的特点就是会追求调度任务的公平性它会认为每一个进程的优先级都是相同的于是给每个进程都均匀的分配时间片长度。 实时操作系统是与分时操作系统相对的概念其进程之间会有强烈的优先级之分比如车载操作系统中遇到紧急情况刹车的优先级就一定比播放音乐的优先级高那此时实时操作系统将会优先并且尽量完全执行完优先级高的进程。 3. 进程具有独立性 进程之间有很强的独立性包括父子进程即使父进程出现问题也不会影响到子进程的正常执行。 4.1 运行状态和阻塞状态 接下来我们所有的讲解都是基于单核CPU的进程并发体系的。 为了了解运行状态的本质我们首先要直到多进程的并发体系是如何实现的 前面提到过操作系统管理的本质是先描述再组织这个规则同样适用于操作系统对于进程的管理。 我们知道每个进程都有自己的PCB(进程控制块)在Linux操作系统中具体为 task_struct 这个东西就是结构体其中记录了该进程的各种信息。此时利用PCB完成了对于进程的描述。之后我们在每个PCB中添加一个 next 指针指向下一个PCB这样就将所有PCB形成一个链表完成对于所有进程的组织。 在操作系统中还有一个结构体 struct runqueue (运行队列结构体)这个结构体中包含了诸多信息比如其下控制的PCB的数量等等成员变量其中最重要的就是提供一个头节点指向已经被组织好的PCB链表。 如此使用运行队列直接控制好所有进程然后按顺序把每一个进程拿到CPU中运行运行时间片到了就再拿出来重新尾插到运行队列中。也就是说依靠链表的头删尾插或者说数据结构的增删查改完成了控制去运算哪个进程的任务。 运行状态处于运行队列中和处于CPU中正在执行计算的所有进程都处在运行状态。而不是只有在CPU中计算着的才算运行状态或者说运行状态是包括了图中的就绪和运行两块的。 我们知道硬件也是要被操作系统管理起来的每一个硬件首先都被描述成一个结构体 struct device 这个结构体中包含这硬件的各种信息比如硬件的类型、状态其中最重要的是 wait_queue (等待队列)成员这个成员的类型是一个指向PCB的一个指针也就是说这个成员后面可以挂进程。然后我们再把描述硬件的结构体用next指针组织起来完成管理硬件的先描述再组织。 此时CPU正在执行一个进程的时候发现进程中有scanf函数那这个进程此时需要等待用户从键盘输入数据后才能继续执行。此时CPU肯定不能一直等着用户输入而把其他进程耽误了。因此CPU就会把这个进程挂到对应描述键盘的结构体的等待队列列中而不是运行队列的尾。直到操作系统发现键盘被输入了这个进程可以继续执行了那就会把这个进程重新挂回运行队列。 标准一点的讲当一个进程执行的时候需要从硬件中获取数据但是此时硬件还没有准备好(键盘还未被输入)那操作系统就会把进程从CPU中拿出来挂到对应硬件的等待队列中直到硬件准备好了这个进程可以被继续执行了操作系统就会重新把该进程挂回运行队列。 阻塞状态处于某个硬件结构体的等待队列中。 运行和阻塞的本质是让不同的进度处在不同的队列中一个进程占有CPPU资源和占有外设资源时是交叉的在操作系统层面上这种交叉本质上就是把一个PCB一会儿放运行队列里一会儿放外设等待队列里在外部表现出来的概念就是运行和阻塞。 实际上PCB中表示状态是很简单的就是单纯使用宏来表示 当进程放在运行队列中时status就设置成RUNNING当要等待硬件给出数据的时候就先把状态设置成BLOCK在把PCB连接进对应硬件的等待队列中。 4.2 挂起状态 一般谈到挂起状态时都有一个大背景就是内存资源严重不足了。 当内存资源严中不足的时候操作系统会选择将阻塞状态的进程的代码和数据移出内存将这些代码和数据存到磁盘中的 swap分区(交换分区) 直到进程的阻塞状态取消要进入运行状态时操作系统会把对应进程的代码和数据重新加载进内存并将进程挂入运行队列。 将代码数据移出内存的操作叫换出换出后进程的状态就是阻塞挂起状态将代码和数据重新加载进内存的操作叫做换入。 因为挂起时要进行内存和磁盘的IO交互这个过程中势必要有大量的时间消耗因此挂起是一种用时间换空间的做法。 当内存资源严重不足的不要不要的时候操作系统还可能会选择将运行队列中队尾的几个进程的数据和代码换出到swap分区这是运行挂起状态。 但是这么做的危险性太大了因此真的出现了特别特别严重的情况时操作系统一般会采用杀进程的方式来解决问题将占用资源最多的进程直接干掉来保证自身的正常运行。这也是为什么有的时候我们打开软件时半天打不开最后直接闪退了的原因。就是因为打开这个软件的时候可能出现了一些问题导致内存压力过大然后操作系统为了保证自身的安全于是就直接把这个进程给杀掉了。 5. Linux下进程的状态 刚才我们在概念上将操作系统的运行、阻塞、挂起状态介绍完了下面我们将具体到Linux中的进程控制块也就是 task_struct 中看看进程状态是怎么描述的。 上图就是Linux源代码中对于进程状态设置的代码。 5.1 R状态 S状态 我们写这样一段代码查看有 printf() 和注释掉 printf() 这行之后这个进程的状态 可以看到无打印语句时该进程的是运行状态有打印语句是是休眠状态。 这是因为有打印语句的时候绝大多数的时间都用在了IO硬件上要等待屏幕准备完毕因此查进程状态的时候一般都会处在阻塞状态。而不打印的话进程就是在运行队列或CPU中来回切换因此一直会处在运行状态。 同时可以注意到S和R后面都有一个加号()这个符号表示该进程是在前台运行的叫前台进程而如果这个符号没有了的话就说明该进程是在后台运行的叫后台进程。前台运行的进程是可以直接 ctrlc 杀进程的但是后台运行的进程只能用 kill -9 pid 杀进程。 5.2 D状态 disk(磁盘) 这个状态是专门为IO磁盘时准备的状态。 为了防止在写入磁盘时内存突然压力过大使得操作系统把正在磁盘等待队列的进程干掉使得正在写入磁盘的数据有丢失风险。直接将正在给磁盘写入的进程设置为D状态这种状态是禁止操作系统杀掉该进程的。 D状态也是一种阻塞状态但是是无法被杀进程的相对应的S状态的进程就可以被干掉。 因此D状态被称为不可中断睡眠或深度休眠状态S状态被称为可中断睡眠或浅休眠状态。 在实际生产应用中如果查到了某个进程处于D状态那说明磁盘出现了严重问题系统差不多也要挂了。 5.3 T状态 这个状态我们是可以进行手动设置的我们需要用到kill的18 19号信号。 首先我们把打印hello word的程序跑起来然后用另一个终端直接把进程暂停 还可以 kill -18 恢复进程 但是我们发现S后面的加号没有了此时的4534号进程已经变成了一个后台进程此时使用ctrlc已经无法关闭这个进程了必须在另一个终端上用kill -9命令干掉这个进程。 实际上我们还可以把一个进程手动启动成一个后台进程方法就是在可以行文件后面加上符号 后台进程的好处就是可以不受干扰的继续执行命令行指令但是还是无法ctrlc干掉。 在进程执行期间如果出现了非法但不致命的操作进程就会被OS用T状态暂停 5.4 t状态 这个状态在我们调试代码打断点的时候会出现当进程被追踪断点停下时进程状态就是t 5.5 X状态 Z状态 要谈进程的退出我们就要先明确一个观点就是进程被创建的原因是什么 事实上进程被创建的原因就是要完成一些任务那任务是否完成就是一个很重要的事情。一般来说会通过进程执行的结果来告知父进程或OS进程对于任务的完成度如何。 Linux中可以通过 $? 查看最近程序退出时的错误信息 可以看到正常退出的程序错误信息为0不正常退出的程序错误信息就是非0 那么在一个进程结束的过程中也要向父进程和操作系统来报告结束信息是正常结束的或是异常结束的这个报告的过程中进程就处于Z状态。报告之后进程结束进程就处于X状态。 具体来讲进程结束要退出的时候它不会立即退出而是将自己的退出信息先维护在自己的 test_struct 中用来准备给父进程反应自己的退出信息。如果父进程一直不来查看子进程的退出信息并释放子进程那子进程就会一直处于Z状态。在Z状态的时候子进程的各种资源还是暂时存在内存中此时如果一直得不到释放就会引起操作系统层面上的内存泄漏。 也就是说后面我们要让父进程对子进程的状态进行管理及时释放掉处于僵尸状态的子进程防止内存泄漏。 6. 孤儿进程 前面处于Z状态的进程我们叫做僵尸进程其特点是子进程结束了父进程还在跑。而孤儿进程是父进程先退出子进程此时就是一个孤儿进程了。 现在对于孤儿进程就有了一个奇怪的问题如果说孤儿进程的父进程已经退出了那到时候孤儿进程想退出的话它的退出信息该交给谁又由谁来释放孤儿进程的资源。 事实上当孤儿进程出现时就会被 pid 为 1 的进程给领养这个进程是系统进程也就是说之后会由系统进程来管理孤儿进程包括获取它的退出信息释放它的资源。当领养完成后孤儿进程会自动变成一个后台进程。