linux进程控制(一)

1.进程创建

1.1 fork

在linux中fork函数是非常重要的函数,从已存在进程中创建一个新进程。新进程为子进程,原进程为父进程。

返回值,子进程返回0,父进程返回子进程pid,出错返回-1

  1. 为什么给子进程返回0,父进程返回子进程的pid?
  2. 为什么一个函数fork有两个返回值?
  3. 为什么一个id既>0有=0?

给父进程返回子进程的pid便于父进程管理子进程 (父进程可以创建多个子进程,根据进程pid对进程进行管理),而对于子进程返回0,一方面,父子进程不同的返回值,通过返回值来指向不同的代码,达到创建子进程的目的(执行父进程代码的一部分,分担父进程的任务;或者指向全新的代码,由父进程创建便于管理,包括资源回收等,比如xshell的bash执行pwd,ls,就是指向权限的代码,和bash执行的代码或任务没多大关系 )一个id既>0又等于0是因为发生了写时拷贝,fork之前父进程页表数据部分由读写->只读,子进程拷贝父进程页表,接着子进程写入,发现是只读,发生缺页中断,OS分配物理内存,改变子进程页表数据该页为读写,进行写入;而父进程写入的时候也是发生缺页中断,写时拷贝;fork之后的代码是父子进程共享,所以父进程和子进程id的虚拟地址一样,但物理地址不一样,并且是两个进程,两个返回值,两套逻辑

在学的时候,我就想子进程写时拷贝的时候可以把父进程该页的属性改为读写,因为共享没有独立性,而现在子进程不和父进程该页,父进程完全可以在这一页上操作。但是OS没有这样干,一方面,如果子进程拷贝,就要改父进程,那如果父进程这一页,或者这些页都不会写入呢?OS的原则就是能晚处理就晚处理;另一方面,统一操作,无论父子进程,写入都是COW,如果还要改另一个进程的页表,那硬件设计更复杂

1.2 fork常规用法

  • 父进程希望赋值自己,使父子进程同时执行不同的代码段。比如,父进程等待客户端请求,生成子进程来处理请求
  • 一个进程要执行一个不同的程序,例如子进程fork返回后,调用exec函数。

fork创建子进程后,父子进程谁先运行是不确定的,取决于调度器;但是子进程先结束,父进程获取子进程退出信息来判断任务完成情况+回收子进程资源。

1.3 fork调用失败的原因

  • 系统中有太多的进程
  • 实际用的的进程数据超过了限制

系统有全局最大进程数限制,单个用户也有最大进程数限制

就是内存不够用啦,因为创建进程肯定要分配内核数据结构占用内存

在man手册,/xxx 可以查找xxx


fork系统调用失败,返回-1,并且设置errno,手动给perror传入错误信息前缀

其实status&0x7F,0x7F是十六进制字面量常量,字面量类型默认是int

对空指针进程写操作,段错误

信号11是SIGSEGV,segmentation violation,段错误

牛刀小试

  1. 不算 main 这个进程自身,创建了多少个进程
cpp 复制代码
int main(int argc, char* argv[]){
   fork();
   fork();
   fork();
}

A.7

B.8

C.9

D.10

A

套公式,m次顺序fork(),2^m-1个子进程被创建,7

  1. 不算 main 这个进程自身,创建了多少个进程
cpp 复制代码
int main(int argc, char* argv[]){
   fork();
   fork() && fork() || fork();
   fork();
}

A.18

B.19

C.20

D.21

B

最后所有进程都会创建5号进程,10*2-1=19

2.进程终止

进程终止的本质是释放系统资源,释放进程申请的相关数据结构和对应的数据和代码

2.1 进程退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常中止(先帝创业未半而中道崩殂)

如果是局部函数(非main函数)调用exit或者_exit整个进程结束,如果局部函数return,局部函数结束

进程结束,return正常结束,exit,_exit强制结束(一般没有运行完),发生错误(比如除零),被信号杀死

退出码的意义要么系统提供要么自定义,大多数情况下自定义

进程是有退出返回值的,Linux源码task_struct也就是进程的PCB是有exit_code和exit_signal,因为子进程无论是异常结束还是正常结束,都会给父进程发信号也就是exit_signal,一般是SIGCHLD,也就是17,此时子进程是僵尸状态,父进程知道我有子进程走了,我要wait来收尸了。子进程会释放代码和数据,但是PCB不会释放,父进程通过wait获取生命结束的子进程的PCB,来获取exit_code,从而判断交给子进程的任务完成的怎么样

cpp 复制代码
int exit_code, exit_signal;

echo $?获取的是exit_code的低16位,也就是退出返回值+终止信号+core dump标志

打印结果分情况讨论,

  • 进程正常结束,echo $?打印的是退出返回值,return跟的是退出返回值;正常结束,终止信号没有意义
  • 进程异常结束,打印的是128+信号值,core dump位为1(正常结束为0),有对应的SIG(signal,信号),除零错误是SIGFPE,signal float point exception,浮点数异常信号,值为8,所以除零打印结果是128+8=136
    异常结束,都没return,退出返回值没有意义,所以不考虑

exit_code是位图结构

cpp 复制代码
正常结束:exit_code>>8
异常结束:exit_code&&0x7FF


kill -l查看所有的信号

库函数调用失败,错误码放在errno

或者说我们认为不是信号杀死,就是正常结束,亦或者说进程自行结束,要么是到底返回0,要么是因为一些错误比如文件打不开,返回非零;而非零有可能执行一半,也有可能和return 0是一个分支;而被信号杀死是异常,是系统认为进程违规,进行被迫死亡;然而return 0逻辑也有可能有问题,只是程序员自身没意识到

牛刀小试

  1. 如何使一个进程退出,以下错误的是
    A.在程序的任意位置调用return
    B.在main函数中调用return
    C.在程序的任意位置调用exit接口
    D.在程序的任意位置调用_exit接口

A

  1. 以下关于进程退出描述正确的有: [多选]
    A.exit函数退出一个进程时会刷新文件缓冲区
    B.exit函数退出一个进程时不会刷新文件缓冲区
    C._exit函数退出一个进程时会刷新文件缓冲区
    D._exit函数退出一个进程时不会刷新文件缓冲区

AD

  1. 关于进程退出返回值的说法中,正确的有
    A.进程退出的返回值可以随便设置
    B.进程的退出返回值可以在父进程中通过wait/waitpid接口获取
    C.程序异常退出时,进程返回值为-1
    D.进程的退出返回值可以在任意进程中通过wait/waitpid接口获取

B

3.进程等待

3.1 为什么要进行进程等待

一方面进程结束后只会释放代码和数据、页表、地址空间,但是PCB留着,因为会把退出状态写入PCB的exit_code字段,便于父进程获取子进程退出状态,此时子进程处于僵尸态

  • 如果不回收,会导致资源泄露
  • 如果不回收,僵尸状态的进程没有办法被解决掉,因为kill -9也无能为力,谁也没有办法杀死一个死掉的进程
  • 另一方面,父进程也要回收子进程来确定交给子进程的工作完成的怎么样,是否运行完成,是否正常运行,结果是否正确
  • 父进程通过进程等待的方式,回收♻️子进程资源,获取子进程退出信息

3.2 wait

3.2.1 wait演示

子进程结束->僵尸态(维持5s)->父进程回收子进程

父进程阻塞->子进程结束->父进程回收子进程

3.2.2 参数返回值说明

pid_t wait(*status); status为输出型参数,如果不关心子进程的退出状态,*status直接填NULL

status可以接受子进程的退出状态,如果是return x/exit(x)/_exit(x)打印的就是x,如果是被信号杀死,打印的就是信号对应的值(不同的是,shell下,如果是被信号杀死,打印的是128+信号值)

如果wait返回值为-1,说明没有子进程,系统调用会直接出错返回,代码继续往下执行。

如果wait返回值>0,则是子进程的pid

3.3 waitpid

3.3.1 参数返回值说明

pid_t waitpid(pid_t pid, int* status, int options);

如果pid为-1,表示等待任意子进程(因为父进程的所有子进程维持一张双向链表,遍历链表,回收第一个状态为EXIT_ZOMBIE的子进程),如果pid>0,表示只等待进程id为pid的进程;

如果options是0,是阻塞等待,如果没有子进程结束,就阻塞父进程;如果options是WNOHANG,是非阻塞等待,没有子进程结束,直接跳过waitpid执行后续代码;如果每隔一段时间执行非阻塞等待,就是非阻塞轮询等待,而在非等待的时间可以做其它事。

如果pid=-1,options为0,waitpid和wait效果一样
那么阻塞等待和非阻塞轮询等待,哪一种更高效呢?

好像看起来非阻塞轮询更高效,因为阻塞等待期间什么都没干,但是非阻塞轮询把等待时间利用了起来,做了其它事。但是这个很难判断,因为父进程要等多长时间是由子进程决定的,而且轮询肯定是用while实现,while内能做什么呢?而且开销呢?用户态和内核态之间切换的开销是非常大的,而且父进程一直轮询占着CPU不干正经事,不如阻塞,把CPU让出来

如果调用中出错,返回-1并设置errno

没有子进程/pid不是父进程的子进程号,errno设置为ECHILD;如果是被信号中断(除ECHILD之外的信号),errno设置为EINTR

测试等待任意进程

pid>0

测试要等待的子进程不是死掉的进程

如果waitpid参数pid填的不是自己的子进程的pid,返回-1设置errno

对waitpid返回值进行打印

3.3.2 宏介绍

而使用的时候获取exit_code=(status>>8)&0xFF,exit_signal=status&0x7F,core_dump=(status>>7)&1;有些麻烦😓

系统定义好了WIFEXITED,看字面意思,W是wait的缩写,wait操作是exit系列操作退出,没有被信号杀死,也就是低七位信号为零

cpp 复制代码
#define WIFEXITED(status) ((status&0x7F)==0)

WEXITSTATUS,也就是进程退出状态,在WIFEXITED为真的情况下,这个宏才有意义,#define WEXITSTATUS(status) ((status&0xff00)>>8)

写代码,也可以直接用这两个宏



等待期间做任务

创建多个子进程


牛刀小试

  1. 以下不是进程等待功能的是()
    A.获取子进程的退出码
    B.释放僵尸子进程资源
    C.等待子进程退出
    D.退出指定子进程

D

  1. 关于pid_t waitpid(pid_t pid,int *status,int options);函数,以下描述错误的有()
    A.若pid大于0,则表示等待指定的子进程退出
    B.若pid等于-1,则表示等待任意一个子进程退出
    C.status参数用于获取退出子进程的退出码
    D.若options选项参数被设置为WNOHANG则waitpid为一直阻塞

D.WNOHANG是非阻塞等待

  1. 关于waitpid函数WNOHANG参数的描述正确的是:()[多选]
    A.若选项参数被设置为WNOHANG则waitpid为一直阻塞
    B.若选项参数被设置为WNOHANG则waitpid为非阻塞
    C.若waitpid设置WNOHANG后,没有子进程退出则返回值为-1
    D.若waitpid设置WNOHANG后,没有子进程退出则返回值为0

BD

相关推荐
开开心心_Every1 小时前
轻量级PDF阅读器,仅几M大小打开秒开
linux·运维·服务器·安全·macos·pdf·phpstorm
the_fat_bird2 小时前
ubuntu install nvidia gpu driver
linux·运维·ubuntu
IMPYLH2 小时前
Linux 的 tac 命令
linux·运维·服务器·bash
计算机安禾2 小时前
【Linux从入门到精通】第50篇:专栏总结与Linux学习之路的未来展望
linux·运维·学习
zhouwy1132 小时前
Linux 内核学习笔记:从零搭建内核开发与调试环境
linux
GottdesKrieges2 小时前
OceanBase备份常见问题
linux·网络·oceanbase
白菜欣2 小时前
Linux —进程概念
linux·运维·服务器
iuu_star3 小时前
Vue+FastAPI 项目宝塔Linux部署指南
linux·运维·fastapi
楼田莉子3 小时前
仿Muduo的高并发服务器:Channel模块与Poller模块
linux·服务器·c++·学习·设计模式