Linux----------进程控制

进程创建:

1. fork函数

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


2. 进程调用fork,当控制转移到内核中的fork代码后,内核做:

(1) 分配新的内存块和内核数据结构给子进程

(2) 将父进程部分数据结构拷贝至子进程

(3) 添加子进程到系统进程列表当中

(4) fork返回,开始调度器调度


     当一个进程调用fork之后,就有两个二进制代码相同的进程,而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程。



这里可以看到三行输出,一行before,两行after。进程3421先打印before消息,然后它有打印after。另一个after消息由3422打印。这里我们可以注意到3422并没有打印before。这是因为什么?


所以,fork之前父进程独立执行,fork之后,父子进程两个执行流分别执行。注意:fork之后谁先执行完全由调度器决定。

3. fork函数返回值

(1) 子进程返回0.

(2) 父进程返回的是子进程的pid。

4. 写时拷贝

     通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本,具体见下图:


5. fork 常规用法

(1) 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如:父进程等待客户端请求,生成子进程来处理请求。

(2) 一个进程要执行一个不同的程序。例如:子进程从fork返回后,调用exec函数。

6. fork调用失败的原因

(1) 系统中有太多的进程

(2) 实际用户的进程数超过了限制

7. vfork函数

     也是用来创建子进程,但是:

(1) vfork用于创建一个子进程,而子进程和父进程共享地址空间,fork的子进程具有独立的地址空间

(2) vfork保证子进程先运行,在它调用exec或(exit)之后父进程才可能调度运行。

下面是vfork的使用程序:



可见,子进程直接改变了父进程的变量值,因为子进程在父进程的地址空间中运行。

进程终止:

1. 进程退出场景

(1) 代码运行完毕,结果正确

(2) 代码运行完毕,结果不正确

(3) 代码异常终止

2. 进程常见的退出方法

(1) 正常终止(可以通过 echo $? 查看退出码):

>> 从main返回

>> 调用exit

>> _exit

(2) 异常退出

>> ctrl + c:信号终止

(3) return退出

return是一种更常见的退出进程方法,执行return  n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当作exit 的参数。

3. _exit函数


 说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

4. exit函数


exit最后也会调用exit,但在调用exit之前,还做了其他工作:

>> 执行用户通过atexit或on_exit定义的清理函数。

>> 关闭所有打开的流,所有的缓存数据均被写入。

>> 调用_exit


实例:


进程等待:

1. 进程等待的必要性

(1) 子进程退出,父进程如果不管不顾,就可能造成"僵尸进程"的问题,进而造成内存泄漏。

(2) 另外,进程一旦变成僵尸状态,那就刀枪不入,kill  -9 也无法将其杀死,因为谁也没有办法杀死一个已经死去的进程。

(3) 最后,父进程派给子进程的任务完成的如何,父进程是需要知道的。如:子进程运行完成,结果对还是不对,或者是否正常退出。

(4) 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

2. 进程等待的方法

(1) wait方法


(2) waitpid方法


>> 如果子进程已经退出,调用wait / waitpid时,wait / waitpid会立即返回,并且释放资源,获得子进程退出信息。

>> 如果在任意时刻调用wait / wairpid ,子进程存在且正常运行,则进程可能阻塞。 

>> 如果不存在该子进程,则立即出错返回。


3. 获取子进程status

(1) wait和waitpid,都有一个status参数,该参数是一个输出型参数由操作系统填充。

(2) 如果传递NULL,表示不关心子进程的退出状态信息。

(3) 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

(4) status不能简单的当做整型来看待,可以当做位图来看待,具体细节如下图(只研究status低16比特位):





具体代码实现:

(1) 进程的阻塞等待方式: 

  


(2) 进程的非阻塞等待方式:



进程程序替换:

1. 替换原理

  用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换。从新程序的启动历程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

2. 替换函数

  其实有6中以exec开头的函数,统称为exec函数:


3. 函数解释

(1) 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。 

(2) 如果调用出错则返回-1。

(3) 所以exec函数只有出错的返回值而没有成功的返回值。

4. 命名解释

(1) l(list):表示参数采用列表

(2) v(vector):参数用数组

(3) p(path):有自动搜索环境变量PATH

(4) e(env):表示自己维护环境变量


   事实上,只有execve是真正的系统调用, 其它5个函数最终都调用execve,所以execve在man手册第2节,其他函数在man手册第3节。这些函数的关系图如下图所示:


函数和进程之间的相似性:

exec / exit就像call / return:一个C程序有很多函数组成。一个函数可以调用另一个函数,同时传递给它多一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call / return系统进行通信。这种通过参数和返回值拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于子程序之内的模式扩展到程序之间,如下图:


一个C程序可以fork / exec另一个程序,并传给它一些参数,这个被调用的程序执行一定的操作,然后通过exit(n)来返回,调用它的进程可以通过wait(&ret)来获取exit的返回值。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值