恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Linux---进程控制 (创建 终止 等待)
首页
资讯中心
/
Linux---进程控制 (创建 终止 等待)
Linux---进程控制 (创建 终止 等待)
发布时间:2026/9/7 18:40:10
进程创建进程创建的绝大部分内容在之前的学习中已经详细学习过了这里就简单看一下然后主要再看一下写时拷贝。fork会在当前进程内部创建一个子进程创建失败返回-1创建成功会有两个返回值对子进程返回0、父进程返回子进程的pid。进程调用fork当控制转移到内核中的 fork 代码后内核会做1.子进程也需要一个内存块及自己的内核数据结构task_struct、mm_struct这些由其分配。2.然后继承父进程的PCB、页表、进程地址空间等信息pid等信息进行修改。3.把子进程添加到系统进程列表中。写时拷贝页表中除了虚拟地址和物理地址的映射关系外还存在着权限这里权限实际是体现一个4KB页的权限这里就理解为每一个物理地址的权限。1.只有父进程的时候通过虚拟地址在页表映射找到物理地址可以找到对应的代码和数据此时的代码是只读的不能进行修改而数据是可写可读的可以对数据进行修改。2.子进程在被父进程创建之后会继承父进程的代码和数据此时父子进程过虚拟地址在页表映射找到物理地址可以找到对应的代码和数据但是此时的代码段对于父子进程都是只读的都不能进行修改。当其中一方对数据进行修改的时候会发生写时拷贝必须是数据才会发生写时拷贝 代码不可以(通过mm_struct可以知道)。所以操作系统实现写时拷贝通过设置页表权限当父子代码段权限出错的时候(并不是真的错误 只是一种识别方式)操作系统就对其进行写时拷贝。为什么写时拷贝1.减少创建时间子进程得到父进程的数据不需要额外的拷贝消耗2.减少内存浪费提高内存使用率。子进程不一定对父进程的所有变量都需要修改 可能只有部分做修改 需要修改的变量修改时写时拷贝父进程创建子进程的目的1.执行自己后面代码2.执行全新的功能 指如令fork失败的原因1.内核数据结构创建失败 PCB页表内存没有申请出来2.加载内存时候失败 如代码和数据本质都是因为内存空间不够进程中止创建进程会占用资源那么进程中止同样的需要释放资源如释放PCB页表等。进程退出 三种情况1.代码运行结束结果正确。2.代码运行结束结果错误。3. 发生异常 如 /0、野指针问题等根据这三种子进程的状态情况父进程就知道子进程是否完成了自己的任务。main函数也是被其他函数调用的需要把返回值返回而它的父进程通过它的返回值就知道了该函数的运行情况通常就是前两种情况。如下 我们一个简单打开文件hh.txt的程序打开失败会返回1。该进程是父进程bash的子进程这个子进程的作用就是以只读方式打开文件(该文件不存在 最终会返回1)但是程序在运行之后就直接结束了那么父进程怎么知道是否成功呢父进程判断该进程是否完成任务就是通过接收该子进程main函数的返回值所以main函数的返回值就是该进程的退出码退出码是0代表成功其他的值表示出错的各种原因(会有多种)而这个退出码存储在当前子进程的task_struct中该信息没有被父进程接收之前子进程是僵尸状态在父进程回收之后子进程僵尸状态结束。通过echo $?打印最近一个程序(进程)退出时的退出码打印的结果就是该程序main函数的返回值所以main函数的返回值就是当前进程退出码而再一次使用后打印的结果是0因为打印的是echo进程的退出码上一次echo是正常结束退出码就是0。但是数字代表的具体信息是什么呢strerror(i)可以打印i号错误对应的字符串信息如下代码打印0到150号的信息我们可以发现信息从0到133一共有134个之后的打印的都是 Unknow 也就是运行正确会返回0 错误会有从1到133一共有133种不同的错误信息。errno在发生错误时自动设置errno为对应的错误编号如下当打开一个不存在的文件 或者ls 对一个不存在文件使用时 errno就是2 就是对应文件不存在的错误信息。这套134个退出码是系统自己设计的我们也可以不遵守自己设计退出码所对应的信息根据退出码自己知道是什么错误。这是上面两点程序正常情况结束的情况那么第三点异常呢如果发生异常了退出码没有意义如下代码发生 /0的错误 此时会发生异常 返回的结果并不是我们指定的666而是154退出码方式main函数结束就代表当前进程结束所以main函数的返回值就是退出码而普通的函数结束只是当前的函数结束了。exit(i)直接结束当前的进程 i就是当前进程的退出码等价于在main函数中的return i 之前应该是在单链表那里见到过如下代码main函数调用fun函数在fun函数中就exit(2)了此时程序直接结束了并没有打印555 且退出码是2。说明fun中用exit(2)就相当于main函数中的return 2直接把这个进程给结束了 。所以退出码的方式有两种1.main函数的return2.任意地方exit()_exit与exit的作用是类似的他们的区别呢来看一下 下图的现象之前在进度条实现那里知道了缓冲区的问题----打印的内容不会直接显示而是在缓冲区中只有刷新缓冲区内容才会显示出来。而刷新缓冲区可以用 \n、ffuls() 、另外就是在程序结束之后会自动刷新缓冲区。现象1.有\n的exit的和_exit的都会显示出来打印的内容(左上和右上图)2.没有\n的exit可以显示出打印出内容(程序结束后自动刷新缓冲区)而没有\n的_exit的却没有显示出打印的内容。(左下和右下图)所以根据现象分析进程如果通过exit退出进程退出后会自动刷新缓冲区而通过_exit退出进程退出后不会自动刷新缓冲区。原理exit是C库的而_exit是系统自己的exit是调用的c库里的而_exit是系统调用。exit 是man手册第三页代表库函数而_exit在man手册第二页代表系统调用。而之前学习知道进程由操作系统直接管理能真正结束一个进程的就只有操作系统其他都是间接封装了。而系统调用更接近操作系统库函数在系统调用的上层所以库里面其实是封装的系统调用所以exit其实底层调用的就是_exit这些都是我们已知的这里真正要提的是所以缓冲区的刷新不是操作系统内部的缓冲区而是库缓冲区是c语言提供的缓冲区。(为之后学习的内容做铺垫)进程等待为什么要等待之前在进程状态知道如果一个子进程退出它的父进程没有回收它的信息子进程会进入僵尸状态 一直不被回收就会造成内存泄漏的问题而解决方式就是通过进程等待。1.父进程获取子进程PCB里面的信息来知道获取子进程运行结果的信息。2. 结束子进程的僵尸状态解决内存泄漏的问题。而第二点回收资源是必须的第一点获取信息不是必要的。怎么等待通过wait或waitpid先来看一下waitwait等待任意一个退出的子进程 等待成功返回值为回收的子进程的pid等待失败为-1。先不管里面的参数先来见一下如下代码如果没有wait在子进程结束之后会进入僵尸状态而如果有wait 在子进程结束之后不会有僵尸状态如下 五秒后子进程会结束此时子进程进入僵尸状态再过五秒父进程通过wait接收子进程的PCB信息进而结束了子进程的僵尸状态并且wait返回值就是子进程的pid等待失败父子进程会同时进行父进程到了wait后会停下来等待直到子进程退出那么如果子进程一直不退出呢或者此时没有子进程正在运行呢再或者waitpid指定等待pid对应的子进程并不存在呢这时候就会等待失败如下 当前进程等待pid为id1的进程会等待失败 并且此时子进程僵尸状态不好被回收 一直是僵尸状态waitpid在返回值上和wait一样 接收成功返回子进程的pid 失败返回-1接下来看一下参数1.waitpid第一个参数pid①-1 代表等待任何子进程就和wait就一样②0 等待指定子进程的pid(就可以指定等待的子进程了)-1及等于0的情况这里不管2.输出型参数wait的参数和wait_pid第二个参数为输出型参数为了让父进程知道子进程的任务完成情况(进程退出的三种情况) 。如下代码及结果我们观察到该值并不是我们自己设置的子进程的退出码1而是256说明这个输出型参数的信息并不单纯的就是退出码。这个输出型参数参数类似于位图一个int有32个比特位高十六位不管后十六位中的前八位才是真正的退出码最后八位的第一位代表core dump标志最后七位代表终止信号如果不是异常的情况 后八位正常都是0。所以如果程序没有异常我们想拿到真正的退出码 可以通过(wai8)(0xFF)(0xFF只有最后的八位为1其他为都为0) 来获取退出码。所以命令行输入命令 其实就是bash进程通过这样wait的方式得到命令的退出码 echo $拿到但是异常呢之前知道kill -l 可以把所有的信号 打印出来 但是这些数字加字符串的方式其实就是宏的方式这些数字就代表着对应的字符串。异常了一般就是收到了异常信号后七位代表的信号如果都为0就代表无异常发生。一旦后七位不为0说明接收到了异常信号那么程序发生了异常此时的退出码无意义。wai0x7F得到wai后七位的值 也就是信号的值是怎么通过wait或者waitpid怎做到的进程之间具有独立性父进程得到子进程的信息是用过系统调用waitpid 那么它是从哪里拿到的如上信号及退出码存储在子进程的task_struct中系统调用从子进程的task_struct中把这两个信息拿到然后通过位运算的操作把它转换为这样位图的方式然后通过输出型参数的方式把这个值给了父进程中的变量sta 。所以僵尸状态作用就是如此把信息存在PCB中由父进程通过系统调用的方式拿到这些信息。getpid getppid也是类似的方法上面我们是通过位操作的方式拿到退出信息的但是其实系统提供了宏来帮我们能直接拿到退出信息。WIFEXITED(status):判断子进程是否正常退出正常退出返回真如果被信号终止返回假。查看进程 是否是正常退出WEXITSTATUS(status): 若 WIFEXITED非零提取子进程退出码。查看进程 的退出码所以可以写如下的代码先判断父进程是否接受到子进程接收到后判断是否正常退出如果正常退出再打印其退出码这样没接收到子进程、非正常退出正常退出的情况都可以处理。3.第三个参数 options 阻塞控制里面值主要关注下面的NOHANG阻塞于非阻塞区别之前第三个参数为NULL时候父进程等待子进程时候就一直处于阻塞状态接收成功返回子进程的pid失败返回-1父进程等待子进程期间它不会做其他的事情。第三个参数为NOHANG后此时父进程处于非阻塞状态返回的参数大于0代表等待结束等于0这一轮调用结束子进程并未退出小于0等待失败。 所以就可以用下面这样的方式来让父进程等待其他可以做其他事情。while(1) { if(id0) { printf(等待成功\n); break; } else if(id0) { printf(这一轮等待结束\n); //等待期间父进程做的事情 } else { printf(等待失败\n); break; } }