LINUX环境编程之进程控制(上)

一、进程标识

每个进程都有一个非负整型表示的唯一进程ID。虽然该id是唯一的,但是进程ID是可复用的。当一个进程终止后,其进程id就成为复用的候选者。

系统中有一些专用进程,但是具体细节随实现而不同。ID为0的进程通常是调度进程,常常被称为交换进程。该进程是内核的一部分,它并不执行任何磁盘上的程序,因此也被成为系统进程。进程ID 1通常是init进程,在自举过程结束时有内核调用。init进程绝不会终止,他是一个普通用户的用户进程(与交换进程不同,他不是内核中的系统进程),但是它以超级用户权限运行。

每个unix系统实现都有它自己的一套提供操作系统服务的内核进程,比如,在某些unix的虚拟存储器中,进程id 2是页守护进程(page daemnon),此进程负责支持虚拟存储系统的分页操作。

附:进程相关函数

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

二、fork函数

#include<unistd.h>
pid_t fork(void);

解释:有fork函数创建的新进程成为子进程,fork函数被调用一次,但返回两次,两次返回的区别是子进程返回值是0,父进程的返回值则是新建子进程的进程id。子进程和父进程继续执行fork调用之后的指令,子进程是父进程的副本。例如,子进程获取父进程数据空间、堆和栈的副本。注意,这是子进程所拥有的副本,父进程和子进程并不共享这些存储空间部分。该部分执行写时复制。

 1 #include<sys/types.h>
 2 #include "ourhdr.h"
 3 int glob=6;
 4 char buf[]="a write to stdout\n";
 5 int main(void)
 6 {
 7 
 8         int var;
 9         pid_t pid;
10         var=88;
11         if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!=sizeof(buf)-1)
12                 printf("write error");
13         printf("before fork\n");
14         if((pid=fork())<0)
15                 printf("fork error\n");
16         else if(pid==0)
17                    {
18                    glob++;
19                    var++;
20                 }
21         else
22                         sleep(2);
23         printf("pid=%d,glob=%d,var=%d\n",getpid(),glob,var);
24         exit(0);
25

技术分享

可以看到子进程对变量所做的改变并不影响父进程中该变量的值。

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

文件共享

 在fork之后处理文件描述符有以下两种常见的情况:

(1)父进程等待子进程 完成。(2)父进程和子进程各自执行不同的程序段。

除了打开文件之外,父进程的很多其他属性也有子进程继承,包括:

实际用户I D、实际组I D、有效用户I D、有效组I D。
• 添加组I D。
• 进程组I D。
• 对话期I D。
• 控制终端。
• 设置-用户- I D标志和设置-组- I D标志。
• 当前工作目录。
• 根目录。
• 文件方式创建屏蔽字。
• 信号屏蔽和排列。
• 对任一打开文件描述符的在执行时关闭标志。
• 环境。
• 连接的共享存储段。
• 资源限制。
父、子进程之间的区别是:
• fork的返回值。
• 进程I D。
• 不同的父进程I D。
• 子进程的t m s _ u t i m e , t m s _ s t i m e , t m s _ c u t i m e以及t m s _ u s t i m e设置为0。
• 父进程设置的锁,子进程不继承。
• 子进程的未决告警被清除。
• 子进程的未决信号集设置为空集。
fork失败的两个原因:
(a)系统中已经有了太多的进程(通常意味着某个方面出了问题)

(b)该实际用户id的进程总数超过了系统限制。

fork的用法:

(1)一个父进程希望赋值自己,是父进程和子进程同时执行不同的代码段。

(2)一个进程要执行一个不同的程序。

函数vfork

(1)vfork函数用于创建一个新的进程,而该进程的目的是exec一个新程序。vfork与fork的区别:(1)v f o r k与f o r k一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用 e x e c (或e x i t ),于是也就不会存访该地址空间。不过在子进程调用 e x e c或e x i t之前,它在父进程的空间中运行。这种工作方式在某些 U N I X的页式虚存实现中提高了效率(与上节中提及的,在 f o r k之后跟随e x e c,并采用在写时复制技术相类似)。

(2)v f o r k保证子进程先运行,在它调用 e x e c或e x i t之后父进程才可能被调度运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。)
函数exit

进程有5种正常终止和3种异常终止方式

5种正常终止方式为:

在main函数内执行return语句,调用exit函数,调用_exit函数或者_Exit函数,进程的最后一个线程在其启动例程中执行return语句,进程的最后一个线程调用pthread_exit函数。

3种异常终止方式如下:

调用abort,当进程接收到某些信号时,最后一个线程对“取消”请求做出响应。

在任意一种终止情况下,该终止进程的父进程都可以用wait或waitpid函数取得其终止状态。

对于父进程已经终止的所有进程,它们的父进程都改变为init进程,我们称这些进程由init进程收养。

僵死进程:一个已经终止、但是其父进程尚未对其进行善后处理的进程成为僵死进程。

函数wait和waitpid

#include<sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);

调用wait和waitpid的进程会发生什么

• 阻塞(如果其所有子进程都还在运行)。
• 带子进程的终止状态立即返回(如果一个子进程已终止,正等待父进程存取其终止状态 )。
• 出错立即返回(如果它没有任何子进程)。
这两个函数的区别是:
• 在一个子进程终止前, wait 使其调用者阻塞,而 waitpid 有一选择项,可使调用者不阻
塞。
• waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的进程。

                                                                 检查w a i t和w a i t p i d所返回的终止状态的宏
技术分享
对于waitpid函数中pid参数的作用的解释如下:

pid == -1 等待任一子进程。于是在这一功能方面 w a i t p i d与w a i t等效。
pid > 0 等待其进程I D与p i d相等的子进程。
pid == 0 等待其组I D等于调用进程的组I D的任一子进程。
pid < -1 等待其组I D等于p i d的绝对值的任一子进程。
对于waitpid函数中options参数的作用的解释如下:

options或者是0或者是表中常量按位或者运算的结果。

技术分享

waitpid函数提供了wait函数没有提供的三个功能:
(1) waitpid等待一个特定的进程 (而wait则返回任一终止子进程的状态 )。
(2) waitpid提供了一个wait的非阻塞版本。有时希望取得一个子进程的状态,但不想阻塞。
(3) waitpid支持作业控制(以WUNTRACED选择项)。

 1 #include<sys/wait.h>
 2 #include<unistd.h>
 3 #include<stdio.h>
 4 #include<stdlib.h>
 5 int main(void)
 6 {
 7         pid_t pid;
 8         if((pid=fork())<0)
 9         {
10                 perror("fork");
11         }else if(pid==0)
12         {
13                 if((pid=fork())<0)
14                 perror("fork");
15                 else if(pid>0)
16                 exit(0);
17                 sleep(2);
18                 printf("second child,parent pid is:%d\n",(long)getppid());
19                 exit(0);
20         }
21         if(waitpid(pid,NULL,0)!=pid)
22         perror("waitpid");
23         exit(0);
24 }
25 ~    

技术分享

以上程序实现的功能是:一个进程fork一个子进程,但不要它等待子进程终止,也不希望子进程处于僵死状态知道父进程终止,通过调用fork两次来实现。

在第二个子进程中调用 sleep以保证在打印父进程 ID时第一个子进程已终止。在 fork之后,父、子进程都可继续执行——我们无法预知哪一个会先执行。如果不使第二个子进程睡眠,则在fork之后,它可能比其父进程先执行,于是它打印的父进程 ID将是创建它的父进程,而不是init进程。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。