进程ID 0是调度进程,常常被称为交换进程(swapper)。该进程并不执行任何磁盘上的程序--它是内核的一部分,因此也被称为系统进程。进程ID 1是init进程,在自举(bootstrapping)过程结束时由内核调用。该进程的程序文件在UNIX的早期版本中是/etc/init,在较新版本中是/sbin/init。此进程负责在内核自举后启动一个UNIX系统。init通常读与系统有关的初始化(/etc/rc*文件),并将系统引导到一个状态(例如多用户)。init进程决不会终止。它是一个普通的用户进程(与交换进程不同,它不是内核中的系统进程),但是它以超级用户特权运行。
在某些UNIX的虚存实现中,进程ID 2是页精灵进程(pagedaemon)。此进程负责支持虚存系统的请页操作。与交换进程一样,页精灵进程也是内核进程。
除了进程ID,每个进程还有其他标识符。下列函数可以返回这些标识符:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
返回: 调用进程的进程ID
pid_t getppid(void);
返回: 调用进程的父进程ID
uid_t getuid(void);
返回: 调用进程的实际用户ID
uid_t geteuid(void);
返回: 调用进程的有效用户ID
gid_t getgid(void);
返回: 调用进程的实际组ID
gid_t getegid(void);
返回: 调用进程的有效阻ID
这些函数都没有出错返回
一个进程调用fork函数是UNIX内核创建一个新进程的唯一方法(除了交换进程、init进程和页精灵进程)
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回: 子进程中为0,父进程中为子进程的进程ID,出错为-1.
子进程和父进程继续执行fork之后的指令。子进程是父进程的复制品。例如,进程获得父进程数据空间、堆和栈的复制品。但是这些都是进程拥有的拷贝不是与父进程共享。如果正文段是只读的,则父、子进程共享正文段。
现在很多实现并不做一个父进程数据段和堆的完全拷贝,因为在fork之后经常跟随着exec。作为替代,使用了在**写时复制(Copy On Write, COW)**的技术。这些区域由父、子进程共享,而且内核将它们的存取权限改成只读的。如果有进程试图修改这些区域,则内核为有关部分(典型的是虚存系统中的"页"),做一个拷贝。
#include <sys/types.h> #include <stdio.h> #inlcude <unistd.h> int glob = 6; char buf[] = "a write to stdout\n"; int main(void) { int var; pid_t pid; var = 88; if (write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1) { fprintf(stderr, "write error"); } printf("before fork \n"); if ((pid = fork()) < 0) { fprintf(stderr, "fork error"); } else if (pid == 0) { glob++; var++; } else { sleep(2); } printf("pid=%d,glob=%d,var=%d\n", gitpid(), glob, var); return 0; }
编译运行上面的进程:
可以看到当我们将输出重定向到temp.out文件后多出个before fork的输出。write函数是不带缓存的。因为在fork之前调用write,所以其数据写到标准输出一次。但是标准IO是带缓存的。如果标准输出连到终端设备,则它是行缓存,否则它是全缓存。当以交互方式运行该程序时,只得到printf输出的行一次,其原因是标准输出缓存由新行符刷新。但是当将标注输出重新定向到一个文件时,却得到printf输出行两次。其原因是,在fork之前调用了printf一次,但当调用fork时,该行数据仍在缓存中,然后在父进程数据空间复制到子进程的过程中时,该缓存数据也被复制到了子进程中。于是那时父、子进程各自有了带该行内容的缓存。在exit之前的第二个printf将其数据添加到现存的缓冲中。当每个进程终止时,缓存中的内容将被写到相应文件中。
文件共享 对于上面的程序需要注意:在重定向父进程的标准输出时也重定向了子进程的标准输出。fork的一个特性是所有由父进程打开的文件描述符都被复制到子进程中。父、子进程每个相同的打开文件描述符共享一个文件表项。
这种共享文件的方式使父子进程对同一文件使用了一个文件位移量。对于以下情况:一个进程fork了一个子进程,然后等待子进程终止。假定,作为普通处理的一部分,父、子进程都向标准输出执行写操作。如果父进程使其标准输出重定向(很可能是由shell实现的),那么子进程写到该标准输出时,他将更新与父进程共享的该文件的位移量。在我们所考虑的例子中,当父进程等待子进程时,子进程写到标准输出;而在子进程终止后,父进程也写入到标准输出上,并且知道其输出会添加在子进程所写数据之后。如果父、子进程不共享同一文件位移量,这种形式的交互就很难实现。[为了理解这一点可参看APUE3.10节]
如果父、子进程写到同一文件描述符文件,但又没有任何形式的同步(例如使父进程等待子进程),那么它们的输出就会相互混合(假定所用的文件描述符是在fork之前打开的)。
在fork之后处理文件描述符有两种常见的情况:
(1) 父进程等待子进程完成。这种情况下,父进程无需对其描述符做任何处理。
(2) 父、子进程各自执行不同的程序段。在这种情况下,在fork之后,父、子进程各自它们不需使用的文件描述符,并且不干扰对方使用的文件描述符。
除了打开文件之外,很多父进程的其他性质也会由子进程继承:
父、子进程之间的区别是:
vfork函数的调用序列和返回值与fork相同,但两个函数的语义不同。
vfork用于创建一个新进程,而该进程的目的是exec一个新程序。vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),所以就不会用到此地址空间。
vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec/exit之后父进程才可能被调度运行。(如果在调用exec/exit之前子进程依赖于父进程的进一步动作,则会导致死锁。) 对于以下示例:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int glob = 6;
int main(void)
{
int var;
pid_t pid;
var = 88;
printf("before vfork\n");
if ((pid = vfork()) < 0) {
fprintf(stderr, "vfork error\n");
} else if (pid == 0) {
glob++;
var++;
_exit(0);
}
printf("pid=%d,glob=%d,var=%d\n", getpid(), glob, var);
return 0;
}
编译运行该程序:
需要注意在上面的程序我们调用了_exit而不是exit。_exit并不执行IO缓存的刷新操作。如果是调用exit而不是_exit,则该程序的输出是:
(上图是APUE原书, 下图是我在centos上实验结果
之所以结果不同是因为在linux中子进程关闭的是自己的, 虽然他们共享标准输入、标准输出、标准出错等 “打开的文件”, 子进程exit时,也不过是递减一个引用计数,不可能关闭父进程的,所以父进程还是有输出的。
)
可见父进程的printf输出消失了。其原因是子进程调用了exit,它刷新开关闭了所有标准IO流,这包括标准输出。虽然这是由子进程执行的,但却是在父进程的地址空间中进行的,所以所有受到影响的标准IO FILE对象都是在父进程中。当父进程调用prinf时,标准输出已经被关闭了,于是printf返回-1。
四、exit函数
对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于e x i t和_ e x i t,这是依靠传递给它们的退出状态( exit status)参数来实现的。在异常终止情况,内核(不是进程本身)产生一个指示其异常终止原因的终止状态( termination status) 。在任意一种情况下,该终止进程的父进程都能用 w a i t或w a i t p i d函数(在下一节说明)取得其终止状态。
一定是一个父进程生成一个子进程。上面又说明了子进程将其终止状态返回给父进程。但是如果父进程在子进程之前终止,则将如何呢 ?其回答是对于其父进程已经终止的所有进程,它们的父进程都改变为init进程。我们称这些进程由init进程领养。其操作过程大致是:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程 I D就更改为1 ( i n i t进程的I D )。这种处理方法保证了每个进程有一个父进程。
另一个我们关心的情况是如果子进程在父进程之前终止,那么父进程又如何能在做相应检查时得到子进程的终止状态呢?对此问题的回答是内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用 w a i t或waitpid 时,可以得到有关信息。这种信息至少包括进程I D、该进程的终止状态、以反该进程使用的 C P U时间总量。内核可以释放终止进程所使用的所有存储器,关闭其所有打开文件。在 U N I X术语中,一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死进程。
最后一个要考虑的问题是:一个由i n i t进程领养的进程终止时会发生什么 ?它会不会变成一个僵死进程?对此问题的回答是“否” ,因为i n i t被编写成只要有一个子进程终止, i n i t就会调用一个w a i t函数取得其终止状态。这样也就防止了在系统中有很多僵死进程。当提及“一个 i n i t的子进程”时,这指的是 i n i t直接产生的进程(例如,将在9 . 2节说明的g e t t y进程),或者是其父进程已终止,由init 领养的进程。
原文:http://www.cnblogs.com/orlion/p/6253989.html