进程应用
一、进程创建
1.获取进程id
1 | pit_t getpid() //当前进程id |
2.创建和终止进程
1 | pit_t fork() //创建子进程 |
新创建的子进程与父进程几乎但不完全相同。子进程拥有父进程用户级虚拟地址空间的副本和全部打开的文件描述符。
fork函数调用一次,却返回两次。一次在父进程中返回子进程的PID,在子进程中返回0。
写时复制
fork创建出的子进程,与父进程共享内存空间。也就是说,如果子进程不对内存空间进行写入操作的话,内存空间中的数据并不会复制给子进程,这样创建子进程的速度就很快了!。在fork之后exec之前两个进程 用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的 物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。这便是写时复制
原理:fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会 把触发的异常的页复制一份,于是父子进程各自持有独立的一份。
优点:
1.可减少分配和复制大量资源时带来的瞬间延时。
2.可减少不必要的资源分配。比如fork进程时,并不是所有的页面都需要复制,父进程的代码段和只读数据段都不被允许修改,所以无需复制。
4.回收子进程
当一个进程由于某种原因终止时,内核并不立刻把它清除。而是让它保持在已终止的状态,知道父进程回收它。
父进程回收时,内核将子进程的退出状态传给父进程,然后抛弃已终止的进程。
一个终止了但未回收的进程称为僵死进程
如果父进程终止了,子进程未回收,这样的子进程叫孤儿进程。内核会让init进程更为它的养父,并回收它们。
1 | pid_t waitpid(pit_t pid,int* statusp,int options); |
5.加载并运行程序
1 | int execve(const char* filename,const char* argv[],const char* envp[]) |
execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量envp。
这个函数在当前进程的上下文中加载并运行一个新的程序,它会覆盖当前进程的地址空间,但并没有创建一个新进程。它同时继承了调用时已经打开的所有文件描述符。