进程控制
一.进程创建
fork()
1.以父进程为模板复制创建一个子进程,父子进程共享代码,数据独有(写时复制技术)。
2.fork的返回值,父进程返回子进程pid(>0),子进程返回0。
3.取决于cpu调度。
vfork()
1.子进程没有(退出)或者运行其他程序,父进程阻塞,意味着子进程先运行,子进程没有运行完,父进程就不会运行。子进程退出(不能在main函数中以return方式退出)后父进程才能使用。
2.子进程先运行的原因:因为创建出子进程,大多数时候都是为了让它运行其他程序。
3.父进程阻塞的原因:vfork创建子进程后,父子进程共用一块虚拟地址空间,那么他们共用同一个栈区,有可能造成调用栈混乱。
vfork设计出来的目的就是为了创建一个子进程,然后直接运行其他的程序。重新运行其他的程序就是重新给子进程开辟新的空间,更新它自己的一份地址空间和页表,就不会造成冲突。
自从fork函数使用了写时拷贝技术以后,vfork就被淘汰了。
二.进程终止
终止方式:
1.main函数中return是退出进程(温和)
2.exit(int status)库函数调用
在程序任意位置调用都会使进程退出
status是进程退出状态
3._exit(int status)系统调用接口
在程序任意位置调用都会使进程退出
exit最终调用的就是这个接口退出的
exit 与_exit的区别
exit是温和性的退出,退出前会温和的释放资源,刷新缓冲区
_exit是暴力退出,直接释放资源退出,不会刷新缓冲区
main 1 exit 默认
main 2 exit
main 3 exit 库函数
不管哪种主动退出方式,都会返回一个数字,这个数字是进程的退出状态,它表明了进程的退出原因。
退出场景:
1.正常运行完毕,结果符合预期
2.正常运行完毕,结果不符合预期
3.异常退出,返回状态将不能作为评判标准
三.进程等待
一个进程退出之后因为要保存自己的退出原因,因此并不会释放所有的资源,它等着父进程查看它的退出原因,然后释放所有资源。
假如父进程根本没管,那么这个子进程就成了僵尸进程,造成资源泄露。
因此为了防止出现僵尸进程,父进程就应该管一下子进程的退出。
进程等待就是等待子进程的状态改变,获取子进程的退出状态码,允许系统释放子进程的所有资源,这时候子进程在所有资源才会被释放掉
进程等待是避免产生僵尸进程的主要方式。
阻塞:一个函数为了完成功能,没有完成就不返回
非阻塞:一个函数为了完成功能,但没有完成也会立即返回
进程等待的方式:
1.pid_t wait(int *status)
status用于获取子进程 退出状态码
返回值是返回退出的子进程pid
wait函数的目的就是为了等待任意一个子进程的退出,获取状态码。因为wait是一个阻塞型函数,因此如果没有子进程退出,就一直等待(任意一个子进程),直到有子进程退出。若没有子进程,则报错。只能等待一个子进程。
2.pid_t waitpid(pid_t, int *status, int options)阻塞非阻塞可选
pid: -1:等待任意子进程
>0:等待指定的子进程
status:获取退出状态码(使用了两个字节)
options:0:阻塞 (默认) WNOHANG:非阻塞
返回值:-1:出错
=0:没有子进程退出(WNOHANG)
>0:退出的子进程pid
我们通常根据低7位是否为0来判断进程是否正常退出,因为正常退出,低7位没有信号值,为0;异常退出,低7位要存储信号值,因此大于0.
高8位 | 低7位 |
退出码 coerdump 异常退出的信号值
WIFEXITED通过wait获取的状态判断进程是否正常退出
WEXITSIATUS正常退出则获取一下进程的退出时返回的状态码
wait获取的状态(status),使用了两个字节,这两个字节中:
高8位存储进程退出时返回的状态码
低7位存储的是引起进程异常退出的信号
还有中间的1位是coredump标志
四.进程程序替换,微型shell,重新认识shell运行原理
1.程序替换:替换的是代码段,初始化数据区域
程序替换相当于让虚拟地址空间中的代码段地址指向了物理内存的另一段代码位置。
这样的话虚拟地址空间中原先的数据区域以及堆栈都会重新初始化,因为现在的代码运行的根本不是复制的那些数据。
但是这个进程的pcb还是原来的pcb。
假如执行ls命令:
输入shell,回车
shell接收到输入的命令
shell去指定的目录下找ls这个程序
找到ls程序之后,shell创建一个子进程
将子进程的代码段给替换掉
一个程序main有三个参数,参数个数,参数的字符串指针,环境变量
2.exec函数族:
ececl execlp execle
execv execvp execve
l和v的区别:l是参数平铺一个一个通过exec函数参数赋予,v参数直接使用字符串指针数组。
execl/execv:需要我们给出要替换的程序的全路径名
execlp/execvp:只需要给出要替换的程序的名称就可以了(前提是程序已经放到指定的目录里)
execle:重新自己组织环境变量,不使用现有的
execl参数一个一个给,最后给一个NULL,execv组装好好给
添加参数的时候记住要有一个NULL表示参数的结尾。NULL之后还有一个参数是用于设置环境变量的,而且这个函数会清空所有的环境变量,因为这个接口就是让我们用户自己来设置的。