1.进程引入:
进程:动态的 运行中的程序;
进程查看:
Linux 下可以通过ps看运行的进程 ps -a 看全部;
实际工作 通过grep过滤 查看我们想要得进程 如:ps -aux|grep init
2.进程标识符
每个进程都有一个非负整数表示的唯一的id。叫做pid,类似于身份证。
Pid = 0 表示交换进程(swapper)。 Pid = 1:init进程,作用系统初始化。Init类似于开机启动程序。
3.父进程 子进程
A创建b,a管b是父 b管a是子;
4.C程序的存储空间分配情况
正文段。这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是频繁执行的程序(如文本编辑器、C编译器和shell等)在存储器中也只需有一个副本,另外正文段常常是只读的,以防止程序由于意外而修改其自身的指令。
初始化数据段。通常将此段称为数据段,它包含了程序中需明确地赋初值的变量。例如 C程序中出现在任何函数之外的声明:int maxcount=99; 使此变量带有其初值存放在初始化数据段中。
·非初始化数据段。通常将此段称为bss段,这一名称来源于一个早期的汇编运算符,意思是“block started by symbol”(由符号开始的块),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。出现在任何函数外的C声明,如 long sum[1000],此变量存放在非初始化数据段中。
-
·栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次调用函数时,其返回地址以及调用者的环境信息(例如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈可以递归调用C函数。递归函数每次调用自身时,就使用一个新的栈帧,因此一个函数调用实例中的变量集不会影响另一个函数调用实例中的变量。
·堆。通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于非初始化数据段和栈之间。
5.进程创建fork使用
Pid_t fork(void); 调用成功返回两次。返回为0,表示当前进程为子进程;返回为1(非父),表示为父;
调用失败,返回为-1;
Fork(void)先创建父进程,再创建子进程。且父进程的id和getpid一样的。而子进程的id与父进程不同;父子进程都会运行这个指令,但是结果不同。因为每个进程对应一个pid。
父进程进行 运行了fork’ 代码段共享 数据段直接copy了1份。
Fork 实际发生了代码的拷贝 fork以后 子进程跑父fork之后的内容。Fork前,代码段共享,fork后,代码段相对于复制给子进程。
举个例子:服务器与客户端 服务器自己是一个进程,创建子进程与客户端交互。
Socket收到一个连接请求,创建子进程对接。
5.1 fork编程实战:
一个现有进程可以调用fork函数创建一个新进程。
#include <unistd.h>
Pid_t fork(void);
返回值:子进程中返回0,父进程中返回子进程ID.出错返回-1
由fork创建的新进程被称为子进程(child process)。fork函数被调用一次,但返回两次。两次返回的唯一区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。将了进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID。fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID(进程ID0总是由内核交换进程使用,所以一个子进程的进程ID不可能为0)。
子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。注意,这是子进程所拥有的副本。父、子进程并不共享这些存储空间部分。父、子进程共享正文段(见7.6节)。
由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全复制。作为替代,使用了写时复制(Copy-On-Write,COW)技术。这些区域由父子进程共享,而且内核将它们的访问权限改变为只读的。如果父、子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统中的一“页” Bach[1986]的9.2节和McKusick等[1996]的5.6节和5.7节对这种特征做了更详细的说明。
5.2 vfork
vfork函数 也可以创建进程,与fork有什么区别
与fork关键区别一:
vfork 直接使用父进程存储空间,不拷贝。
与fork关键区别二:
vfork保证子进程先运行,当子进程调用exit退出后,必须使用exit才能正常退出子进程,必须调用exit 之后父进程才执行。
6,进程退出之正常退出
1.Main函数调用return
2.进程调用exit(),标准c库
3.进程调用 exit()或者 Exit(),属于系统调用 标重点
6.1补充:
1.进程最后一个线程返回
2.最后一个线程调用pthread exit
6.2 进程退出之异常退出
1. 调用abort
2.当进程收到某些信号时,如ctrl+C
3.最后一个线程对取消(cancellation)请求做出响应
补充(进程退出的方式总结):
6.3 退出的补充
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
对上述任意一种终止情形、我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit、_exit和_Exit),exit(参数3) 参数3可为0、1、2;实现这一点的方法是,将其退出状态(exit status作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或 waitpid函数(在下一节说明)取得其终止状态。
7、等待子进程
为啥要等待子进程退出?
创建子进程目的:为了让他去干活 干完了没? 正常退出:干完 exit(?); 不正常:异常退出
父进程等待子进程退出,并收集子进程的退出状态。子进程退出状态不被收集,变成僵死进程·(僵死进程(一直在运行))。
父进程获取子进程退出状态 调用函数:wait函数
Wait函数能使父进程阻塞 而waitpid 不会 waitpid更灵活。
7、2 孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。
Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的新的父进程进程。
8、父、子进程代码实践
代码测试wait返回的参数及WEXITSTATUS的返回:
运行结果如下:
9.exec族函数
博文学习:
《unix环境编程》业内权威
10 进程间的通信引入
在下一篇博文梳理