Linux进程理解与实践(三)进程终止函数和exec函数族的使用


进程的几种终止方式(Termination)

(1)正常退出
从main函数返回[return]
调用exit
调用_exit或者_Exit
最后一个线程从其启动处返回
从最后一个线程调用pthread_exit (最后两点见后面博客)
(2)异常退出
调用abort 产生SIGABOUT信号
由信号终止 Ctrl+C [SIGINT]
最后一个线程对取消请求做出响应
\
从图中可以看出,_exit 函数的作用是:直接使进程停止运行,清除其使用的内存空间,并清除其在内核的各种数据结构;exit 函数则在这些基础上做了一些小动作,在执行退出之前还加了若干道工序。exit() 函数与 _exit() 函数的最大区别在于exit()函数在调用exit 系统调用前要检查文件的打开情况,把文件缓冲区中的内容写回文件。也就是图中的“清理I/O缓冲”。另外注意_exit是一个系统调用,exit是一个c库函数。


int main()
{
    pid_t result;
    result=fork();
    if(result<0)
        ERR_EXIT("fork error");
    if(result==0)
    {
        printf("This is the _exit test.Child pid=%d\n",getpid());
        printf("Output the content in Child!");
        _exit(0);
    }
    else
    {
       printf("This is the exit test.Parent pid=%d\n",getpid());
       printf("Output the content in Parent!");
       exit(0);
    }
    return 0;
}


\
结果分析:
子进程中运行_exit(0)并未将This is the content in Child打印出来,而父进程中运行的exit(0)将This is the content in Parent打印出来了。说明,exit(0)会在终止进程前,将缓冲I/O内容清理掉,所以即使printf里面没有 \n也会被打印出来,而_exit(0)是直接终止进程,并未将缓冲I/O内容清理掉,所以不会被打印出来。
终止处理程序:atexit
按照ISO C的规定,一个进程可以登记多至32个函数,这些函数将由exit自动调用,我们称这些函数为终止处理函数,并调用atexit函数来登记这些函数


void exitHa1()
{
    printf("when exit,the num is one\n");
}
void exitHa2()
{
    printf("when exit,the num is two\n");
}
int main()
{    
    printf("In main ,pid=%d\n",getpid());
    atexit(exitHa1);
    atexit(exitHa2);
    return 0;
 
}
\


我们可以看出的是:先注册的后执行。
\
如果将return -0替换成abort()异常退出的话,那么程序运行的结果如上图。


exec函数族





exec替换进程印象
在进程的创建上,Unix采用了一个独特的方法,它将进程创建与加载一个新进程映象分离。这样的好处是有更多的余地对两种操作进行管理。
当我们创建了一个进程之后,通常将子进程替换成新的进程映象,这可以用exec系列的函数来进行。当然,exec系列的函数也可以将当前进程替换掉。
exec只是用磁盘上的一个新程序替换了当前进程的正文段, 数据段, 堆段和栈段.并没有创建新进程,所以进程的ID是不变的。
exec函数族:


int execve(const char *filename, char *const argv[],  
                  char *const envp[]);  
  
#include   
extern char **environ;  
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 envp[]);  
int execv(const char *path, char *const argv[]);  
int execvp(const char *file, char *const argv[]);  
int execvpe(const char *file, char *const argv[],  
            char *const envp[]);  


说明:
execl,execlp,execle(都带“l”, 代表list)的参数个数是可变的,参数以必须一个空指针结束。
execv和execvp的第二个参数是一个字符串数组(“v”代表“vector”,字符串数组必须以NULL结尾),新程序在启动时会把在argv数组中给定的参数传递到main。
名字最后一个字母是“p”的函数会搜索PATH环境变量去查找新程序的可执行文件。如果可执行文件不在PATH定义的路径上,就必须把包括子目录在内的绝对文件名做为一个参数传递给这些函数;
/*总结:l代表可变参数列表,与v互斥;v表示该函数取一个argv[]矢量;p代表在path环境变量中搜索file文件;e表示该函数取envp[]数组,而不使用当前环境*/
\


int main()
{
    printf("Test the execlp:\n");
    pid_t pid=fork();
    if(pid==0)
    {
        if(execlp("/bin/pwd","pwd",NULL)==-1)
            ERR_EXIT("execlp pwd error");
    }
    wait(NULL);
 
    pid=fork();
    if(pid==0)
    {
        if(execlp("/bin/ls","-l",NULL)==-1)
            ERR_EXIT("execlp ls -l error");
    }
    wait(NULL);
     
    printf("Test the execve:\n");
 
    char *argv_execve[]={"/bin/data","+%F",NULL};
    pid=fork();
    if(pid==0)
    {
        if(execve("/bin/date",argv_execve,NULL)==-1)
            ERR_EXIT("execve error");
    }
    wait(NULL);
    return 0;
 
}
\


另外,如果你理解execv, 那么execle和他的区别就是, 前者的调用参数是以数组形式给的,而后者则是以列表方式给,也就是execle(path, arg1, arg2, ..., envp), 并且提供了环境变量参数;


#include
#include
#include

int main()
{
    char *envp[]={"PATH=/tmp","USER=shan",NULL};
    if(fork()==0)
    {
        if(execle("/bin/dir","dir",NULL,envp)<0)
            perror("execle error!");
    }
    return 0;
}


System系统调用





system()函数调用“/bin/sh -c command”执行特定的命令,阻塞当前进程直到command命令执行完毕,system函数执行时,会调用fork、execve、waitpid等函数。
原型:
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
int system(const char *command);自己利用execl实现system:\
 

相关内容