【UNIX环境高级编程】第八章.进程控制(一)

1.进程标识符
每一个进程都有一个非负整形表示的唯一进程ID。系统中的专用进程,ID为0进程一般为调度进程,也叫交换进程,或系统进程(为内核的一部分)。进程ID1通常为init进程,在自举过程结束时由内核调用。init通常读与系统有关的初始化文件,并将系统引导到一个状态(例如多用户)。进程ID 2是页守护进程。此进程负责支持虚拟存储系统的分页操作。
#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
2.fork函数
#include<unistd.h>
pid_t fork(void);//返回值:子进程中返回0,父进程中返回子进程ID,出错返回-1
由fork创建的新进程被称为子进程。fork函数被调用一次,但返回两次。子进程返回0,父进程返回值是新子进程的进程ID。
(2)父、子进程的区别:
fork的返回值;进程ID不同;两个进程具有不同的父进程ID:子进程的父进程ID是创建它的进程的ID,父进程的父进程ID则不变;父进程设置的文件锁不会被子进程继承;子进程的未处理的闹钟被清除;子进程的未处理信号集设置为空集
(3)fork的两种用法 :
a.父子进程同时执行不同的代码段,父进程等待客户端的服务请求。子进程处理此请求。父进程等待下一个服务请求到达。
b.一个进程要执行一个不同的程序,shell中常见,子进程从fork返回后立即调用exec
(4)fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,这个新产生的进程称为子进程。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。需要注意的一点:就是调用fork函数之后,一定是 两个进程同时执行的代码段是fork函数之后的代码 ,而之前的代码以及由父进程执行完毕。下面来看一个很简单的例子。 
fork函数返回两个值
  • 返回一个大于0的值给父进程
  • 返回0给子进程
  • 返回其他值说明fork失败了
3.exec函数
5种为正常终止:
1)从main返回;
2)调用exit;
3)调用_ exit _ Exit;
4)最后一个线程从其启动例程返回;
5)最后一个线程调用pthread_exit
异常终止3种方式:
1)调用abord;
2)接到一个信号并终止;
3)最后一个线程对取消请求做出响应
用fork函数创建子进程后,子进程往往调用一种exec函数以执行另一个程序,当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,从其main函数开始执行。
僵死进程
一个已经终止,但其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它任占用的资源)的进程被称为僵死进程。
4.vfork函数
vfork用于创建一个新进程,与fork区别:
它不将父进程的地址空间完全复制到子进程,因为子进程会立即调用exec(或exit)。于是也就不会存访改地址空间。
例:
#include "apue.h"
int glob = 6;
int main(void)
{
  int var;
  pid_t pid;
  var = 90;
  printf("before vfork\n");
  if((pid = vfork())<0)
    err_sys("vfork false");
  else if(pid == 0)
  {
      var++;
      glob++;
      printf("child pid: %d(0x%x)\n",getpid(),getpid());
      _exit(0);
  }
  //----------parent continue here
  printf("father pid:%d glob = %d var = %d\n",getpid(),glob,var);
}
运行结果

5.wait与waitid函数

调用wait或waitpid的进程可能发生的情况:
  • 如果其所有子进程都还在运行,则阻塞
  • 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
  • 如果它没有任何子进程,则立即出错返回
#include <sys/types.h> /* 提供类型pid_t的定义 */
#include <sys/wait.h>
pid_t wait(int *status)
进程一旦调用了wait,就立即 阻塞 自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵死的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个 僵死进程 消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:
pid = wait(NULL);
如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
wait调用例程:
/* wait1.c */
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
main()
{
        pid_t pc,pr;
        pc=fork();
 
        if(pc<0) /* 如果出错 */
              printf("error ocurred!\n");
       else if(pc==0){ /* 如果是子进程 */
              printf("This is child process with pid of %d\n",getpid());
              sleep(1); /* 睡眠1秒钟 */
            }
        else{ /* 如果是父进程 */
               pr=wait(NULL); /* 在这里等待 */
              printf("I catched a child process with pid of %d\n",pr);
        }
       exit(0);
}
运行结果

可以明显注意到,在第2行结果打印出来前有1秒钟的等待时间,这就是我们设定的让子进程睡眠的时间,只有子进程从睡眠中苏醒过来,它才能正常退出,也就才能被父进程捕捉到。父进程的等待时间随子进程睡眠时间变化而变化。
参数status:
如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的(一个进程也可以被其他进程用信号结束),以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:
1、WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数---指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了)
2、WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。
下面通过例子来实战一下我们刚刚学到的内容:
/* wait2.c */
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
main()
{
       int status;
       pid_t pc,pr;     
       pc=fork();
       if(pc<0) /* 如果出错 */
         printf("error ocurred!\n");
       else if(pc==0){ /* 子进程 */
        printf("This is child process with pid of %d\n",getpid());              
	exit(3); /* 子进程返回3 */
        }
       else{ /* 父进程 */
            pr=wait(&status);
           if(WIFEXITED(status)){ /* 如果WIFEXITED返回非零值 */
             printf("the child process %d exit normally.\n",pr);
             printf("the return code is %d.\n",WEXITSTATUS(status));
            }else /* 如果WIFEXITED返回零 */
             printf("the child process %d exit abnormally.\n",pr);
        }
}
运行结果

父进程准确捕捉到了子进程的返回值3,并把它打印了出来。
waitpid函数
#include <sys/types.h> /* 提供类型pid_t的定义 */
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *statusp,int options)
从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下这两个参数:
1.pid: 从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。
         pid > 0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
         pid == -1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
         pid == 0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
         pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
2.statusp :如果statusp参数是非空的,那么waitpid就会在status中放上关于导致返回子进程的状态信息,status是statusp指向的指。status参数的几个宏:
WIFEXITED(status):如果子进程通过调用exit或者返回正常终止,就返回真。
WEXITSTATUS(status):返回一个正常终止的子进程的退出状态,只有在WIFEXITED()返回为真时,才会定义这个状态。
3.options :options提供了一些额外的选项来控制waitpid,目前在Linux中支持WNOHANG、WUNTRACED和WCONTINUED选项,可以用"|"运算符把它们连接起来使用,比如:
ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
如果我们不想使用它们,也可以把options设为0,如:
ret=waitpid(-1,NULL,0);
如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。
而WUNTRACED参数,涉及到一些跟踪调试方面的知识,极少用到。
WCONTINUED参数,由pid指定的任一子进程再暂停后已经继续,但其状态尚未报告,则返回其状态。
wait就是经过包装的waitpid,察看<内核源码目录>:
static inline pid_t wait(int * wait_stat)
{
    return waitpid(-1,wait_stat,0);
}
waitpid函数和wait函数的区别:
返回值和错误
waitpid的返回值比wait稍微复杂一些,一共有3种情况:
          1、当正常返回的时候,waitpid返回收集到的子进程的进程ID;
          2、如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
          3、如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;
不同功能
witpid函数提供了wait函数没有提供的三个功能
  1. waitpid可等待一个特定的进程,而wait则返回任一终止子进程的状态。
  2. waitpid提供一个wait的非阻塞版本。有时用户希望取得一个子进程的状态,但不想阻塞。
  3. waitpid支持作业控制(利用WUNTRACED和WCONTINUED选项)
/* waitpid例程1 */
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
main()
{
        pid_t pc, pr; 
        pc=fork();
        if(pc<0) /* 如果fork出错 */
               printf("Error occured on forking.\n");
        else if(pc==0){ /* 如果是子进程 */
               sleep(10); /* 睡眠10秒 */
              exit(0);
        }
        /* 如果是父进程 */
       do{
               pr=waitpid(pc, NULL, WNOHANG); /* 使用了WNOHANG参数,waitpid不会在这里等待 */
               if(pr==0){ /* 如果没有收集到子进程 */
                  printf("No child exited/n");
                   sleep(1);
               }
        }while(pr==0); /* 没有收集到子进程,就回去继续尝试 */
        if(pr==pc)
               printf("successfully get child %d/n", pr);
        else
               printf("some error occured/n");
}
运行结果

 
  
/*waitpid例程2*/
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
void unix_error(char *msg)
{
    fprintf(stderr, "%s:%s\n",msg,strerror(errno));//打印错误情况
    exit(0);
}
pid_t Fork(void)
{
    pid_t pid;//创建进程
    if((pid = fork())<0)
        unix_error("Fork error");
    return pid;
}
void main()
{
   int status;
   pid_t pid;
   printf("hello\n");
   pid=Fork();
   printf("%d\n",!pid);
   if(pid != 0 )
   {
       if(waitpid(-1,&status,0)>0)
       {
           if(WIFEXITED(status)!=0)
		  printf("%d\n",WEXITSTATUS(status));
       }
   }
    printf("bye\n");
    exit(0);
}
进程图:

结果:








猜你喜欢

转载自blog.csdn.net/zhangxiafll/article/details/80689027