「APUE」进程控制(上)

一、进程标识

进程ID0是调度进程,常常被称为交换进程(swapper)。该进程并不执行任何磁碟上的程序--它是内核的一部分,因此也被称为系统进程。进程ID

1是init进程,在自举(bootstrapping)过程结束时由内核调用。该进程的程序文件在UNIX的早期版本中是/etc/init,在较新版本中是/sbin/init。此进程负责在内核自举后启动一个UNIX系统。init通常读与系统有关的初始化(/etc/rc*文件),并将系统引导到一个状态(例如多用户)。init进程决不会终止。它是一个普通的用户进程(与交换进程不同,它不是内核中的系统进程),但是它以超级用户特权运行。

在某些UNIX的虚存实现中,进程ID2是页精灵进程(pagedaemon)。此进程负责支持虚存系统的请页操作。与交换进程一样,页精灵进程也是内核进程。

除了进程ID,每个进程还有其他标识符。下列函数可以返回这些标识符:

#include

#include

pid_tgetpid(void);

返回:调用进程的进程ID

pid_tgetppid(void);

返回:调用进程的父进程ID

uid_tgetuid(void);

返回:调用进程的实际用户ID

uid_tgeteuid(void);

返回:调用进程的有效用户ID

gid_tgetgid(void);

返回:调用进程的实际组ID

gid_tgetegid(void);

返回:调用进程的有效阻ID

这些函数都没有出错返回

二、fork函数

一个进程调用fork函数是UNIX内核创建一个新进程的唯一方法(除了交换进程、init进程和页精灵进程)

#include

#include

pid_tfork(void);

返回:子进程中为0,父进程中为子进程的进程ID,出错为-1.

子进程和父进程继续执行fork之后的指令。子进程是父进程的复制品。例如,进程获得父进程数据空间、堆和栈的复制品。但是这些都是进程拥有的拷贝不是与父进程共享。如果正文段是只读的,则父、子进程共享正文段。现在很多实现并不做一个父进程数据段和堆的完全拷贝,因为在fork之后经常跟随着exec。作为替代,使用了在**写时复制(CopyOnWrite,

COW)**的技术。这些区域由父、子进程共享,而且内核将它们的存取权限改成只读的。如果有进程试图修改这些区域,则内核为有关部分(典型的是虚存系统中的"页"),做一个拷贝。

#include

#include

#inlcude

intglob=6;

charbuf="awritetostdout

";

intmain(void)

{

intvar;

pid_tpid;

var=88;

if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!=sizeof(buf)-1){

fprintf(stderr,"writeerror");

}

printf("beforefork

");

if((pid=fork)

#include

#includeintglob=6;intmain(void){intvar;pid_tpid;var=88;printf("beforevfork

");if((pid=vfork)<0){fprintf(stderr,"vforkerror

");}elseif(pid==0){glob++;var++;_exit(0);}printf("pid=%d,glob=%d,var=%d

",getpid,glob,var);return0;}

编译运行该程序:

需要注意在上面的程序我们调用了_exit而不是exit。_exit并不执行IO缓存的刷新操作。如果是调用exit而不是_exit,则该程序的输出是:

(上图是APUE原书,下图是我在centos上实验结果

之所以结果不同是因为在linux中子进程关闭的是自己的,虽然他们共享标准输入、标准输出、标准出错等「打开的文件」,子进程exit时,也不过是递减一个引用计数,不可能关闭父进程的,所以父进程还是有输出的。

)

可见父进程的printf输出消失了。其原因是子进程调用了exit,它刷新开关闭了所有标准IO流,这包括标准输出。虽然这是由子进程执行的,但却是在父进程的地址空间中进行的,所以所有受到影响的标准IOFILE对象都是在父进程中。当父进程调用prinf时,标准输出已经被关闭了,于是printf返回-1。

四、exit函数

对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于exit和_exit,这是依靠传递给它们的退出状态(exitstatus)参数来实现的。在异常终止情况,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(terminationstatus)。在任意一种情况下,该终止进程的父进程都能用wait或waitpi

d函数(在下一节说明)取得其终止状态。

一定是一个父进程生成一个子进程。上面又说明了子进程将其终止状态返回给父进程。但是如果父进程在子进程之前终止,则将如何呢?其回答是对于其父进程已经终止的所有进程,它们的父进程都改变为init进程。我们称这些进程由init进程领养。其操作过程大致是:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程ID就更改为1(ini

t进程的ID)。这种处理方法保证了每个进程有一个父进程。

另一个我们关心的情况是如果子进程在父进程之前终止,那么父进程又如何能在做相应检查时得到子进程的终止状态呢?对此问题的回答是内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到有关信息。这种信息至少包括进程ID、该进程的终止状态、以反该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储器,关闭其所有打开文件。在

UNIX术语中,一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死进程。

最后一个要考虑的问题是:一个由init进程领养的进程终止时会发生什么?它会不会变成一个僵死进程?对此问题的回答是「否」,因为init被编写成只要有一个子进程终止,init就会调用一个wait函数取得其终止状态。这样也就防止了在系统中有很多僵死进程。当提及「一个init的子进程」时,这指的是init直接产生的进程(例如,将在9.2节说明的g

etty进程),或者是其父进程已终止,由init领养的进程。