免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1121 | 回复: 0
打印 上一主题 下一主题

学习笔记04-学习《精通UNIX下C语言编程及项目实践》 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-04-18 20:03 |只看该作者 |倒序浏览

                欢迎转载,请保留作者信息bill@华中科技大学
[/url]
[url=http://billstone.cublog.cn/]http://billstone.cublog.cn

  
  第三篇 并发程序设计
  业精于勤, 而荒于嬉.
  九、进程控制
  进程是程序的一次执行, 是运行在自己的虚拟地址空间的一个具有独立功能的程序. 进程是分配和释放资源的基本单位, 当程序执行时, 系统创建进程, 分配内存和CPU等资源; 进程结束时, 系统回收这些资源.
  线程与进程
  线程又名轻负荷进程, 它是在进程基础上程序的一次执行, 一个进程可以拥有多个线程.
  线程没有独立的资源, 它共享进程的ID, 共享进程的资源.
  线程是UNIX中最小的调度单位, 目前有系统级调度和进程级调度两种线程调度实行方式: 系统级调度的操作系统以线程为单位进行调度; 进程级调度的操作系统仍以进程为单位进行调度, 进程再为其上运行的线程提供调度控制. 
  环境变量
  UNIX中, 存储了一系列的变量, 在shell下执行'env'命令, 就可以得到环境变量列表.
  环境变量分为系统环境变量和用户环境变量两种. 系统环境变量在注册时自动设置, 大部分具有特定的含义; 用户环境变量在Shell中使用赋值命令和export命令设置. 如下例先设置了变量XYZ, 再将其转化为用户环境变量:

  
  
  [bill@billstone
  Unix_study]$ XYZ=/home/bill
  [bill@billstone
  Unix_study]$ env | grep XYZ
  [bill@billstone
  Unix_study]$ export XYZ
  [bill@billstone
  Unix_study]$ env | grep XYZ
  XYZ=/home/bill
  [bill@billstone
  Unix_study]$
  
  

  UNIX下C程序中有两种获取环境变量值的方法: 全局变量法和函数调用法
  (a) 全局变量法
  UNIX系统中采用一个指针数组来存储全部环境值:

  
  
  Extern
  char **environ;
  
  

  该法常用于将environ作为参数传递的语句中, 比如后面提到的execve函数等.

  
  
  [bill@billstone
  Unix_study]$ cat env1.c
  #include
  extern char **environ;
   
  int main()
  {
          char **p = environ;
   
          while(*p){
                  fprintf(stderr,
  "%s\n", *p);
                  p++;
          }
   
          return 0;
  }
  [bill@billstone
  Unix_study]$ make env1
  cc     env1.c  
  -o env1
  [bill@billstone
  Unix_study]$ ./env1
  SSH_AGENT_PID=1392
  HOSTNAME=billstone
  DESKTOP_STARTUP_ID=
  SHELL=/bin/bash
  TERM=xterm
  ... ... ... ...
  [bill@billstone
  Unix_study]
  
  

  (b) 函数调用法
  UNIX环境下操作环境变量的函数如下:

  
  
  #include
  
  char *getenv(char *name);
  int
  putenv(const char *string);
  
  

  函数getenv以字符串形式返回环境变量name的取值, 因此每次只能获取一个环境变量的值; 而且要使用该函数, 必须知道要获取环境变量的名字.

  
  
  [bill@billstone
  Unix_study]$ cat env2.c
  #include
  #include
   
  int main(int argc, char
  **argv)
  {
          int i;
   
          for(i=1;i
                  fprintf(stderr,
  "%s=%s\n", argv, getenv(argv));
   
          return 0;
  }
  [bill@billstone Unix_study]$
  make env2
  cc     env2.c  
  -o env2
  [bill@billstone
  Unix_study]$ ./env2 HOME LOGNAME rp
  HOME=/home/bill
  LOGNAME=bill
  rp=(null)
  [bill@billstone
  Unix_study]$
  
  

  在进程中执行新程序的三种方法
  进程和人类一样, 都有创建、发展、休眠和死亡等各种生命形态. 其中, 函数fork创建新进程, 函数exec执行新程序, 函数sleep休眠进程, 函数wait同步进程和函数exit结束进程.
  (1)
fork-exec
  调用fork创建的子进程, 将共享父进程的代码空间, 复制父进程数据空间, 如堆栈等. 调用exec族函数将使用新程序的代码覆盖进程中原来的程序代码, 并使进程使用函数提供的命令行参数和环境变量去执行新的程序.
  exec函数族有六个函数如下:

  
  
  #include
  
  int execl(const char *path,
  const char *arg0, ..., (char *)0);
  int execle(const char
  *path, const char *arg0, ..., (char *)0, char *const envp[]);
  int execlp(const char
  *file, const char *arg0, ..., (char *)0);
  int execv(const char *path,
  const char *argv[]);
  int execve(const char
  *path, const char *argv[], const char *envp[]);
  int execvp(const char
  *file, const char *argv[]);
  extern
  char **environ;
  
  

  如何用fork-exec方式执行程序'uname -a?

  
  
  [bill@billstone
  Unix_study]$ cat exec1.c
  #include
  
  #include
  #include
   
  int main()
  {
          pid_t pid;
   
          if((pid = fork()) == 0){
                  fprintf(stderr, "----
  begin ----\n");
                  // sleep(3);               // 睡眠3秒会导致子进程成为僵死进程
                  execl("/bin/uname",
  "uname", "-a", 0);
                  fprintf(stderr,
  "----  end  ----\n");
          }
          else if(pid > 0)
                  fprintf(stderr, "fork
  child pid = [%d]\n", pid);
          else
                  fprintf(stderr, "Fork
  failed.\n");
   
          return 0;
  }
  [bill@billstone
  Unix_study]$ make exec1
  cc     exec1.c  
  -o exec1
  [bill@billstone
  Unix_study]$ ./exec1
  ---- begin ----
  Linux billstone 2.4.20-8
  #1 Thu Mar 13 17:18:24 EST 2003 i686 athlon i386 GNU/Linux
  fork child pid =
  [13276]               
  [bill@billstone
  Unix_study]$ ./exec1
  ---- begin ----
  fork child pid =
  [13278]                      
  [bill@billstone
  Unix_study]$ Linux billstone 2.4.20-8 #1 Thu
  Mar 13 17:18:24 EST 2003 i686 athlon i386 GNU/Linux
  
  

  (2)
vfork-exec
  vfork比起fork函数更快, 二者的区别如下:
  a) vfork创建的子进程并不复制父进程的数据, 在随后的exec调用中系统会复制新程序的数据到内存, 继而避免了一次数据复制过程
  b) 父进程以vfork方式创建子进程后将被阻塞, 知道子进程退出或执行exec调用后才能继续运行.
  当子进程只用来执行新程序时, vfork-exec模型比fork-exec模型具有更高的效率, 这种方法也是Shell创建新进程的方式.

  
  
  [bill@billstone
  Unix_study]$ cat exec2.c
  #include
  
  #include
  #include
   
  int main()
  {
          pid_t pid;
   
          if((pid = vfork()) == 0){
                  fprintf(stderr, "----
  begin ----\n");
                  sleep(3);
                  execl("/bin/uname",
  "uname", "-a", 0);
                  fprintf(stderr,
  "----  end  ----\n");
          }
          else if(pid > 0)
                  fprintf(stderr, "fork child pid =
  [%d]\n", pid);
          else
                  fprintf(stderr, "Fork
  failed.\n");
   
          return 0;
  }
  [bill@billstone
  Unix_study]$ make exec2
  make: `exec2' is up to
  date.
  [bill@billstone
  Unix_study]$ ./exec2
  ---- begin ----
  fork child pid =
  [13293]               
  [bill@billstone
  Unix_study]$ Linux billstone 2.4.20-8 #1 Thu
  Mar 13 17:18:24 EST 2003 i686 athlon i386 GNU/Linux
   
  
  

  (3) system
  在UNIX中, 我们也可以使用system函数完成新程序的执行.

  
  
  #include
  
  char *getenv(char *name);
  int
  putenv(const char *string);
  
  

  函数system会阻塞调用它的进程, 并执行字符串string中的shell命令.

  
  
  [bill@billstone
  Unix_study]$ cat exec3.c
  #include
  #include
   
  int main()
  {
          char cmd[] = {"/bin/uname
  -a"};
   
          system(cmd);
   
          return 0;
  }
  [bill@billstone
  Unix_study]$ make exec3
  cc     exec3.c  
  -o exec3
  [bill@billstone
  Unix_study]$ ./exec3
  Linux billstone 2.4.20-8
  #1 Thu Mar 13 17:18:24 EST 2003 i686 athlon i386 GNU/Linux
  [bill@billstone
  Unix_study]$
  
  

  僵死进程
  僵死进程是已经终止, 但没有从进程表中清除的进程, 下面是一个僵死进程的实例

  
  
  [bill@billstone
  Unix_study]$ cat szomb1.c
  #include
  #include
  #include
  
   
  int main()
  {
          pid_t pid;
   
          if((pid = fork()) == 0){
                 
  printf("child[%d]\n", getpid());
                  exit(0);
          }
          // wait();
          printf("parent[%d]\n",
  getpid());
          sleep(10);
   
          return 0;
  }
  [bill@billstone
  Unix_study]$
  
  

  后台运行程序szobm1, 并在子进程结束后, 父进程没有结束前, 运行命令查询进程情况:

  
  
  [bill@billstone
  Unix_study]$ make szomb1
  cc     szomb1.c   -o szomb1
  [bill@billstone
  Unix_study]$ ./szomb1 &
  [2] 13707
  child[13708]
  [bill@billstone
  Unix_study]$ parent[13707]
  ps -ef | grep 13707
  bill     13707 
  1441  0 04:17 pts/0    00:00:00 ./szomb1
  bill     13708 13707  0 04:17 pts/0    00:00:00 [szomb1 ]     // 僵死进程
  bill     13710 
  1441  0 04:17 pts/0    00:00:00 grep 13707
  [bill@billstone
  Unix_study]$
  
  

  其中, 'defunct'代表僵死进程. 对于僵死进程, 不能奢望通过kill命令杀死之, 因为它已经'死'了, 不再接收任何系统信号.
  当子进程终止时, 它释放资源, 并且发送SIGCHLD信号通知父进程. 父进程接收SIGCHLD信号,调用wait返回子进程的状态, 并且释放系统进程表资源. 故如果子进程先于父进程终止, 而父进程没有调用wait接收子进程信息,则子进程将转化为僵死进程, 直到其父进程结束.
  一旦知道了僵死进程的成因, 我们可以采用如下方法预防僵死进程:
  (1) wait法
  父进程主动调用wait接收子进程的死亡报告, 释放子进程占用的系统进程表资源.
  (2) 托管法
  如果父进程先于子进程而死亡, 则它的所有子进程转由进程init领养, 即它所有子进程的父进程ID号变为1. 当子进程结束时init为其释放进程表资源.
  (3) 忽略SIGC(H)LD信号
  当父进程忽略SIGC(H)LD信号后, 即使不执行wait, 子进程结束时也不会产生僵死进程.
  (4) 捕获SIGC(H)LD信号
  当父进程捕获SIGC(H)LD信号, 并在捕获函数代码中等待(wait)子进程
  守护进程
  所谓守护进程是一个在后台长期运行的进程, 它们独立于控制终端, 周期性地执行某项任务, 或者阻塞直到事件发生, 默默地守护着计算机系统的正常运行. 在UNIX应用中, 大部分socket通信服务程序都是以守护进程方式执行.
  完成一个守护进程的编写至少包括以下几项:
  (1) 后台执行
  后台运行的最大特点是不再接收终端输入, 托管法可以实现这一点

  
  
  pid_t pid;
  pid = fork();
  if(pid > 0)
  exit(0);       // 父进程退出
  /* 子进程继续运行  */
  父进程结束, shell重新接管终端控制权, 子进程移交init托管
  
  

  (2) 独立于控制终端
  在后台进程的基础上, 脱离原来shell的进程组和session组, 自立门户为新进程组的会话组长进程, 与原终端脱离关系

  
  
  #include
  
  pid_t
  setsid();
  
  

  函数setsid创建一个新的session和进程组.
  (3) 清除文件创建掩码
  进程清除文件创建掩码,代码如下:

  
  
  umask(0);
  
  

  (4) 处理信号
  为了预防父进程不等待子进程结束而导致子进程僵死, 必须忽略或者处理SIGCHLD信号, 其中忽略该信号的方法为:

  
  
  signal(SIGCHLD,
  SIG_IGN);
  
  

  守护进程独立于控制终端, 它们一般以文件日志的方式进行信息输出.
  下面是一个简单的守护进程实例InitServer

  
  
  [bill@billstone
  Unix_study]$ cat initServer.c
  #include
  #include
  #include
  #include
  
   
  void ClearChild(int
  nSignal){
          pid_t pid;
          int nState;
                      //  WNOHANG非阻塞调用waitpid, 防止子进程成为僵死进程
          while((pid = waitpid(-1, &nState,
  WNOHANG)) > 0);
          signal(SIGCLD, ClearChild);    // 重新绑定 SIGCLD信号
  }
   
  int InitServer(){
          pid_t pid;
   
          assert((pid = fork()) >= 0);        // 创建子进程
          if(pid != 0){               // 父进程退出, 子进程被init托管
                  sleep(1);
                  exit(0);
          }
          assert(setsid() >= 0);           // 子进程脱离终端
          umask(0);                    // 清除文件创建掩码
          signal(SIGINT, SIG_IGN);      // 忽略SIGINT信号
          signal(SIGCLD, ClearChild);     //
  处理SIGCLD信号,预防子进程僵死
   
          return 0;
  }
   
  int main()
  {
          InitServer();
          sleep(100);
   
          return 0;
  }
  [bill@billstone
  Unix_study]$ make initServer
  cc     initServer.c   -o initServer
  [bill@billstone
  Unix_study]$ ./initServer
  [bill@billstone
  Unix_study]$ ps -ef | grep initServer
  bill     13721    
  1  0 04:40 ?        00:00:00 ./initServer      // '?'代表initServer独立于终端
  bill     13725 
  1441  0 04:41 pts/0    00:00:00 grep initServer
  [bill@billstone
  Unix_study]$
  
  

  程序在接收到SIGCLD信号后立即执行函数ClearChild, 并调用非阻塞的waitpid函数结束子进程结束信息, 如果结束到子进程结束信息则释放该子进程占用的进程表资源, 否则函数立刻返回. 这样既保证了不增加守护进程负担, 又成功地预防了僵死进程的产生.
               
               
               
               
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/47687/showart_1903611.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP