1、fork创建进程
fork函数原型 : pid_t fork(void)
fork函数会生成一个新的进程,调用fork函数的进程为父进程,新生成的进程为子进程。fork函数调用一次返回两次,在父子进程中返回进程的pid,在子进程中返回0,失败返回-1.
子进程的代码与父进程完全相同,同时他还会复制父进程的数据(堆数据、栈数据和静态数据)。数据的复制采用 写书拷贝技术(我们后面会详细说明),即只有在任意进程(父进程或进程)对数据执行了写操作时,复制才会发生,但它不执行一个父进程数据、堆、段、栈的完全复制,这些区域的父子进程共享,而内核将他们的权限改为只读。如果父子进程的任何一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,并且是以虚拟的存储器中的“一页”为单位复制。
子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如:子进程获得父进程数据空间、堆和栈的副本。
注意:这是子进程所拥有的副本。父子进程不共享这些存储空间部分,父子进程共享正文段。
vfork函数原型:pid_t vfork(void)
vfork函数的调用序列和返回值与fork相同,但两者语义不同。
vfork用于创建一个新进程,而该进程的目的是exec一个新程序,(exec我们稍后会说)。vfork和fork一样都创建一个子进程,但它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec,于是也就不会存访该地址空间。
特点:vfork保证子进程先运行,在它调用exec或exit后父进程才可能被调用运行。
exec函数功能:进程替换——调用它并没有产生新的进程,一个进程一旦调用exec()函数,它本身就死亡了,系统吧代码替换成新的代码,在这其中唯一保存的就是进程的ID,对于系统而言,还是同一个进程,只不过执行另一个程序罢了。
写时拷贝:内核只为新生成的子进程创建虚拟空间结构,他们复制与父进程的虚拟空间结构,但是不为这些段分配物理内存,他们共享父进程的物理空间,当父进程中有更改相应的段行为时,在为子进程相应的段分配物理空间。
2、僵死进程
含义:父进程未结束,子进程结束,并且父进程未获取子进程的退出状态。这种进程,进程主体空间已释放,只有PCB还未释放。
处理方法:
a.父进程中调用wait或waitpid获取子进程的退出状态,这种方式可能导致父进程在wait或waitpid调用除阻塞运行,直到子进程退出。
b.父进程调用signal(SIGCHLD,SIG_IGN),来忽略SIGCHLD信号,这样子进程结束后会由内核释放资源。
c.对子进程的退出捕获它们的退出信号SIGCHLD,父进程退出信号时,在信号处理函数中调用wait或waitpid操作来释放它们的资源。
3、PCB(进程控制块)
进程是执行中的程序,是一个动态的过程,好比程序是乐谱,进程就是演奏的过程。操作系统为了管理进程,因而通过一个task_struct结构体记录进程的信息(进程标识符、优先级、进程状态、程序计数器、程序上下文、信号、打开的文件等),每一个进程都有一个task_struct结构体变量,称之为PCB(进程控制块),32 位系统上,每个PCB大概1.7K。操作系统通过一个双向循环链表管理所有PCB。
4、进程运行状态
进程的生命周期有多个状态,主要有就绪、阻塞、运行状态
就绪:所有资源准备完成,等待CPU空闲状态。
阻塞:等待某些事件发生,事件未发生前,不能被CPU调用的状态。
运行:在CPU上真正执行的状态。
就绪——>运行:系统进行进程调度
运行——>就绪:分配给进程的时间用完
运行——>阻塞:需要某些事件的发生才能运行
阻塞——>就绪:等待的事件已发生,只能转到就绪态,不能直接到运行态。
下面我们画图解释一下这个过程: