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

相关推荐
程序员AlbertTu10 分钟前
第2章 上手 Linux:环境与文件操作
linux·运维·服务器
☆凡尘清心☆1 小时前
CentOS Stream 9 源码编译搭建 Zabbix7.0.29 完整部署方案(部署成功)
linux·运维·centos
Escalating_xu1 小时前
【Linux】进程间通信:从匿名管道、FIFO 到共享内存、消息队列与信号量
java·linux·面试
予昊1 小时前
Linux 资源控制实战分析:用 LXC 亲手造一个 “迷你虚拟机“
linux·运维·服务器
allforgood1 小时前
管理网络安全
linux
灵晔君2 小时前
【Linux】进程控制(一)——进程创建、进程终止与进程等待
linux·c语言·算法
wsad05322 小时前
CentOS 10 上使用 Docker Compose 部署 Discuz X5.0 论坛完整教程
linux·docker·centos·discuz
张宇Joaquin2 小时前
openEuler编译和更换内核的方法
java·linux·服务器
小此方2 小时前
Re:Linux系统篇(五十六)线程篇 · 九:基于阻塞队列的生产者消费者模型与条件变量
linux·运维·服务器
迷途之人不知返2 小时前
自动化工程的构建
linux·自动化