进程控制—进程创建/等待/终止

进程创建

进程创建被定义为通过复进程创建子进程的过程
  • 进程创建的一般过程

1.给新建的进程分配一个内部的标识符,在内核中分配PCB\
2.复制父进程的环境\
3.为父进程分配资源(代码,数据,堆栈)\
4.父进程地址空间的内容也复制到新的进程空间中
5.将该放到就绪队列

  • 进程的撤销
    1.关闭软中断\
    2.回收资源,如关闭其打开的文件\
    3.写记账信息\
    4.将进程的状态制为僵尸态\
    5.转存储(进程)调度,让出CPU资源

初识fork函数

在Linux中fork函数非常重要,它能够从已存在的进程中创建出一个新进程.这个新进程就为子进程,而原进程为父进程.
  • 函数原型
#include<unistd.h>
    pid_t fork(void);

返回值:子进程中返回0,父进程返回子进程pid,出错返回-1(将ID返回给父进程的因为没有函数能让父进程得到子进程PID,这样会便于管理子进程)

进程调用fork,当控制转移到内核中的frok代码后,内核会做如下工作:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当种
  • frok返回,开始调度器调度

这里写图片描述

fork常规用法

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

fork调用失败的原因

1.系统中有太多的进程
2.实际用户的进程数超过了限制

  • 当一个进程调用fork之后,就有两个二进制代码相同的进程,而且他们都运行到相同的地方,这两个进程的代码共享,父子不再写入的时候,数据也是共享的,当一方试图写入,便以写时拷贝的方式各自存放在一个副本中(子进程获得父进程数据空间,堆,栈的副本<一定注意是副本>);
  • 父进程和子进程只共享正文段(.text)
  • 由于fork之后经常跟随者exec(程序加载函数),所以现在很多操作系统的实现并不执行一个父进程的副本,而是使用了写时拷贝(这些区域由父子进程所共享,而且内核将这些区域的访问权限设定为只读权限,如果父进程和子进程中的任意一个区域试图被修改,则内核会为修改区域产生一个副本)。
  • fork之后父进程和子进程的执行顺序是随机的,取决于内核所使用的调度算法。

写时拷贝

读时都是共享内容和内存,一旦写,就不共享,便以写时拷贝的方式各自开辟空间创建对象,成为相互独立的两份副本。

写实拷贝技术(其实可以理解成需要写入时才去分配空间拷贝)也就是写入时复制(Copy-on-write)当然,如果有多个呼叫者同时要求相同资源,他们会共同取得相同的指标指向相同的资源.直到某个呼叫者尝试修改资源时,系统才会真正复制一个副本(private copy)给该呼叫者,以避免被修改的资源被直接察觉到.(此时读也拷贝)

在C++深浅拷贝问题中,我们知道浅拷贝—只拷贝指针,而深拷贝则是拷贝内存中的值, 并重新生成对象,那么在Linux中,string类引用计数的浅拷贝 = 写时拷贝(注意在VS环境下string类则是采用深拷贝),同fork一样,这都是一个写时拷贝的应用.

父进程和子进程的区别:

  • fork的返回值不同
  • 进程ID,父进程ID不同
  • 子进程不继承父进程设置的文件锁
  • 子进程未处理的闹钟被清楚
  • 子进程未处理的信号集,被设置为空集

vfork函数

vfork也是用来创建子进程,但是,
1.vfork用于创建一个与父进程共享地址空间的子进程,新进程的目的是exec一个新程序,而fork的子进程则拥有独立的地址空间;
2.vfork保证子进程先在父进程的空间中运行,只要创建子进程一直运行子进程,直到子进程运行结束(调用exec或(exit)后)父进程才能恢复运行.

扫描二维码关注公众号,回复: 1103596 查看本文章

fork与vfork区别

1.fork ():子进程拷贝父进程的数据段,代码段 ,vfork ( ):子进程与父进程共享数据段
2.fork父子进程交替运行, vfork ()保证子进程先运行,父进程阻塞,直到子进程结束(使用exit或excl),如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
3.fork实现了写时拷贝,vfork就算是写,也不拷贝
4.就算是fork实现了写实拷贝,性能也没有vfork高
关于具体fork与vfork区别及例子参考

进程终止

正常终止

1.main中执行return退出
2.exit C库函数->执行(回调函数)用户定义的清理函数->刷新输出缓冲区,关闭标准I/O流等详细参考
3._exit 内核层面系统调用,它为进程提供了一种无需终止运行终止处理程序或信号处理程序而终止的办法。
4.进程的最后一个线程调用pthread_exit函数。

不正常终止

1.ctrl + c 信号终止
2.kill
3.abort()

退出码: 0~255 只用了int的8个比特位,其余的为有别的用途,比如,程序是否正常退出,如果是异常瑞出,则要记录导致异常退出的原因。

如果正常退出,返回值为0
如果不正常退出,返回值为非0
(用 echo $? 查询)

  • 所有不正常的推出都是信号
  • @exit 注册的回调函数 来去都不带
  • on_exit可以带上更多的数据进而退出

不管进程以上面的何种方式进行终止,都会执行内核中的同一段代码,这段代码为相应的进程关闭所有打开的描述符,释放进程所拥有的地址空间。

进程等待

父进程通过wait函数或waitpid函数等待子进程,回收子进程的资源,获取子进程的退出信息。

1.wait函数
参数:获取子进程的退出状态,不关心则可设置为NULL。
返回值:成功返回被等待进程的pid,失败返回-1。

wait 干了三件事情:
1.阻塞当前进程,知道有子进程退出,他才返回\
2.回收子进程在内核中的残留信息(资源)\
3.获得子进程的退出状态\

2.waitpid函数
参数:
pid:
pid=-1,等待任一个子进程,与wait等效
pid>0,等待其进程ID与pid相等的子进程

WIFEXITED(status)//返回非0 表示正常退出
WEXITSTATUS(status)//获得退出码
WIFSIGNALED(status)//返回非0,由信号导致瑞出

status:
WIFEXITED(status):查看进程是否正常退出
WEXITSTATUS(status):查看进程的退出码
options:
WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予等待。若正常结束,则返回该子进程的ID。

返回值:
当正常返回时,返回子进程的pid。
若设置了WNOHANG,而pid指定的子进程没有结束,则返回0。
如果调用出错,则返回-1。

获取子进程status(可将其看做位图,只研究低16位)

wait和waitpid都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态。
如果不为空,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

这里写图片描述

正常退出时,打印的是子进程的status的次低8位(进程的退出状态),异常退出时,打印的是子进程的status的低8位(终止信号)。

猜你喜欢

转载自blog.csdn.net/ego_bai/article/details/79780128