进 程 控 制

来源:互联网 发布:mac蓝光播放软件 编辑:程序博客网 时间:2024/05/16 17:49
进程标识
每个进程都有一个非负整型的唯一进程I D。因为进程I D标识符总是唯一的,常将其用做其

他标识符的一部分以保证其唯一性。

有某些专用的进程:进程ID 0是调度进程,常常被称为交换进程( s w a p p e r )。该进程并不执
行任何磁盘上的程序 — 它是内核的一部分,因此也被称为系统进程。进程 ID 1通常是i n i t进
程,在自举过程结束时由内核调用。该进程的程序文件在 U N I X的早期版本中是/ e t c / i n i t,在较
新版本中是/ s b i n / i n i t。此进程负责在内核自举后起动一个 U N I X系统。i n i t通常读与系统有关的
初始化文件( / e t c / r c * 文件),并将系统引导到一个状态(例如多用户)。i n i t进程决不会终止。它是
一个普通的用户进程(与交换进程不同,它不是内核中的系统进程 ),但是它以超级用户特权运
行。

在某些U N I X的虚存实现中,进程ID 2是页精灵进程( p a g e d a e m o n )。此进程负责支持虚存系
统的请页操作。与交换进程一样,页精灵进程也是内核进程。


#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回:调用进程的进程I D
pid_t getppid(void); 返回:调用进程的父进程I D
uid_t getuid(void); 返回:调用进程的实际用户I D
uid_t geteuid(void); 返回:调用进程的有效用户I D
gid_t getgid(void); 返回:调用进程的实际组I D
gid_t getegid(void); 返回:调用进程的有效组I D


fork函数
一个现存进程调用f o r k函数是U N I X内核创建一个新进程的唯一方法 (这并不适用于前节提
及的交换进程、i n i t进程和页精灵进程。这些进程是由内核作为自举过程的一部分以特殊方式
创建的)。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回:子进程中为0,父进程中为子进程I D,出错为- 1

由f o r k创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返
回的区别是子进程的返回值是 0,而父进程的返回值则是新子进程的进程 I D。将子进程I D返回
给父进程的理由是:因为一个进程的子进程可以多于一个,所以没有一个函数使一个进程可以
获得其所有子进程的进程 I D。f o r k使子进程得到返回值 0的理由是:一个进程只会有一个父进
程,所以子进程总是可以调用g e t p p i d以获得其父进程的进程I D (进程ID 0总是由交换进程使用,
所以一个子进程的进程I D不可能为0 )。

一般来说,在f o r k之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的
调度算法。如果要求父、子进程之间相互同步,则要求某种形式的进程间通信。

使f o r k失败的两个主要原因是:( a )系统中已经有了太多的进程(通常意味着某个方面出了问
题),或者( b )该实际用户I D的进程总数超过了系统限制。


f o r k有两种用法:
(1) 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程
中是常见的——父进程等待委托者的服务请求。当这种请求到达时,父进程调用 f o r k,使子进
程处理此请求。父进程则继续等待下一个服务请求。
(2) 一个进程要执行一个不同的程序。这对 s h e l l是常见的情况。在这种情况下,子进程在
从f o r k返回后立即调用e x e c 


exit函数

(1) 正常终止:
(a) 在m a i n函数内执行r e t u r n语句。
(b) 调用e x i t函数。此函数由ANSI C定义,其操作包括调用各终止处理程序(终止处理程序
在调用a t e x i t函数时登录),然后关闭所有标准I / O流等。因为ANSI C并不处理文件描述符、
多进程(父、子进程)以及作业控制,所以这一定义对U N I X系统而言是不完整的。
(c) 调用_ e x i t系统调用函数。此函数由 e x i t调用,它处理U N I X特定的细节。_ e x i t是由
P O S I X . 1说明的。
(2) 异常终止:
(a) 调用a b o r t。它产生S I G A B RT信号,所以是下一种异常终止的一种特例。
(b) 当进程接收到某个信号时。(第1 0章将较详细地说明信号。)进程本身(例如调用
a b o r t函数)、其他进程和内核都能产生传送到某一进程的信号。例如,进程越出其
地址空间访问存储单元,或者除以0,内核就会为该进程产生相应的信号。

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


对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对
于e x i t和_ e x i t,这是依靠传递给它们的退出状态( exit status)参数来实现的。在异常终止情
况,内核(不是进程本身)产生一个指示其异常终止原因的终止状态( termination status)。
在任意一种情况下,该终止进程的父进程都能用 w a i t或w a i t p i d函数(在下一节说明)取得其终止
状态。



exec函数

当进程调用一种e x e c函数时,该进程完全由新程序代换,而新程序则从其 m a i n函数开始执行。
因为调用e x e c并不创建新进程,所以前后的进程I D并未改变。e x e c只是用另一个新程序替换了
当前进程的正文、数据、堆和栈段。

#include <unistd.h>
int execl(const char * p a t h n a m e, const char * a rg 0, ... /* (char *) 0 */);
int execv(const char * p a t h n a m e, char *const  a rgv [] );
int execle(const char * p a t h n a m e, const char * a rg 0, ...
/* (char *)0, char *const  e n v p [] */);
int execve(const char * p a t h n a m e, char *const  a rgv [], char *const  envp [] );
int execlp(const char * f i l e n a m e, const char * a rg 0, ... /* (char *) 0 */);
int execvp(const char * f i l e n a m e, char *const  a rgv [] );
六个函数返回:若出错则为- 1,若成功则不返回


这些函数之间的第一个区别是前四个取路径名作为参数,后两个则取文件名作为参数。当
指定f i l e n a m e作为参数时:

• 如果f i l e n a m e中包含/,则就将其视为路径名。
• 否则就按PAT H环境变量,在有关目录中搜寻可执行文件。
PAT H变量包含了一张目录表 (称为路径前缀),目录之间用冒号( : )分隔。例如下列n a m e = v a l u e
环境字符串:
P A T H = / b i n : / u s r / b i n : / u s r / l o c a l / b i n :.
指定在四个目录中进行搜索。(零长前缀也表示当前目录。在 v a l u e的开始处可用:表示,在行

中间则要用::表示,在行尾以:表示。)


第二个区别与参数表的传递有关 ( l表示表( l i s t ),v表示矢量( v e c t o r ) )。函数e x e c l、e x e c l p和
e x e c l e要求将新程序的每个命令行参数都说明为一个单独的参数。这种参数表以空指针结尾。
对于另外三个函数( e x e c v, e x e c v p和e x e c v e ),则应先构造一个指向各参数的指针数组,然后将该
数组地址作为这三个函数的参数。


最后一个区别与向新程序传递环境表相关。以 e结尾的两个函数( e x e c l e和e x e c v e)
可以传递一个指向环境字符串指针数组的指针。其他四个函数则使用调用进程中的
e n v i r o n变量为新程序复制现存的环境。



wait和w a i t p i d函数                        linux wait与waitpid函数的深入分析

调用w a i t或w a i t p i d的进程可能会:
• 阻塞(如果其所有子进程都还在运行)。
• 带子进程的终止状态立即返回(如果一个子进程已终止,正等待父进程存取其终止状态)。
• 出错立即返回(如果它没有任何子进程)。

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int * s t a t l o c) ;
pid_t waitpid(pid_t  p i d, int *s t a t l o c, int o p t i o n s) ;
两个函数返回:若成功则为进程I D,若出错则为- 1



更改用户I D和组I D           linux 设置用户id 设置组id


可以用s e t u i d函数设置实际用户I D和有效用户I D。与此类似,可以用s e t g i d函数设置实际组
I D和有效组I D。
#include <sys/types.h>
#include <unistd.h>
int setuid(uid_t  u i d) ;
int setgid(gid_t  g i d) ;
两个函数返回:若成功则为0,若出错则为- 1


关于谁能更改I D有若干规则。现在先考虑有关改变用户 I D的规则(在这里关于用户 I D所说明
的一切都适用于组I D)。
(1) 若进程具有超级用户特权,则 s e t u i d函数将实际用户I D、有效用户I D,以及保存的设
置-用户- I D设置为u i d。
(2) 若进程没有超级用户特权,但是u i d等于实际用户I D或保存的设置-用户- I D,则s e t u i d只
将有效用户I D设置为u i d。不改变实际用户I D和保存的设置-用户- I D。
(3) 如果上面两个条件都不满足,则e r r n o设置为E P E R M,并返回出错。
在这里假定_ P O S I X _ S AV E D _ I D S为真。如果没有提供这种功能,则上面所说的关于保存
的设置-用户- I D部分都无效。


关于内核所维护的三个用户I D,还要注意下列几点:
(1) 只有超级用户进程可以更改实际用户 I D。通常,实际用户 I D是在用户登录时,由
l o g i n ( 1 )程序设置的,而且决不会改变它。因为 l o g i n是一个超级用户进程,当它调用 s e t u i d时,
设置所有三个用户I D。
(2) 仅当对程序文件设置了设置-用户- I D位时,e x e c函数设置有效用户 I D。如果设置-用
户- I D位没有设置,则e x e c函数不会改变有效用户 I D,而将其维持为原先值。任何时候都可以
调用s e t u i d,将有效用户I D设置为实际用户I D或保存的设置-用户- I D。自然,不能将有效用户
I D设置为任一随机值。
(3) 保存的设置-用户- I D是由e x e c从有效用户I D复制的。在e x e c按文件用户I D设置了有效用
户I D后,即进行这种复制,并将此副本保存起来。