第8章 进程控制

8.2 进程标识符

ID为0的进程通常是调度进程,常常被称为交换教程。该进程是内核的一部分,它并不执行任何磁盘上的程序,因此也被称为系统进程。 进程ID 1通常是init进程,在自举过程结束时由内核调用。该进程的程序文件在unix早期版本中是/etc/init,在新版本中是/sbin/init.此进程负责在自举内核后启动一个unix系统。

#include<unistd.h>

pid_t getpid(void);

pid_t getppid(void); 返回值:调用进程的进程ID 调用进程的父进程ID

uid_t getuid(void); 返回值:调用进程的实际用户ID

uid_t geteuid(void); 返回值:调用进程的有效用户ID

gid_t getgid(void); 返回值:调用进程的实际组ID

gid_t getegid(void); 返回值:调用进程的有效组ID

 

8.3 fork函数

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

#include<unistd.h>

pid_t fork(void); 返回值:子进程中返回0,父进程中返回子进程ID,出错则返回-1

父子进程并不共享这些存储空间部分。父子进程共享正文段。

 

#include "apue.h"    int     glob = 6;       /* external variable in initialized data */  char    buf[] = "a write to stdout\n";    int  main(void)  {      int       var;      /* automatic variable on the stack */      pid_t     pid;        var = 88;      if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)          err_sys("write error");      printf("before fork\n");    /* we don't flush stdout */        if ((pid = fork()) < 0) {          err_sys("fork error");      } else if (pid == 0) {      /* child */          glob++;                 /* modify variables */          var++;      } else {          sleep(2);               /* parent */      }        printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);      exit(0);  }
注意:如果把标准输出 重定向到一个文件, before fork 会输出两次!!!!
在fork之后处理文件描述符有两种常见的情况:
(1)父进程待子进程完成。在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读写操作的任一共享描述符的文件偏移量已执行了相应更新
(2)父子进程各自执行不同的程序段。在这种情况下,在fork之后,父子进程各自关闭它们不需要使用的文件描述符,这样就不会干扰对方使用的文件描述符。这种方法是网络服务进程中经常使用的。
  
  
第8章  进程控制 - night person - 夜归人
 

某些操作系统将两个操作fork之后执行exec组合成一个,并称其为spawn.

 

8.4 vfork函数

1.vfork与fork一一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中。因为子进程会立即调用EXEC或EXIT,于是也就不会村访该地址空间。

2.vfork和fork之间的另外一个区别是:vfork保证字进程先运行,在它调用exec或exit之后父进程可能被调度运行。

 

 

#include "apue.h"

 

int     glob = 6;       /* external variable in initialized data */

 

int

main(void)

{

    int     var;        /* automatic variable on the stack */

    pid_t   pid;

 

    var = 88;

    printf("before vfork\n");   /* we don't flush stdio */

    if ((pid = vfork()) < 0) {

        err_sys("vfork error");

    } else if (pid == 0) {      /* child */

        glob++;                 /* modify parent's variables */

        var++;

        _exit(0);               /* child terminates */

    }

    /*

     * Parent continues here.

     */

    printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);

    exit(0);

}

 

 

./a.out
before vfork
pid = 29039, glob = 7, var = 89

 

如果子进程调用exit,而该实现冲洗所有标准I/O流。如果这会死函数库采取的唯一动作,那么我们会见到输出与子进程调用_exit所产生的输出完全相同,没有任何区别。如果该实现也关闭标准I/O流,那么表示标准输出FILE对象的相关存储区将被清0.

 

 

 

8.5 exit函数

进程有下面 5种正常终止方式:

(1)    在main函数内执行return语句。这等效于调用exit.

(2)    调用exit函数

(3)    调用_Exit和_exit是同义的。在UNIX系统中,_Exit和_exit是同义的,并不清洗标准I/O流。_exit函数由exit调用,它处理unix特定的细节。

(4)    进程最后一个线程在其启动历程中执行返回语句。但是 该线程的返回至不会作为进程的返回值。当最后一个线程从其启动历程返回时,该进程以终止状态0返回。

(5)    进程的最后一个线程调用pthread_exit函数。如同前面一样,在这种情况下,进程终止状态是0,这与传送给pthread_exit的参数无关。

三种异常终止方式如下:

(1)       调用abort。

(2)       当进程接收到某些信号时。

(3)       最后一个线程对”取消”请求做出响应。

 

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

 

对于三个终止函数(exit,_exit和_Exit),实现这一点的方法是,将其退出状态参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。

 

 

8.6 wait 和 waitpid函数

如果其所有子进程都还在运行,则阻塞。

如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回

如果它没有任何子进程,则立即出错返回。

 

如果进程由于接收到SIGCHLD信号而调用wait,则可期望wait会立即返回。但是如果注意在任何时候调用wait,则进程可能会阻塞。

#include < sys/wait.h>

pid_t wait (int * statloc);

pid_t  waitpid(pid_t pid , int * statloc, int options);

两个函数返回值:若成功则返回进程ID , 0.若出错则返回-1;

 

这两个函数的区别如下:

在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。

Waitpid并不等于在其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程

 

 

 

8.7 waitid函数

#include<sys/wait.h>

int maitid(idtype_t idtype, id_t id,siginfo_t *infop,int options); //返回值:若成功则返回0  若出错则返回-1

与waitpid相似,waitid允许一个进程制定要等待的子进程。但它使用单独的参数表示要等待的子进程的类型,而不是将此与进程ID或进程组ID组合成一个参数。ID参数的作用与IDTYPE的值相关。

idtype参数: P_PID 等待一个特定的进程:id包含要等待子进程的进程ID

                     P_PGID 等待一个特定进程组中的任意子进程:ID包含要等待子进程的进程组ID

                    P_ALL  等待任一子进程:忽略ID

第8章  进程控制 - night person - 夜归人
 
 
8.8 wait3和wait4函数
第8章  进程控制 - night person - 夜归人
 
 
8.9竞争条件
 
 
8.10 exec函数
第8章  进程控制 - night person - 夜归人
 
这些函数之间的第一个区别是前4个路径名作为参数,后两个则取文件名作为参数。当指定filename作为参数时:
  如果filename中包含/,则将其视为路径名
 否则就按path环境变量,在它所指定的个目录中搜寻可执行文件。
PATH变量中包含一张目录表, 目录之间用冒号:分割,例如: name=value环境字符串
PATH=/bin:/usr/bin:/usr/loca/bin/:.
制定在4个目录中进行搜索。最后的路径前缀表示当前目录。
 
 
#include "apue.h"  #include <sys/wait.h>    char    *env_init[] = { "USER=unknown", "PATH=/tmp", NULL };    int  main(void)  {      pid_t   pid;        if ((pid = fork()) < 0) {          err_sys("fork error");      } else if (pid == 0) {  /* specify pathname, specify environment */          if (execle("/home/sar/bin/echoall", "echoall", "myarg1",                  "MY ARG2", (char *)0, env_init) < 0)              err_sys("execle error");      }        if (waitpid(pid, NULL, 0) < 0)          err_sys("wait error");        if ((pid = fork()) < 0) {          err_sys("fork error");      } else if (pid == 0) {  /* specify filename, inherit environment */          if (execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)              err_sys("execlp error");      }        exit(0);  }  

 
#include "apue.h"    int main(int argc, char *argv[])  {      int         i;      char        **ptr;      extern char **environ;        for (i = 0; i < argc; i++)      /* echo all command-line args */          printf("argv[%d]: %s\n", i, argv[i]);        for (ptr = environ; *ptr != 0; ptr++)   /* and all env strings */          printf("%s\n", *ptr);        exit(0);  }  
 
 
8.11 更改用户ID 和 组ID
可以用setuid函数设置实际用户ID和有效用户ID。与此类似,可以用setgid函数设置实际组ID和有效组ID
#include<unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);  两个函数返回值:若成功则返回0 , 若出错则返回-1
(1)若进程具有超级用户特权,则setuid函数将实际用户ID,有效用户ID,以及保存的设置用户ID设置为UID。
(2)若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置用户ID,则setuid只将有效用户iD设置为uid,不改变实际用户ID和
    保存的设置用户ID。
(3)如果上面两个条件都不满足,则将errno设置为EPERM,并返回-1。
第8章  进程控制 - night person - 夜归人
 
1.setreuid和setregid函数
#include<unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid , gid_t egid); 两个函数的返回值:若成功则返回0 若出错则返回-1
 
一个非特权用户总能交换实际用户ID和有效用户ID。着就允许一个设置用户ID程序转换成只具有用户的普通权限,以后又可再次转换回设置用户ID所得到的额外权限。
 
 
 
2.seteuid和setegid只是更改有效用户ID和有效组ID
#include<unistd.h>
int seteuid(uid_t uid);
int setegid(ugid_t gid);两个函数的返回值:若成功则返回0 若出错则返回-1
 
 
 
 
 8.12解释器文件
#!pathname [ optional-argument]
感叹号和pathnae之间的空格是可选的。
 
#include "apue.h"  #include <sys/wait.h>    int main(void)  {      pid_t   pid;      if ((pid = fork()) < 0) {          err_sys("fork error");      } else if (pid == 0) {          /* child */          if (execl("/home/sar/bin/testinterp",                    "testinterp", "myarg1", "MY ARG2", (char *)0) < 0)              err_sys("execl error");      }      if (waitpid(pid, NULL, 0) < 0) /* parent */          err_sys("waitpid error");      exit(0);  }  

 结果:
$ cat /home/sar/bin/testinterp
   #!/home/sar/bin/echoarg foo
   $ ./a.out
   argv[0]: /home/sar/bin/echoarg
   argv[1]: foo
   argv[2]: /home/sar/bin/testinterp
   argv[3]: myarg1
   argv[4]: MY ARG2
说明:
当内核exec该解释器时候,argv[0]是该解释器的pathname,argv[1]是解释器文件中的可选参数,其余参数是pathname,以及程序中调用execl的第二个和第三个参数。
 
 
8.13 system函数
#include<stdlib.h>
int system(const char * cmdstring);
如果cmdstring是一个空指针,则仅当命令处理程序可用时,system返回非0值,这一特征可以确定在一个给定的操作系统上是否支持system函数。因为system在其实现中调用fork,exec和waitpid,因此有三种返回值:
(1)如果fork失败或者waitpid返回除EINTR之外的出错,则system返回-1,而且errno中设置了错误类型值。
(2)如果exec失败(表示不能执行shell),则其返回值如同shell执行了exit(127)一样。
(3)否则所有三个函数(for,exec和waitpid)都执行成功,并且system的返回值是shell的终止状态,其格式已在waitpid中说明。
 
#include    <sys/wait.h>  #include    <errno.h>  #include    <unistd.h>    int system(const char *cmdstring)    /* version without signal handling */  {      pid_t   pid;      int     status;        if (cmdstring == NULL)          return(1);      /* always a command processor with UNIX */        if ((pid = fork()) < 0) {          status = -1;    /* probably out of processes */      } else if (pid == 0) {              /* child */          execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);          _exit(127);     /* execl error */      } else {                             /* parent */          while (waitpid(pid, &status, 0) < 0) {              if (errno != EINTR) {                  status = -1; /* error other than EINTR from waitpid() */                  break;              }          }      }        return(status);  }
 
shell的-c选项告诉shell程序取下一个命令行参数(在这里是cmdstring)作为命令输入。
 
设置用户ID程序
设置用户ID或设置组ID程序决不应调用system函数。
 
8.14 进程会计
 一个至今没有说明的函数(acct)用于启用和禁用进程会计。会计记录结构定义在头文件<sys/acct.h>中,其形式如下:
第8章  进程控制 - night person - 夜归人
 
第8章  进程控制 - night person - 夜归人
会计记录所需的各种数据(如CPU时间,传输字符数)都由内核保存在进程表中,并在一个新进程被创建时置初值。每次进程终止时都会编写一条会计记录。会计文件中记录的顺序对应于进程终止的顺序。而不是它们启动的顺序。为了确定启动顺序,需要读全部会计文件,并按启动日历时间进行排序。
。。。。
 
 
8.15 用户标识
#include <unistd.h>
char * getlogin(void); 返回值:若成功则返回指向登录名字字符串的指针,若出错则返回NULL
给出了登录名,就可用getpwnam在口令文件中查找用户的相应的记录,从而确定其登录shell等。
 
 
8.16  进程时间
#include<sys/temes.h>
clock_t times(struct tms * buf);
返回值:若成功则返回流逝的墙上时钟时间(单位:时钟滴答数),若出错则返回-1 
  
第8章  进程控制 - night person - 夜归人
注意:此结构没有包含墙上时钟时间的任何测量值。作为替代,times函数返回墙上时钟时间作为其函数值。此值是相对于过去的某一时刻测量的,所以不能用其绝对值,而必须使用其相对值。
所有由此函数返回的clock_t值都用_SC_CLK_TCK(由sysconf函数返回的每秒时钟滴答数)变成秒数。
 
 
 
#include "apue.h"  #include <sys/times.h>    static void pr_times(clock_t, struct tms *, struct tms *);  static void do_cmd(char *);    int  main(int argc, char *argv[])  {        int     i;        setbuf(stdout, NULL);      for (i = 1; i < argc; i++)          do_cmd(argv[i]);    /* once for each command-line arg */      exit(0);  }    static void  do_cmd(char *cmd)        /* execute and time the "cmd" */  {       struct tms  tmsstart, tmsend;       clock_t     start, end;       int         status;         printf("\ncommand: %s\n", cmd);         if ((start = times(&tmsstart)) == -1)    /* starting values */           err_sys("times error");         if ((status = system(cmd)) < 0)     /* execute command */           err_sys("system() error");         if ((end = times(&tmsend)) == -1)       /* ending values */           err_sys("times error");         pr_times(end-start, &tmsstart, &tmsend);       pr_exit(status);  }  static void  pr_times(clock_t real, struct tms *tmsstart, struct tms *tmsend)  {      static long     clktck = 0;        if (clktck == 0)    /* fetch clock ticks per second first time */          if ((clktck = sysconf(_SC_CLK_TCK)) < 0)              err_sys("sysconf error");       printf(" real:  %7.2f\n", real / (double) clktck);       printf(" user:  %7.2f\n",         (tmsend->tms_utime - tmsstart->tms_utime) / (double) clktck);       printf(" sys:   %7.2f\n",         (tmsend->tms_stime - tmsstart->tms_stime) / (double) clktck);       printf(" child user:   %7.2f\n",         (tmsend->tms_cutime - tmsstart->tms_cutime) / (double) clktck);       printf(" child sys:    %7.2f\n",         (tmsend->tms_cstime - tmsstart->tms_cstime) / (double) clktck);  }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值