UNIX环境高级编程(八)进程控制

来源:互联网 发布:禁止吸烟网络图片大全 编辑:程序博客网 时间:2024/05/16 14:23

8.2进程标识符

每个进程都有一个非负整数表示的唯一进程ID。虽然是唯一的但是进程ID可以重用,当一个进程终止后,其进程ID可以再一次使用了。

ID为0的进程通常是调度进程,常常被称作交换进程。该进程是内核的一部分,它并不执行任何磁盘上的程序,一次被称作系统进程。

进程ID为1的是init进程,在自举过程结束后由内核调用,此进程负责在自举内核启动UNIX系统,init进程通常读与系统有关的初始化文件,并将系统引导到一个状态,init进程决不会终止。他是一个普通的用户进程。(与交换进程不同,他不是内核中的系统进程)

下面一些函数返回一些进程标识符:

#include<unistd.h>pid_t getpid(void)//返回值:调用进程的进程IDpid_t getppid(void)//返回值:调用进程的父进程ID

8.3 fork函数

一个进程可以调用fork函数创建一个新进程

#include<unistd.h>pid_t fork(void);//返回值:子进程返回0,父进程返回子进程ID,出错返回-1

将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有很多个,并且没有一个函数使一个进程可以获得其所有子进程I的进程D。

fork使子进程返回0的理由是:一个进程只有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID。

子进程是父进程的副本。子进程获得父进程的数据空间,堆和栈的副本。父,子进程并不共享这些存储空间部分。父子进程共享正文段。

由于在fork后经常跟着exec,所以现在很多实现并不执行一个父进程数据段,堆栈的完全复制。作为替代使用了写时复制(copy-on-write)技术。这些区域由父子进程共享,而且内核将它们的访问权限改变为只读的。如果父,子进程任意一个试图改变这些区域,则内核只为修改区域的那块内存制作一个副本。


文件共享

父进程所有打开的文件描述符,都被复制到子进程中。父子进程的每个相同的打开的文件描述符共享一个文件表项


这种共享文件使父子进程对同一文件使用了一个文件偏移量。

fork之后处理文件描述符有两种常见情况:
1.父进程等待子进程完成
2.父,子进程执行不同的程序段。在这种情况下,fork后,父子进程关闭自己不用的文件描述符,这样不会干扰对方对文件描述符的使用。
除了打开文件以外,父进程很多其它属性也由子进程继承,包括
实际用户ID,有效用户ID,有效组ID
文件模式创建屏蔽字
信号屏蔽和安排
针对任意打开文件描述符的在执行时关闭(close-on-exec)标志
环境

父子进程的区别:
fork的返回值
进程ID不同
两个进程拥有不同的父进程ID
子进程的时间值均为0
父进程设置的文件锁不会被子进程继承
子进程未处理得闹钟被清除
子进程未处理得信号集被清除

使fork失败的两个主要原因是:系统中已经有了太多进程,或者该实际用户ID的进程总数超过了系统限制。

fork有下面两种用法:
1.一个父进程希望复制自己,使父子进程同时执行不同的代码段。
2.一个进程要执行不同的程序

8.5 exit函数
进程有以下5正常终止的方式:
1.在main函数内执行return语句
2.调用exit函数。其操作包括:调用终止处理程序,关闭所有标准IO流
3.调用_exit或_Exit函数。是一种无需运行终止处理程序的函数,并不清洗标准IO流
4.进程中最后一个线程从启动例程中返回
5.进程中最后一个线程调用pthread_exit函数

三种异常终止方式:
1.调用abort。它产生SIGABRT信号
2.进程接受到某些信号。信号可以由自身产生,或者其它进程或内核产生。
3.最后一个线程对“取消”请求作出响应

不管进程如何终止,最后都会执行内核中同一段代码,这段代码关闭进程打开的文件描述符,释放它所使用的存储器等

我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit,_exit和)_Exit),实现这一点的方法是将其退出状态作为参数传递给函数。在异常终止的情况下,内核产生一个指示其异常终止原因的终止状态。
在最后调用_exit函数时,内核将退出状态转换成终止状态

如果父进程在子进程前终止,那么将子进程的父进程改变为init进程。我们称这些进程由init进程领养。在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止进程的子进程,如果是,将它的父进程ID改为1.

内核为每个终止子进程保存一定的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包括进程ID,进程终止状态,以及该进程使用CPU时间总量。内核可以释放终止进程的所有存储区,关闭其打开文件。
一个已经终止,但是其父进程没有对其进行善后处理的进程被称作僵死进程。

8.6 wait或waitpid函数
当一个进程正常或异常终止时,内核就向其父进程发送一个SIGCHLD信号,对于这种信号的默认动作是忽略它。
调用wait或waitpid的进程会发生什么情况:
1.如果其所有子进程都在运行,则阻塞
2.如果一个子进程已经终止,正等待父进程获得其终止状态,取得孩子进程的终止状态,立即返回
3.该进程没有子进程,出错

#include<sys/wait.h>pid_t wait(int *staloc)pid_t waitpid(pid_t pid,int *staloc,int options)//若成功返回进程ID,0,出错返回-1

waitpid函数中pid参数:
pid==-1:等待任意子进程
pid>0等待进程ID为pid的进程
pid==0等待组ID等于调用进程组ID的任意子进程
pid<-1等待其组ID等于pid绝对值的任意子进程

对于wait函数,唯一出错情况是该进程没有子进程。
对于waitpid函数,出错是指定的进程或进程组不存在,也有可能参数pid指定的进程不是调用进程的子进程

8.10 exec函数

使用fork函数创建子进程后,子进程往往都要调用一种exec函数以执行另一个程序,当进程调用一种exec函数时,该进程执行

的程序完全替换为新程序,新程序从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec

只是用一个全新的程序替换了当前进程的正文、数据、堆和栈。

有6种不同的exec函数可以使用,它们通常被统称为exec函数。


#inlcude <unistd.h>int execl(const char *pathname, const char *arg0, ... ,(char*)0 );int execv(const char *pathname, char *const argv[]);int execle(const char *pathname, const char *arg0, ..., (char *)0, char *const envp[]);int execve(const char *pathname, char *const argv[], char *const envp[]);int execlp(const char *filename, const char *arg0, ..., (char*)0);int execvp(const char *filename, char *const argv);//6个函数返回值:若出错则返回-1,成功则不返回值。

实际上,这六个函数只有一个execve属于系统调用,其它都属于库函数的定义,它们之间的关系可见下图:


对于这六个函数的命名方式,其规律其实很容易看出来,exec是共有部分,而l带表可变参,与之相对的则是v,代表数组。如果有p,则说明使用了PATH环境变量,其参数则是filename,最后的e则代表替换的进程(实际上并没有创建进程)将使用envp所指定的环境表。
需要着重解释的是关于filename与pathname的区别,如果是pathname,则它所代表的是可执行文件的路径,而filename则分两种情况,如果包含'/'前缀,则其意义与pathname相同,否则,则只代表文件名,寻找该可执行文件时需要使用PATH环境变量中的值作为查找前缀。
每一个打开的文件描述符都有一个close-on-exec标识,它默认是没有被设置的,如果该位被设置,则在使用exec系列函数后,该文件描述符将被关闭。

许多情况下,我们创建进程的目的只是为了调用exec系列的函数去加载新的程序,而在这种情况下使用fork就太浪费了,因为我们并不需要使用到父进程的地址空间及相关资源,替代的,我们可以使用vfork函数。该函数和fork的功能基本相同,区别在于,子进程并不会复制父进程的地址空间,在子进程调用_exit系列函数或是exec系列函数之前,子进程将在父进程的地址空间中运行,而在此期间父进程将被阻塞(请注意,一旦子进程调用了_exit系列函数或是exec系列函数之后,父进程将可以继续执行)。
关于vfork函数,还有一点需要特别注意的是,永远不要再子进程里进行main函数的返回或是调用exit函数,因为子进程和父进程共用地址空间,而exit函数会导致流的关闭以及调用“出口函数”,这将影响到父进程的状态。至于main函数的返回,其效果和调用exit函数是一样的。
尽管vfork函数并不复制地址空间,但进程表之类的东西它仍然是会复制的,也就是说,虽然调用_exit函数会关闭文件描述符,但这并不影响到父进程。如果父进程先于子进程结束,那么子进程将被init进程收养




0 0
原创粉丝点击