进程控制

        在了解了进程的相关概念之后,接下来了解一下进程控制。

一. 进程的创建

        以下是两种创建子进程的方法:

1. 通过系统调用fork

        该系统调用的原型为:

pid_t fork(void);//需引用的头文件为<unistd.h>

        fork的作用是通过正在运行的父进程来创建子进程。fokk在执行过程中:

(1)系统分配新的内存块和数据结构给子进程,其中子进程与父进程对代码进行共享,数据进行写实拷贝。

(2)将父进程的部分数据结构内容拷贝给子进程

(3)使子进程添加到系统进程列表中等待调度。

        返回值为:

(1)如果fork调用失败,即子进程创建失败,返回-1;

(2)如果子进程创建成功,给父进程返回子进程的pid,给子进程返回0。

        fork之后一般用if-else语句分流来使父,子进程执行不同的代码段或程序。通过下面的代码来演示一下fork:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
    pid_t pid = fork();
    if(pid == -1) 
    {   
        perror("fork");
        exit(1);
    }   
    else if(pid > 0)//父进程
    {   
        printf("father:pid = %d,ppid = %d\n",getpid(),getppid());
        sleep(1);                                                                                                                                                       
    }   
    else//子进程
    {   
        printf("child:pid = %d,ppid = %d\n",getpid(),getppid());
    }   
    return 0;
}

        运行结果如下:

[admin@localhost control.c]$ gcc fork.c 
[admin@localhost control.c]$ ./a.out 
father:pid = 12943,ppid = 2641
child:pid = 12944,ppid = 12943

        在该例中,子进程创建好后,先执行父进程,在执行子进程。

        注意:fork之后,父,子进程那个先运行是不一定的,由调度器决定。

2. 通过系统调用vfork

        vfork的作用和返回值与fork类似。但是,有以下几点需要区别:

(1)vfork创建的子进程与父进程共享虚拟地址空间,所以代码和数据均共享

(2)vfork之后子进程先运行,在子进程调用exec或exit之后,父进程才可能运行。

        vfork演示:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
    int g_val = 100;//设置全局变量
    pid_t pid = vfork();
    if(pid == -1) 
    {   
        perror("vfork");
        exit(1);
    }   
    if(pid > 0)//父进程
    {   
        printf("father:pid = %d,g_val = %d,ret= %d\n",getpid(),g_val,pid);
        printf("%p\n",&g_val);
    }   
    else//子进程
    {   
        g_val = 50; 
        printf("child:pid %d,g_val = %d,ret = %d\n",getpid(),g_val,pid);
        printf("%p\n",&g_val);                                                                                                                                          
        exit(0);
    }   
    return 0;
}

        该程序运行起来之后,先睡眠了5s,在输出下面的结果:

child:pid 14150,g_val = 50,ret = 0
0xbff0e288
father:pid = 14149,g_val = 50,ret= 14150
0xbff0e288

        可以观察到:

(1)子进程在睡眠期间,父进程并没有输出,等子进程exit退出之后,父进程才输出。说明子进程先运行。

(2)子进程将全局变量g_val改变之后,父进程输出的也是改变之后的值,同时两进程中分别输出全局变量的地址,发现是相同的,说明父,子进程指向同一片虚拟内存空间,所以它们共享代码和数据。

        将上述代码中子进程的exit去掉,运行结果如下:

[admin@localhost control.c]$ ./a.out 
child:pid 13041,ppid = 13040,g_val = 50
father:pid = 13040,ppid = 2641,g_val = 50
child:pid 13042,ppid = 13040,g_val = 50
father:pid = 13040,ppid = 2641,g_val = 50
child:pid 13043,ppid = 13040,g_val = 50
father:pid = 13040,ppid = 2641,g_val = 50
child:pid 13044,ppid = 13040,g_val = 50
father:pid = 13040,ppid = 2641,g_val = 50
......
        此时,子进程会return返回,而main()函数return后,通常会调用exit()或相似的函数,然后父进程执行。此时会再次调用main(),父进程一直创建子进程,于是进入一个无限循环的结果。

二,进程终止

        一个进程结束方式有以下几种:

(1)正常退出,结果正确

(2)正常退出,结果错误

(3)异常退出。

1. 进程正常退出方式

(1)通过在main函数中调用return(只有main函数中调用return,进程才会退出,若在其他函数中调用,进程不会退出)

(2)在程序的任意位置调用exit(无论在main函数中,还是在其他函数中调用exit进程都会退出);

(3)在程序的任意位置调用_exit(无论在main函数中,还是在其他函数中调用exit进程都会退出);

        程序正常退出后,运行的结果如何,是否正确。此时可以通过:echo $?命令来查看进程的退出码。

        注意:echo只会保存最近一个进程的退出码

(1)如果退出码为0,表示结果正确;

(2)如果退出码非0,表示结果错误。此时可根据退出码来打印出相应的错误码描述,进而得知错误原因。例如:

[admin@localhost control.c]$ ls 378
ls: cannot access 378: No such file or directory//错误描述
[admin@localhost control.c]$ echo $?
2//退出码

        在上述情况下直接给出了错误描述。而在某些情况下,只会给出退出码,此时如何将退出码转换为错误描述呢?系统中有132条错误描述,每个错误描述对应一个数字。所以可以根据退出码即数字调用strerror函数找到对应的错误描述。

exit函数

        函数原型

void exit(int status);//头文件<unistd>

        在系统调用该函数后,首先会执行用户定义的清理函数,清空缓冲区,关闭流等。再使程序退出。

_exit函数

        函数原型

void _exit(int status);//头文件<unistd>

        该函数与exit的区别是在调用它之后,直接是程序退出。不做前期的处理。

例如:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
    printf("hello world");
    sleep(3);
    exit(1);//调用exit函数                                                                     
}

        程序运行后,先睡眠了3s,在输出结果:

[admin@localhost control.c]$ ./a.out 
hello world[admin@localhost control.c]$ 

        因为printf会将“hello wold”先放入输出缓冲区中,在执行之后的语句,当执行到exit,exit会先将缓冲区中的内容输出,再结束进程。

        将程序中的exit替换为_exit之后,程序开始运行后,先睡眠了3s,然后直接退出。

[admin@localhost control.c]$ ./a.out 
[admin@localhost control.c]$ 

        因为printf会将“hello wold”先放入输出缓冲区中,在执行之后的语句,当执行到_exit,_exit不会先将缓冲区中的内容输出,而是直接结束进程。

注意:exit和_exit参数中的status虽然是整数,但只有低8位可以被父进程使用。所以status中的低8位是多少,其退出码即为多少,所以调用exit或_exit后退出码的取值范围是0~255。

return

        在main函数中执行return n与执行exit(n)同效。

2. 异常终止

        异常终止是程序还没运行完,就结束了。它是操作系统向进程发送一个信号,是进程强制终止。

三. 进程等待

        父进程创建了子进程,子进程退出后,若父进程继续做自己的事,对子进程不管不问,此时,

(1)子进程就会变为僵尸进程,僵尸进程用kill是杀不掉的,此时就会造成内存泄漏。

(2)同时父进程交给子进程的任务完成的如何,即子进程的退出状态如何,这些都是未知的。

        所以,父进程需要通过进程等待的方式,等待子进程完成,获取其退出信息并释放资源。

进程等待的方法

1. 通过系统调用wait

        wait的函数原型如下:

pid_t wait(int* status);//头文件<sys/types.h>和<sys/wait.h>

        该系统调用的作用是等待子进程退出。将子进程的退出信息保存在status中,然后系统释放子进程相关资源。其中:

(1)返回值:如果wait调用失败,返回-1;如果调用成功,返回等待的子进程的pid。

(2)参数:

        如果参数为空,表示不关心退出信息。

        如果参数不为空,操作系统会将退出信息保存在status中。status被当做位图来看待,其中:

        1) status的低7位存放信号信息。即子进程是否正常退出。如果是正常退出,则这几位为0;如果是异常终止,这几位存放的就是引起子进程异常终止的信号值。

       2) status的次低8位存放的是子进程的退出码。如果子进程正常终止,那么子进程执行的结果是正确还是错误,可以查看退出码。如果退出码为0,则表示结果正确,若退出码不为零,则结果错误,可以通过退出码来查看相应的错误信息。如果子进程异常终止,则不关心退出码。

        wait代码演示:

#include<stdio.h>                                                                                                                                                       
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
    pid_t ret = fork();//创建子进程
    if(ret == -1) 
    {   
        perror("fork");
        exit(1);
    }   
    else if(ret > 0)//父进程
    {   
        printf("father:pid = %d,ret = %d\n",getpid(),ret);
        int st; 
        printf("father is waiting\n");
        int ret1 = wait(&st);//父进程等待子进程退出
        if(ret1 == -1)//如果wait调用失败
        {   
            printf("wait failed,wait return %d\n",ret1);
        }
        else//wait调用成功
        {   
            printf("wait success,wait return %d\n",ret1);
            if((st & 0x7f) == 0)//,如果st的低7位等于0,即子进程正常退出
            {
                printf("child exit normal,sig code:%d,exit code:%d\n",st & 0x7f,(st >> 8) & 0xff);//打印退出码
            }
            else//如果低7位不为0,即子进程异常退出
            {
                printf("child exit unnormal,sig code:%d\n",st & 0x7f);//打印引起子进程异常退出的信号值
            }
        }
        printf("father quit\n");//子进程退出之后,父进程才退出
    }   
  else//子进程在运行
    {
        printf("child is running\n");
        printf("child:pid = %d,ret = %d\n",getpid(),getppid(),ret);
        int count = 5;
        while(count--)
        {
            printf("count = %d\n",count);
            sleep(2);
        }
        printf("child quit\n");
        exit(1);//令子进程的退出码为1
    }
return 0;
}
        运行结果如下:
father:pid = 15118,ret = 15119
father is waiting
child is running
child:pid = 15119,ret = 15118
count = 4
count = 3
count = 2
count = 1
count = 0
child quit
wait success,wait return 15119
child exit normal,sig code:0,exit code:1
father quit

        此时,信号值为0,所以子进程正常退出。退出码为1。

        当该程序运行起来后,在另一终端用kill命令杀死子进程:

[admin@localhost ~]$ kill -9 15140

        运行结果如下:

[admin@localhost control.c]$ ./a.out 
father:pid = 15139,ret = 15140
father is waiting
child is running
child:pid = 15140,ret = 15139
count = 4
count = 3
count = 2
count = 1
count = 0
wait success,wait return 15140
child exit unnormal,sig code:9
father quit
        此时,子进程异常终止,信号值为9。

2. 通过系统调用waitpid

        函数原型:

  pid_t waitpid(pid_t pid, int *status, int options);//头文件<sys/types.h>和<sys/wait.h>

        waitpid的作用与wait相同。其中

(1)参数pid:父进程指定等待的子进程的pid。如果父进程等待任意一个子进程,该参数为-1。

(2)参数status:与wait中的status作用和用法相同。另外:

        参数status中的信号信息和退出码还可由以下方式获得:

        1)WIFEXITEND(status):判断子进程是否正常终止,如果正常终止,则返回非零值,如果异常终止,则返回0

        2)WEXITSTATUS(status):显示子进程的要退出码

        上述方法也可用于wait函数中的status。

(3)参数options:表示父进程以何种方式等待。

        1)以阻塞式等待,该参数为0。此时父进程除了等待,不能做别的事情(wait默认以阻塞式等待,故与wait同效)。

        2)如果以非阻塞式等待,该参数为WNOHANG。

            若子进程还没退出,则返回值为0。此时,父进程可以做其他的事,但父进程要一直以轮询的方式查看子进程是否结束。

            若子进程退出了,返回值为所等待进程的pid。

(4)返回值:

        1)waitpid调用失败,返回值为-1;

        2)waitpid调用成功。

            若父进程以阻塞式等待,则子进程退出后,返回所等待的子进程的pid;

            若父进程以非阻塞式等待,若子进程还没退出,则返回值为0。若子进程退出了,返回值为所等待进程的pid。

代码演示:

        (1)父进程阻塞式等待(与wait作用相同)

#include<stdio.h>                                                                                                                                                       
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid > 0)//父进程
    {   
        printf("father is running\n");
        printf("father:pid = %d\n",getpid());
        printf("father is waiting\n");
        int st;
        int ret = waitpid(-1,&st,0);//阻塞式等待任意进程,注意与wait的用法做区别
        printf("wait end\n");
        if(ret == -1)
        {
            printf("wait failed\n");
        }
        else
        {
            printf("wait success!,wait return %d\n",ret);
            if(WIFEXITED(st))//可替换为:if(!(st&0x7f))
            {
                printf("child exit normal! exit code of child is %d\n",WEXITSTATUS(st));
            }
            else
            {
                printf("childe exit inormal! sig code of child is %d\n"st&0x7f);
            }
        }
    }
    else//子进程
    {
        printf("child is running\n");
        printf("child:pid = %d\n",getpid());
        int count = 5;
        while(count--)        
  {
            printf("count = %d\n",count);
            sleep(3);
        }
        exit(20);
    }
    return 0;
}

        上述程序与wait演示代码中的运行结果相同。

        (2)父进程非阻塞式等待

#include<stdio.h>                                                                                                                                                       
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
    pid_t pid = fork();
    if(pid == -1)
    {
        printf("%s fork error\n",__FUNCTION__);
        exit(1);
    }
    else if(pid > 0)//父进程
    {
        printf("father is running\n");
        printf("father:pid = %d\n",getpid());
        printf("father is waiting\n");
        int ret;
        int st;
        do
        {
            ret = waitpid(-1,&st,WNOHANG);//非阻塞式等待
            if(ret == 0)//子进程没有结束,父进程就做其他事情
            {
                printf("child is running:\n");//此时,父进程所做的其他事情就是一直输出该语句
                sleep(1);
            }
        }while(ret == 0);
        printf("wait end\n");
        if(WIFEXITED(st))
        {
            printf("child exit normal,the exit code of child is %d\n",WEXITSTATUS(st));
        }
        else
        {
            printf("child exit inormal,the exit sig of child is %d\n",st&0x7f);
        }
  }
        printf("father quit\n");
    }
    else//子进程
    {
        printf("child:pid = %d\n",getpid());
        sleep(5);
        printf("child quit\n");
        exit(50);
    }
 return 0;                                                                                                                                                           
}

        运行结果:

[admin@localhost control.c]$ ./a.out 
father is running
father:pid = 15528
father is waiting
child is running:
child:pid = 15529
child is running:
child is running:
child is running:
child is running:
child quit
wait end
child exit normal,the exit code of child is 50
father quit

        此时,子进程正常退出,子进程的退出码是50。

        当该程序运行起来后,在另一终端用kill命令杀死子进程:

admin@localhost ~]$ kill -9 15553

        运行结果如下:

[admin@localhost control.c]$ ./a.out 
father is running
father:pid = 15552
father is waiting
child is running:
child:pid = 15553
child is running:
child is running:
child is running:
wait end
child exit inormal,the exit sig of child is 9
father quit

        此时,子进程异常退出,退出信号为9。

四. 进程的程序替换

        上述例子中父进程fork创建子进程后,子进程去执行与父进程不同的代码段。那如果子进程要执行不同的程序呢?这时,子进程通过调用exec函数去执行另一程序。子进程在执行另一程序时,只是将另一程序的代码和数据拷贝到属于子进程的物理内存中,然后从新程序的启动历程开始执行。因为并没有创建新的进程,所以exec前后子进程的pid并没有改变。

        以下是exec函数的一种表示,可以用man exec来查看:

int execl(const char* path,const char* arg,...);
int execlp(const char* file,const char* arg,...);
int execle(const char* path,const char* arg,...,char* const env[]);
int execv(const char* path,char* const argv[]);
int execvp(const char* file,char* const argv[]);
int execve(const char* path,char* const argv[],char* const env[]);//头文件均为<unistd>

        在调用这些函数后子进程就去执行执行的程序,如果调用成功,则不再返回,如果调用失败,则返回-1。其中:这些函数第一个参数表示的均为子进程要去执行什么文件或命令,第二个参数表示以何种方式执行。         

         根据函数名区别函数的不同:

(1)l:表示执行方式以可变参数列表的形式呈现

(2)v:表示执行方式以指针数组的形式呈现

(3)p:表示子进程执行的程序要根据系统自带的环境变量PATH中查找路径;

(4)e:表示要自己维护环境变量

        代码演示:

#include<stdio.h>                                                                                                                                                       
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
    pid_t pid = fork();
    if(pid == -1)
    {
        printf("%s fork error\n",__FUNCTION__);
        exit(1);
    }
    else if(pid > 0)//父进程
    {
        int st;
        printf("father is waiting\n");
        int re = wait(&st);//父进程等待
        if(re == -1)//等待失败
        {
            printf("father:wait failed\n");
            exit(1);
        }
        printf("father quit\n");//等待成功
    }
    else//子进程
    {
        char *const arg[]={"ls","-a","--color=auto",NULL,"-l"};
        char *const env[]={"PATH=/bin",NULL};

//      int ret = execl("/bin/ls","ls","-a","-l","--color=auto",NULL);//exec函数的几种调用形式
//      int ret = execlp("ls","ls","-a","-l","--color=auto",NULL);
//      int ret = execle("/bin/ls","ls","-a","-l","--color=auto",NULL,env);  
        int ret = execv("/bin/ls",arg);
//      int ret = execvp("ls",arg);
//      int ret = execve("/bin/ls",arg,env);
        if(ret == -1)//exec函数调用失败
        {
            printf("execl error\n");
        }
        printf("child quit\n");//如果调用失败,就会输出该语句,否则不输出
        exit(1);
    }
    return 0;
}

        注意:可变参数列表和指针数组均以NULL结尾

        运行结果:

[admin@localhost control.c]$ ./a.out 
father is waiting
.  ..  a.out  echo.c  exec.c  exit.c  fork.c  myshell.c  t.c  test.c  vfork.c  wait.c  waitpid.c
father quit

        上述结果并没有输出“child quit”语句,说明子进程在调用exec函数直接去执行指定的程序,并没有返回。

        将上述的“/bin/ls”改为"/bin/l",结果如下:

[admin@localhost control.c]$ ./a.out 
father is waiting
execl error
child quit
father quit

        此时,因为子进程并没有找到执行的程序而是exec函数调用失败,从而返回到exec调用处继续执行之后的语句,所以输出了"child quit"。

        上述即说明了如果exec调用成功,没有返回值,调用失败才会返回。

五. 实现简易的shell

        在linux中输入一条命令,shell就会执行相应的命令,并输出相关的结果。这里,利用进程的程序替换来实现建议的shell。

        例如要执行"ls -l -a"命令。要使子进程执行这些命令,可以调用exec函数,上述有六种exec函数,具体要选用哪个呢?

(1)因为在执行exec之前并不知道要执行的程序和对应的执行方式。所以不能将其一一列举出来,故不能用可变参数列表,而要用指针数组的形式实现。提前定义一个指针数组,将输入的命令以空格为分隔符,分隔各字符串,作为执行程序的方式选项。(2)而该数组的第一个字符串如ls即为要执行的程序名,但不知道其路径,所以要在环境变量path中查找。

        所以要用execvp函数。

        接下来来一步步实现shell:

(1)提取命令

(2)创建子进程,使子进程执行exec函数

(3)一直循环执行上述过程

        代码如下:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>

char* arg[20] = {}; 
int count = 0;
//命令解析函数
void do_parse(char* buf)
{
    int i = 0;                                                                                                                                                          
    int status = 0;//定义状态变量,0表示为空格状态,1表示非空格状态
    for(count = i = 0;buf[i] != '\0';i++)
    {   
        if((!isspace(buf[i])) && (status == 0))//当遇到非空格且状态原状态为0,及表示有空格进入到非空个时
        {
            arg[count++] = buf + i;//将字符串放入arg中
            status = 1;//此时状态变为非空格状态1
        }   
        else if(isspace(buf[i]))//如果遇到空格
        {
            buf[i] = 0;//使空格处为0,作为字符串的结束符
            status = 0; //此时,状态变为空格状态0
        }   
    }   
    arg[count] = NULL;//提取完命令中的各字符后,使最后一个为NULL
}

//创建子进程
void do_fork()
{

    pid_t pid = fork();
    if(pid == -1) 
    {   
        perror("fork error");
        exit(1);
 }
    else if(pid == 0)//子进程
    {   
        int ret = execvp(arg[0],arg);//使子进程执行exec函数,arg[0]表示要执行的命令,arg表示以何种方式执行
        if(ret == -1)
        {   
            printf("execv error\n");
            exit(1);
        }
    }
    else//父进程
    {   
        int st;
        int ret1 = wait(&st);//父进程等待
        if(ret1 == -1)
        {   
            printf("wait failed\n");
            exit(1);
        }
    
    }
}
int main()
{
    char buf[1024] = {};
    while(1)
    {
 printf("myshell# ");
        fgets(buf,sizeof(buf),stdin);//获取命令
        do_parse(buf);//解析命令
        do_fork();//创建子进程
    }
    return 0;
}

        运行结果:

myshell# ls
a.out  echo.c  exec.c  exit.c  fork.c  myshell.c  t.c  test.c  vfork.c	wait.c	waitpid.c
myshell# pwd             
/home/admin/bit_code/Linux/control.c
myshell# 

        该shell程序只能支持最基本的命令,在管道,重定向等方面的执行还有待改进。

六. 封装fork/wait等函数

        封装后的函数是这样的:

void process_create(pid_t* pid, void (*func)(void*), void* buf);

        在该函数中,通过fork创建一个子进程,使子进程去执行回调函数func,buf为回调函数的参数。其中参数pid是输出型参数,用于保存子进程的的pid。父进程wait去等待子进程退出。

        假设buf是字符串,关于字符串可进行很多不用的操作,所以为方便起见,将关于该字符串的操作封装为回调函数。当关于该字符串的操作改变时,执行修改相应的回调函数即可,而不需要修改封装函数process_create,这样可以提高代码的维护性。  

        下面,通过两个例子,来说明该封装函数(关于fork,wait,exec等函数的用法在上文已说明,这里就不细说了):

(1)创建子进程,使子进程调用func函数将字符串的内容输出

int main()                                                                                                                                                              
{
    pid_t pid = 0;//定义输出型变量,用于接收子进程的pid
    char buf[100] = "hello world";//子进程要执行的回调函数的参数,在解析字符串时会改变字符串的内容,所以定义为数组的形式
    process_create(&pid,func,buf);//创建子进程,使子进程调用func函数将字符串buf输出
    printf("child:pid = %d\n",pid);
    return 0;
}

        所以在process_create中用fork创建一个子进程,使子进程去执行回调函数func,父进程通过wait等待子进程退出:

//该函数用于创建一个子进程,使子进程去执行回调函数func,buf为回调函数的参数
void process_create(pid_t* pid, void (*func)(void*), void* buf)
{
    pid_t ret1 = fork();//创建子进程
    if(ret1 == -1)//子进程创建失败
    {
        perror("fork");
        exit(1);
    }
    else if(ret1 > 0)//父进程
    {
        *pid = ret1;//*pid接受子进程的pid
        int status;
        int ret = wait(&status);//父进程等待
        if(ret == -1)//等待失败
        {
            perror("wait");
            exit(1);
        }
        else//等待成功
        {
            if(WIFEXITED(status))//子进程正常终止
            {
                printf("exit code %d\n",WEXITSTATUS(status));
            }
            else//子进程异常终止
            {
                printf("sig code %d\n",status & 0x7f);
            }
        }
    }
    else//子进程
    {
        (*func)(buf);//使创建好后的子进程去执行回调函数
        exit(0);//执行结束后,子进程终止
    }
}

        而在回调函数中func中,子进程要输出其参数字符串:

func为子进程要执行的回调函数,参数为buf                                                                                                                                 
void func(void* buf)
{
    printf("%s\n",(char*)buf);//使子进程输出参数字符串
}

        运行结果如下:

[admin@localhost control.c]$ gcc process_create.c 
[admin@localhost control.c]$ ./a.out 
hello world
exit code 0//子进程的退出码
child:pid = 21156//子进程的pid

(2)创建子进程,使子进程调用func函数去执行字符串所表示的shell命令

        例如,字符串为“ls -a”,使子进程去执行该命令。

        只需在main函数中将字符串buf的内容改为:

 char buf[100] = "ls -a";

        并将回调函数改为:

//func为子进程要执行的回调函数,参数为buf                                                                                                                               
void func(void* buf)
{
    char* buf1 = (char*)buf;//类型强转
    do_parse(buf1);//解析参数字符串
    int r = execvp(arg[0],arg);//子进程调用execvp函数去执行其他的程序
    if(r == -1)//execvp调用失败
    {   
        perror("execvp");
        exit(1);
    }  

        其中,func函数中的do_parse函数即为上述“简易shell函数”中的命令解析函数,arg,count也为“简易shell函数”中定义的全局变量。

        运行结果如下:

[admin@localhost control.c]$ gcc process_create.c 
[admin@localhost control.c]$ ./a.out 
.  ..  a.out  echo.c  exec.c  exit.c  fork.c  myshell.c  popen.c  process_create.c  system.c  test.c  vfork.c  wait.c  waitpid.c
exit code 0//子进程的退出码
child:pid = 21180//子进程的PID
        所以,之后再使子进程执行不同的字符串或操作时,只需改变传入的参数字符串和回调函数即可。而不必再封装函数中做任何修改。

七. popen/pclose函数

        函数原型:

FILE* popen(const char* command,const char* type);
int pclode(FILE* stream);//头文件均为<stdio.h>

1. popen函数

(1)popen函数通过fork创建一个子进程,然后在子进程中调用exec函数执行参数command表示的指令。调用popen的为父进程,由popen启动的进程为子进程。

(2)同时,popen会创建一个管道,用连接子进程的标准输入和标准输出设备。然后返回一个文件指针。

(3)参数type有两种:

"r":读,根据文件指针从管道读内容。子进程将命令执行的结果放入标准输出,父进程通过管道读取内容。

"w":写,根据文件指针向管道写内容,此时,父进程通过管道向子进程的标准输入设备中写内容,然后子进程去执行命令。

(4)返回值:如果popen函数调用成功,返回一个标准的I/O文件指针,通过文件指针来对管道的内容进行读写。如果失败,返回NULL

2. pclose函数

        popen函数在执行过程中,不等待shell命令执行完成就返回了,所以子进程的退出状态就有pclose来获取。所以pclose的作用是关闭管道。成功返回子进程的终止状态,失败返回-1。

        代码演示1:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
    FILE* fp = popen("ls -a","r");//以读方式从管道中读取内容                                                                                                                                  
    if(fp == NULL)
    {
        perror("popen");
        exit(1);
    }
    char buf[4096];
    fread(buf,1,sizeof(buf),fp);//通过文件指针fp将管道中的内容写入buf中
    printf("%s\n",buf);
    pclose(fp);
    return 0;
}

        运行结果:

[admin@localhost control.c]$ gcc popen.c 
[admin@localhost control.c]$ ./a.out 
.
..
a.out
echo.c
exec.c
exit.c
fork.c
myshell.c
popen.c

        代码演示2:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
    FILE* fp = popen("cat","w");//以写方式打开管道
    if(fp == NULL)
    {   
        perror("popen");
        exit(1);
    }   
    fwrite("hello world\n",1,strlen("hello world"),fp);//向管道中写入字符串“hello world”,在由子进程执行cat输出到屏幕上
    pclose(fp);                                                                                                                       
    return 0;
}

        运行结果:

[admin@localhost control.c]$ gcc popen.c 
[admin@localhost control.c]$ ./a.out 
hello world

7. system函数

        函数原型:

int system(const char* command);//头文件<stdlib.h>

            该函数的功能也是执行shell命令,其中,在执行过程中:

(1)会fork一个子进程

(2)在子进程中调用exec函数去执行command命令,同时父进程调用wait等待子进程调用结束

(3)返回值:

        若fork失败,返回-1

        若exec执行失败,返回127

        若exec执行成功,即command命令执行成功,返回command命令的退出码

        如果command为NULL,返回非零值。

(4)在system执行过程中,信号SIGCHLD,SIGINT,SIGQUIT是被忽略的。

注意:system执行时,父进程必须等待子进程退出。

          popen执行时,未等待子进程完成就退出,子进程的退出状态通过pclose来获取,所以,popen和pclose要成对出现。

        代码演示:

#include<stdlib.h>
#include<stdio.h>
int main()
{
    system("ls");                                                                 
    return 0;
}

        运行结果:

[admin@localhost control.c]$ gcc system.c 
[admin@localhost control.c]$ ./a.out 
a.out	exec.c	fork.c	   popen.c	     system.c  vfork.c	waitpid.c
echo.c	exit.c	myshell.c  process_create.c  test.c    wait.c
注意:popen本身是不阻塞的,要通过标准io读取是它阻塞。而system本身是阻塞的。

























  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值