进程标识
ID为0的进程通常是调度进程,常常被称为交换进程,是内核的一部分,并不执行磁盘中的程序,常被称作系统进程。
ID为1的进程是init进程,在自举结束时由内核调用。init进程通常读取与进程有关的初始化文件(/etc/init.d等)并将系统引导到一个状态。init进程不会终止,他是一个用户进程,但是以超级用户特权运行。
ID为2的进程是页守护进程,负责虚拟存储器系统的分页操作。
虚拟存储器
CPU通过发出虚拟地址,虚拟地址再通过MMU翻译成物理地址,最后获得数据
将一块连续的内存,直接使用不符合实际,将一定大小的地址分成一页,通过页来访问数据,把每一页的首地址作为入口,即PTE来检索页,用页表来管理页。
函数fork
一个现有的进程可以调用fork创建一个新进程,fork函数被调用一次,但返回两次,在子进程返回0,在父进程返回子进程的ID,因为没有其他的函数可以获取所有子进程的ID,而父进程总是可以调用getppid获取父进程的ID。
子进程是父进程的副本,子进程获取父进程的进程空间、堆和栈的副本,但他们之间并不共享这些存储空间部份,只共享正文段。
正文段
C程序的存储空间布局
正文段
CPU执行的机器指令部分,通常是可共享的,即使是频繁执行的程序也只需要有一个副本,通常是只读的。例如,有几个用户都在使用文本编辑器,在内核中只需要该指令的一个副本即可。
初始化数据段
包含了程序中明确的赋值的变量,例如C函数中任何函数之外的声明。
未初始化数据段
bss段,在程序开始之前,内核将此段中的数据初始化为0或空指针。
栈
自动变量以及每次函数调用所需保存的信息都放在此段中,比如每次调用返回地址以及调用者的环境信息都存放在栈中;然后最近被调用的函数在栈上为其自动和临时变量分配空间。
堆
通常在堆中进行动态存储分配。
在fork之后通常跟随着exec,很多实现并不执行一个父进程数据段、栈和堆的完全副本,而是使用了写时复制技术。这些区域父子进程可以共享,但是如果任一方修改了其中的数据,只为修改的部份制作一个副本,通常是虚拟存储中的一“页”。
当标准输出、输入连接到终端设备时,它是行缓冲的,否则是全缓冲的。
文件共享
fork的一个特性是所有打开的文件描述符都被复制到子进程中。父进程和子进程共享一个文件表项。
子进程继承父进程下列属性:
- 文件表项
- 实际用户ID、实际组ID、有效用户ID、有效组ID
- 附属组ID
- 进程组ID
- 会话ID
- 控制终端
- 设置用户ID标志和设置组ID标志
- 当前工作目录
- 根目录
- 文件模式和创建屏蔽字
- 信号屏蔽和安排
- 对任意打开文件描述符的执行时关闭标志
- 环境
- 连接的共享存储段
- 存储映像
- 资源限制
区别如下:
- fork的返回值不同
- 进程ID不同
- 父进程的ID不同
- 子进程不继承父进程的文件锁
- 子进程的未处理闹钟被清除
- 子进程未处理的信号集设置为空集
fork失败的两个原因:
- 系统中有太多的进程
- 实际用户ID的进程总数超过了限制
fork的两种用法:
- 网络服务中父进程在收到请求后fork一个子进程处理这个请求,父进程继续等待下一个请求。
- 一个进程要执行不同的程序,在shell中是常见情况,子进程在fork返回后立即调用exec
函数 exit
不管进程以何种形式终止,都会执行同一段代码,即关闭所有打开的文件描述符,释放所使用的存储器
退出状态:main函数返回值,或者是传递参数给exit、_exit、_Exit的参数
终止状态:调用_exit或者_Exit最后返回给父进程的值
孤儿进程:父进程在子进程之前终止。这种情况下,该子进程会被init进程收养,保证所有进程都有一个父进程。内核中是这样处理的:在一个进程终止时,内核会检查所有活动的进程,然后判断它是否是要终止进程的子进程,如果是,则该进程的父进程ID更改为1。
僵死进程:子进程在父进程之前终止,父进程可以用wait或者waitpid函数获取它的内核为子进程保存的信息。至少包括进程ID、终止状态以及该进程使用的CPU时间总量。一个已经终止,但是父进程还未对其进行善后处理的进程为僵死进程。
孤儿进程并不会有什么危害,因为孤儿进程会被init进程接管,init进程会定时的调用wait函数去获取孤儿进程的终止状态,内核随机释放所占用的数据结构。产生僵尸进程的元凶是父进程,父进程没有及时处理已经终止的子进程,导致进程ID和空间持续占用,这时候只需要调用kill函数发送SIGTERM或者是SIGKILL信号杀死父进程,僵尸进程也就变成了子进程,子进程会被init函数接管,最终被销毁,总的来说,两种方式:
- 信号机制,子进程终止时父进程会收到一个SIGCHLD信号,在父进程中进行处理
- fork两次,使子进程变成孤儿进程
init函数不会产生僵死进程,因为只要有一个子进程终止,init就会调用一个wait函数取得其终止状态。一个init的子进程可能指init函数产生的子进程,也可能指孤儿进程。
函数wait和waitpid
当一个进程正常或异常终止时,内核就向其父进程发送一个SIGCHLD信号。
调用wait或者是waitpid的进程发生了什么:
- 如果其所有子进程都还在运行,则阻塞
- 如果一个进程已终止,父进程取得他的终止状态立即返回
- 如果没有自己进程则立即出错返回
两个函数区别如下:
- wait使其调用者阻塞,但是waitpid有一个选项可是其调用者不阻塞
- waitpid可以控制它所等待的进程
如果等待的进程是一个僵死进程,则wait立即返回,如果其有多个子进程,在第一个子进程终止时返回。
core文件介绍
在一个进程崩溃时,一般会在指定目录下生成一个core文件。core文件仅仅是一个内存映像(同时加上调试信息),主要用来调试。
竞争条件
当多个进程都企图对共享数据进行处理,而最后的结果又取决于进程运行的顺序时,我们认为发生了竞争条件。
函数exec
使用fork函数创建一个新的子进程后,子进程往往要调用另一种exec函数执行另一个程序。当进程调用exec后,该进程执行的程序完全替换为新程序,新程序从main函数开始执行。
进程会计
进程调度
调度策略和调度优先级是由内核确定的,进程可以选择nice值降低对CPU的占用,特权进程允许提高权限
进程组
进程组是一个或多个进程的集合,通常他们是在同一种作业结合起来的。同一进程组的各进程接受来自同一个登陆终端的各种信号,每个进程组有唯一一个进程组ID
每个进程组有一个组长进程,组长进程的ID等于其进程ID
该进程组长可以创建一个进程组、创建该组中的进程、然后终止。只要某进程组中有一个进程存在,该进程组就存在。这与其组长进程组是否终止无关
一个进程只能为他自己或它的子进程设置进程组ID,在他的子进程调用了exec后就不能再更改子进程的进程组ID
会话
会话是一个或多个进程组的集合
通常是由shell的管道降级各进程编成一组