Linux下进程的控制

linux下进程的控制

进程的创建

  1. 首先,我们知道进程的创建需要调用fork()函数。
  2. fork()一次调用两次返回,子进程返回0,父进程返回子进程的pid。
  3. 同样也可以调用vfork来创建子进程,但此时父子进程共享地址空间,因此我们一般不建议使用。
#include<stdio.h>
#include<unistd.h>
int main()
{

        pid_t pid;
        pid=fork();
        if(pid<0)
        {
                perror("fork");
                return -1;
        }
        if(pid==0)
        {
                //chilid
                while(1)
                {
                        printf("I am Child\n");
                        sleep(1);
                }

        }
        while(1)
        {
                printf("I am Father\n");
                sleep(1);
        }
        return 0;
}

这样就完成了进程的创建。

进程的等待

  1. 我们知道,一个进程的退出,它的父进程必须知道它的退出信息,因此会保持僵尸状态,而如果僵尸进程没有人去回收的话,就会造成内存的泄露。

  2. 父进程需要通过等待的方式,来获取子进程退出的信息,并且回收掉子进程的资源。

  3. 函数1:pid_t wait(int*status),成功返回等待进程id,失败返回-1,并且带出等待进程的退出信息。不关心退出信息的话就可以设置成NULL;

  4. 函数2:pid_t waitpid(pid_t pid,int *status,int options);正常返回收集到的子进程的ID,如何设置了WNOHANG,调用发现已经没有已退出的子进程时候,返回0,pid=-1时,表示回收任一个子进程,pid>0表示等待其进程的ID和pid相等的子进程。


  1 #include<stdio.h>
  2 #include<unistd.h>                                                                                                                                                                                           
  3 int main()
  4 {
  5     int status=0;
  6     int count=5;
  7     pid_t pid=fork();
  8     if(pid<0)
  9     {
 10         perror("fork");
 11             return -1;
 12     }
 13     else if(pid==0)
 14     {
 15         //child
 16         while(count--)
 17         {
 18             printf("Child process running\n");
 19  
 20         }
 21         exit(1);
 22  
 23  
 24     }
 25     else
 26     {
 27         //father
 28         wait(&status);
 29         printf("status is %d\n",status);
 30         printf("Father process running\n");
 31     }
 32  
 33     return 0;
 34 }

其中不管是wait还是waitpid,都有一个status标志位标志着子进程的退出消息,因此很有必要研究一下里面到底存放的是什么。
这里写图是三数片描是述

  • 首先,这个status不能简单的当,作一个整形来看待,更应该把它比喻成一个位图。
  • 而我们只需要研究它的低16位就足够了
  • 其中,当一个进程代码完成,正常结束掉的时候,它所告诉父进程的退出状态存放在高8位。第八位全0
  • 而它如果是由信号杀死的话,那么它的低7位就存放的是被那个终止信号干掉的,第8为是core dump 标志位,表示它是否生成了core dump文件。我们称之为核心转储

进程终止

  1. 一个进程的结束有三种情况:1.代码运行完,结果正确,2.代码运行完,结果不正确,3.代码
    运行异常终止
  2. 而正常退出又分为man函数return返回和调用exit或者_exit来。
  3. 异常终止比如接收到ctrl+c 这样的信号终止。
    调用exit
  1 #include<stdio.h>
  2 #include<stdlib.h>                                                                                                                                                                                           
  3 #include<unistd.h>
  4 int main()
  5 {
  6  
  7     printf("hello world\n");
  8     exit(1);
  9 }

这里写图片描述
调用_exit

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 int main()
  5 {     
  6       
  7     printf("hello world");
  8       _exit(1);
  9 }  

这里写图片描述

  • 为什么同样是正常退出,但是结果却不一样呢,这里我们要讲一下exit和_exit的区别。
  • 首先,exit是c库函数,而_exit是系统调用函数,exit封装了_exit。
  • 当我们调用exit 的时候,首先执行用户自定义的清理函数,再刷新缓冲区,关闭流等操作,最后才回去调用_exit。
  • 其中,还有一种退出方式就是return ,其实return还是将后面的参数传递给了exit。

进程替换

  • 我们创建子进程的最主要原因,就是想让子进程来代替父进程做一部分工作。而fork出来的子进程和父进程拥有相同的代码和数据,因此就需要对子进程的代码和数据进行替换,从而完成别的任务,这就是进程替换的基本思想。
  • 下面我们来看看6个进程替换的函数:
  • 这里写图片描述

如何更好记忆

  1. 首先,execve才是真正的系统调用接口,这些函数都是在它的基础上进行了封装.
  2. l表示参数使用列表,即可变参数参数列表注意要以NULL结尾,而v表示参数采用数组,需要用户自己定义。
  3. 带p表示自动搜索当前环境变量PATH,不带P就需要手动的来添加路径。
  4. 带e表示需要自己维护环境变量。
    自己实现一个shall
    综合了之前所学的知识,我们大概能明白shall是如何运行命令的,当我们用户输入一条命令时,shall获取这条命令,并且对它进行解析,创建一个子进程,然后用当前命令替换子进程,父进程等待子进程的退出。
  1 #include<stdio.h>
  2 #include<sys/wait.h>
  3 #include<stdlib.h>
  4 #include<string.h>
  5 #include<unistd.h>
  6 char *argv[8];  
  7 int argc=0;     
  8 void do_pause(char*buf)
  9 {              
 10     int i;  
 11     int status=0;
 12     for(argc=i=0;buf[i];i++)
 13     {    
 14         if(!isspace(buf[i])&&status==0)
 15         {   
 16             argv[argc++]=buf+i;
 17             status=1;
 18              
 19         }    
 20         else if(isspace(buf[i]))
 21         {   
 22             status=0;
 23             buf[i]=0;
 24         }   
 25     }       
 26     argv[argc]=NULL;
 27 }   
 28 void do_execute(void)
 29 {   
 30     pid_t pid=fork();
 31     switch(pid)
 32     {       
 33         case -1:
 34             perror("fork");
 35                 exit(EXIT_FAILURE);
 36             break;
 37         case 0:
 38             execvp(argv[0],argv);
 39             perror("execvp");
 40             exit(EXIT_FAILURE);
 41         default:
 42             {  
 43                 int st;
 44                 while(wait(&st)!=pid)
 45                     ;
 46             }
 47     }       
 48 }   
 49 int main()  
 50 {   
 51     char buf[1024]={};
 52     while(1)
 53     {       
 54         printf("myshell>>");
 55         scanf("%[^\n]%*c",buf);
 56         do_pause(buf);
 57         do_execute();
 58     
 59     }
 60 }

这里写图片描述

popen/system

  • FILE *popen(const char *command, const char *type);
  • 这个函数创建一个管道,然后通过fork或者inovke一个子进程,然后在子进程里面执行command的命令,然后把结果返回到标准I/O当中,由于管道只能单向同通信的原因,所以command只能从管道中读取r或者把结果w到管道里面。在调用完成后我们要调用fclose来回收创建的子进程,不然就可能产生僵尸进程。
    int system(const char *command);
  • 这个函数创建一个进程,然后进程替换去执行我们指定的command命令。然后shell阻塞直到该命令执行完成,再返回到调用函数,
  • 函数 process_create(pid_t* pid, void* func, void* arg), func回调函数就是子进程执行的入口函数, arg是传递给func回调函数的参数
    int process_create(int(*func)(),const char* file,char* argv[])
    {
    int ret = 0;
    pid_t pid = fork();
    if(pid == 0)
    {
    //child
    ret = func(file,argv);
    if(ret == -1)
    {
    printf("调用execvp失败\n");
    perror("func");
    _exit(-1);
    }
    }
    else
    {
    int st;
    pid_t ret = wait(&st);
    if(ret == -1)
    {
    perror("wait");
    exit(-1);
    }
    }
    return 0;
    }
    int main()
    {
    char* argv1[] = {"ls"};
    char* argv2[] = {"ls","-al","/etc/passwd",0};
    process_create(execvp,*argv1,argv2);
    return 0;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章