笔记二:进程间的通信(fork、孤儿进程,僵死进程等)

来源:互联网 发布:淘宝模板怎么用 编辑:程序博客网 时间:2024/06/10 14:47
      以下是以前学习《unix环境高级编程》时的一些笔记和测试代码,好久没看过了,没有再次验证,存在错误的话,希望见谅,分享下主要是!!!
ps     查看系统中的进程   ps–axj
A与B在用户空间是不能通信的,相当于封闭的房子,也没有窗户,所以在用户空间是无法通信的
二:进程控制相关函数
1.fork()函数
功能:创建一个子进程
参数:没有参数
返回值:成功,返回二个值;0 --- 子进程中  >0(子进程的PID号) ---- 父进程中
出错,返回一个值-1
成功时,内核为何会返还二个值?
用户空间的进程,可以认为是一个封闭的房子(在用户空间),内核是怎样对用户空间这么多进程进行管理,怎样识别,是通过PID。
fork() 调用成功,内核则在用户空间创建一个进程(房子),这个房子和之前的进程代码段,数据段等都一样(除了PID之外)
以下两段fork函数简单的使用
#include "stdio.h"#include "unistd.h"#include "sys/types.h"#include "stdlib.h"int main(){  pid_t pid;  pid=fork();  printf("hello linux\n");  usleep(200);  //200微秒  printf("11111111111\n");  while(1);  return 0;}
#include "stdio.h"#include "unistd.h"#include "sys/types.h"#include "stdlib.h"int main(){  pid_t pid;  pid=fork();  if(pid >0)  {    printf("parent process run\n");  }  if(pid ==0)  {    printf("child process run\n");  }  while(1);  return 0;}
getpid(); 获取本进程的PID号(返回值)
getppid();获取本进程父进程PID号(返回值)
exit():   进程退出函数
什么是僵尸进程?什么是孤儿进程?
  子进程退出,父进程没有退出,此时子进程处于Z(zobile)(僵尸) 状态,处于此状态的进程仍要耗费系统资源。子进程自己不能回收资源,只有父亲负责为子进程回收资源。父进程退出,子进程没有退出,此时子进程变为孤儿进程,会被1号进程收养,即init进程为其父进程。
例子1:
/*僵尸进程    查看进程pid状态等消息    可以终端使用命令ps aux或者ps axj*/#include "signal.h"#include "sys/types.h"#include "stdio.h"int main(){    pid_t pid;    pid = fork();    if(pid < 0)    {        printf("创建进程失败\n");        return -1;    }    /*对于父进程首先是睡眠(8秒)S状态,8秒后进入R(运行)状态      对于子进程就变成了T状态;     */    if(pid > 0)    {        sleep(8);//睡眠状态;         /*如果设置的waitpid设置为waitpid(pid,NULL,0)为阻塞,那么将不会产生僵尸进程,父进程会为其回收*/        if(waitpid(pid,NULL,WNOHANG) == 0)//等待子进程结束;可以设置为非阻塞(WNOHANG);        {            kill(pid,9);//杀死子进程,父进程没有结束,子进程将变成Z状态,因为父进程没有给子进程回收资源,子进程将变为僵尸进程;        }        while(1);    }    if(pid == 0)    {        printf("raise function before\n");        raise(SIGTSTP);//暂停程序,相当于ctrl+z;  T状态        printf("raise function after\n");        exit(0);    }}
如果我们要求:子进程退出,父进程没有退出,同时也不要子进程处于Z(zobile) 状态,而是完全结束,怎么办?
2.exit()--------库函数   进程的退出函数
头文件:#include “stdlib.h”
参数:  int 退出状态----- 假设:0 为正常退出,-1为非正常退出
exit中的参数退出状态主要作用是传递给父进程,父进程根据这些状态可判定子进程现在处于什么情况。父进程怎样接收到这个状态呢?是通过wait 或waitpid
exit()------系统调用函数----unistd.h
以下为exit()和_exit()基本使用

#include "stdio.h"#include "unistd.h"#include "stdlib.h"int main(){  printf("hello linux"); // fflush(stdout);  //_exit(0);  exit(0);  printf("1111111111111");  return 0;}
3  wait()函数
所需头文件:#include <sys/types.h>
          #include <sys/wait.h>
函数原型:  pid_t wait(int *status)
函数参数: status 是一个整型指针,指向的对象用来保存子进程退出时的状态。
                      status 若为空,表示忽略子进程退出时的状态
                      status 若不为空,表示保存子进程退出时的状态
另外,子进程的结束状态可由Linux中一些特定的宏来测定。
函数返回值:    成功:子进程的进程号  失败:-1
什么时候会阻塞,什么时候不会阻塞?
调用该函数使进程阻塞(睡眠),直到任一个子进程结束或者是该进程接收到了一个信号为止。
如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回,即不会阻塞。
以下是关于wait函数的使用
#include "sys/wait.h"#include "sys/types.h"#include "stdio.h"int main(){  printf("wait before\n");  wait(NULL);  printf("wait after\n");  return 0;}
#include "sys/types.h"#include "stdio.h"#include "unistd.h"int main(){  pid_t pid;  pid=fork();  if(pid <0 )  {     printf("creat process failure\n");     return -1;  }  if(pid ==0)//child process  {   printf("this is child process\n") ;   while(1);  }  if(pid >0) //parent process  {    printf("this is parent process\n");      printf("parent process:wait before\n");      wait(NULL);      printf("parent process:wait after\n");      while(1);  }  return 0;}
解析:
  子进程1: 
        printf     ------- pid
        sleep(10)  -------S                   10秒后   结束
        exit(1)
  子进程2: 
        printf     ------- pid
        sleep(20)  -------S                   S(睡眠状态)                    再过10秒   
        exit(3)
   父进程:
        printf---
        printf ---wait before
        pet=wait------------------S
        printf---wait after ret=%d               
        pet=wait                              S(睡眠状态)                         R(运行状态)
        printf  second wait ret=%d
#include "sys/types.h"#include "sys/wait.h"#include "unistd.h"#include "stdio.h"#include "stdlib.h"int main(){  pid_t pid;  int ret;  pid = fork();  if(pid <0)  {    printf("creat process failure\n");    return -1;  }  if(pid == 0) //child process1  {    printf("this is a child process,pid=%d\n",getpid());    sleep(10);    exit(1);  }  if(pid >0)  {    pid_t  pid1;    pid1=fork();    if(pid1<0)    {      printf("creat child process1 failure\n");      exit(2);    }    if(pid1 ==0)    {      printf("this is a child process1 run,pid=%d\n",getpid());      sleep(20);      exit(3);    }    printf("this is a parent process\n");    printf("wait before\n");    ret=wait(NULL);    printf("wait after,first wait ret=%d\n",ret);    ret=wait(NULL);    printf("second wait after,ret=%d\n",ret);    while(1);    return 0;  }
4. waitpid()   功能比wait 功能更强
可以等待某一个子进程退出,可通过第一个参数来实现
所需头文件:#include <sys/types.h>
         #include <sys/wait.h>
函数原型: pid_t waitpid(pid_t pid, int *status, int options)
函数参数: pid    pid>0:只等待进程ID等于pid的子进程,不管已经有其他子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
          pid=-1:等待任何一个子进程退出,此时和wait作用一样
          pid=0:等待其组ID等于调用进程的组ID的任一子进程。
          pid<-1:等待其组ID等于pid的绝对值的任一子进程。
          status   同wait
          options   WNOHANG:若由pid指定的子进程并不立即可用,则waitpid不阻塞,此时返回值为0
         WUNTRACED:若某实现支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还未报告过,则返回其状态。
          0:同wait,阻塞父进程,等待子进程退出。
函数返回值:   正常:结束的子进程的进程号
         使用选项WNOHANG且没有子进程结束时:0
         调用出错:-1
例子:
#include "sys/types.h"#include "sys/wait.h"#include "unistd.h"#include "stdio.h"#include "stdlib.h"int main(){  pid_t pid;  int ret;  int status;  pid = fork();  if(pid <0)  {    printf("creat process failure\n");    return -1;  }  if(pid == 0) //child process1  {    printf("this is a child process,pid=%d\n",getpid());    sleep(10);    exit(1);  }  if(pid >0)  {    pid_t  pid1;    pid1=fork();    if(pid1<0)    {      printf("creat child process1 failure\n");      exit(2);    }    if(pid1 ==0)    {      printf("this is a child process1 run,pid=%d\n",getpid());      sleep(20);      exit(3);    }    printf("this is a parent process\n");    printf("wait before\n");    ret=waitpid(pid1,&status,0);    printf("wait after,first wait ret=%d,status=%d\n",ret,WEXITSTATUS(status));//    ret=wait(&status);//    printf("second wait after,ret=%d,status=%d\n",ret,WEXITSTATUS(status));    while(1);    return 0;  }}
5  vfork()    ----形式和fork()
vfork() 调用成功,内核则在用户空间创建一个进程,但是不会重新建立一个房子,父子里程共享父进程这个房子,然后子进程先运行,父进程后运行
6.在此之前创建子进程,子进程进行了代码的完全拷贝,虽然我们用返回值的不同使得父子进程执行了二个不同的代码区。但是很多情况下:
      父子进程的代码完全不一样,比如 bash 这是父进程,a.out 是子进程,这二段代码没有相似性;或父进程是C代码,子进程是一个shell脚本,等,我们应该怎么处理?
       当进程认为自己不能再为系统和用户做出任何贡献了时就可以调用exec函数,让自己执行新的程序,我们应该怎么处理?
伪代码:
int main()                           computer 这个可执行程
{                                     
  printf(“dfdfggf\n”);
  i++;
  .....
  Computer//不是函数,而是另外一个程序
}
可以通过exec 函数族来解决这些问题。
函数的功能:可以调用另外一个程序来运行,会把原来的程序中的代码段,数据段等都替换掉,从这个调用的程序的开始来执行,会保留原进程的PID,其它都不保留。
什么时候要用这个函数呢?
      当进程认为自己不能再为系统和用户做出任何贡献了时就可以调用exec函数,让自己执行新的任务。
如果某个进程想同时执行另一个程序,它就可以调用fork函数创建子进程,然后在子进程中调用任何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样。

所需头文件:#include <unistd.h>
函数原型:   int execl(const char *path, const char *arg, ...);
                   int execv(const char *path, char *const argv[]);
                   int execle(const char *path, const char *arg, ..., char *const envp[]);
                   int execve(const char *path, char *const argv[], char *const envp[]:xp[]);
                   int execlp(const char *file, const char *arg, ...);
                   int execvp(const char *file, char *const argv[]);//环境变量加
函数返回值:    -1:出错,成功是0
参数:        第一个参数,前面四个都一样,后面二个一样,path  file
                   path 必须要指定所在程序的路径,要不是绝对路径或相对路径
                   file  不用指定路径(指定路径也可以),在环境变量PATH中找这个程序
char * 逐个列举
例如这样一个程序
ls  –l  /mnt   ./child/xxx          “./child/xxx”  NULL
怎样传递这个参数呢? 
“ls” “-l” “/mnt”  NULL   
采用字符指针数组的方式传递参数
char *argv[]={“ls” “-l” “/mnt”  NULL }

第一个参数:有二类,char *path—不仅要指定程序的名称也要指定程序的路径 
char *file----指定程序的名称,你这个程序的所在路径一定要在环境变量位PATH中找到
第二参数:  char *arg ……   char *argv[]
execl例子
#include "stdio.h"#include "unistd.h"int main(){  printf("exec before\n");  execl("./xxx","./xxx",NULL);  printf("exec after\n");  return 0;}
execv例子
#include "stdio.h"#include "unistd.h"int main(){  char *buf[]={"./child/xxx",NULL};  printf("exec before\n");  execv("./child/xxx",buf);  printf("exec after\n");  return 0;}
execvp例子
#include "stdio.h"#include "unistd.h"int main(){  char *buf[]={"xxx",NULL};  printf("exec before\n");  execvp("xxx",buf);  printf("exec after\n");  return 0;}
0 0