Linux进程退出、等待和替换

程序退出的场景:

1.代码运行完毕,结果正确

2.代码运行完毕,结果不正确

3.代码异常终止


常见的进程退出方法

正常终止:(可通过echo $? 查看进程的退出码):

1.main函数的return

执行return n; 等同于执行exit(n);因为调用main的运行时函数会将main的返回值当做exit的参数

2.调用exit

1>调用退出处理程序(用户通过atexit或on_exit定义的清理函数)

2>刷新输出缓存(关闭所有打开的流, 所有缓存数据均被写入)

3>调用_exit

3._exit (n) : 不执行退出程序

void _exit(int status); status定义了进程的终止状态,父进程通过wait来获取该值

虽然status是int型,但是仅有低8位可以被父进程所用。所以_exit(-1)在终端下执行echo $?会发现返回255

异常退出:

ctrl + c

abort();

进程等待:

wait: pid_t wait(int *status);

一直阻塞,直到有一个子进程死亡,成为僵尸进程,回收掉僵尸进程,并且wait返回,pid_t wait(int *status),status获得子进程的死亡信息。

返回值: 成功返回被等待进城的pid,失败返回-1

参数: 输出型参数,获取子进程的退出状态,不关心则可以设置为NULL

wait中几个重要的函数:

WIFEXITED(status)返回真表示子进程正常退出

WEXITSTATUS(status) 得到子进程的退出码

WIFSIGNALED(status) 返回真, 被信号打断

WTERMSIG(status) 获得信号的信息

kill -num pid    给pid进程发送num信号,缺省为15

#include 
#include 
#include 
#include 

int main( void  )
{
     pid_t pid = fork();

      if (pid == -1) 
      {
          perror("fork"),
          exit(1);
      }
      if ( pid == 0)
      {
          int i;              
          for (i=0; i < 10; i++) 
            {                   
                printf("$");    
                fflush(stdout); 
                sleep(1);       
            }                   
             exit(10);          
      } 
      else 
      {
          int status;
          int ret = wait(&status);   //清理子进程
          if(ret > 0 && (status & 0x7F) == 0)
          {
              printf("child exit code : %d\n", (status>>8) & 0xFF);
          }
          else if(ret > 0)
          {
              printf("sig code : %d\n", status & 0x7F);
          }
      }   
}
Linux进程退出、等待和替换_第1张图片

waitpid: pid_t waitpid(pid_t pid, int *status, int options);

返回值:

1.当正常返回时,waitpid返回收集到的子进程的id。

2.如果设置options为WNOHANG,而调用中waitpid发现没有已退出的子进程可手机则返回0;

3.如果调用中出错,则返回-1,这时errno会被设置为相应的值来指示错误的所在。

参数:

pid:

pid > 0: 等待指定进程

pid == 0: 等待本进程组的任何一个子进程死亡

pid = -1: 等待本进程的任何一个子进程死亡

pid  < -1: 等待|pid|进程组的任何一个子进程死亡

status:

WIFEXITED(status) 返回真表示子进程正常退出

WEXITSTATUS(status)           非零,得到子进程的退出码

options:

WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予等待。若正常结束,则返回盖子进程的ID。

补充:

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并释放资源,获得子进程退出信息。

如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则可能阻塞。

如果不存在该子进程,则立即出错返回。

获取子进程status:

status为输出型参数,有操作系统填充。如果传递NULL,表示不关心子进程的退出状态信息。否则系统根据该参数,将子进程的退出信息反馈给父进程。

进程正常终止时,status 8-15位存放退出状态,当被信号所结束时,0-6位存放终止信号,第7位存放core dump标志

#include 
#include 
#include 
#include 

int main( void )
{
    pid_t pid;

    if ((pid=fork()) == -1) perror("fork"),exit(1);
    if ( pid == 0 ) {
        int i;
        for (i=0; i<3; i++) {
            printf("$");
            fflush(stdout);
            sleep(1);
        }
        exit(-1);
    } else {
        int status;
        pid_t p = waitpid(pid, &status, WNOHANG);		//非阻塞式等待
        if ( WIFEXITED(status) ) {
            printf("exit code=%u\n", WEXITSTATUS(status));
        }
        if ( WIFSIGNALED(status) ) {
            printf("exit sig=%u\n", WTERMSIG(status));
        }

        for ( ; ; ) {
            printf(".");
            fflush(stdout);
            sleep(1);
        }
    }
}
进程的替换:

用fork()创建子进程后执行的是和父进程相同的程序,子进程往往要调用一种exec函数以执行另一个程序,当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新进程的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

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[]);

函数解释:

1.这些函数如果调用成功,则加载新的程序从代码开始执行,不再返回。

2.如果调用出错则返回-1

3.所以exec函数只有出错的返回值而没有成功的返回值。
l(list): 表示参数使用列表

v(vector): 参数使用数组

p(path): 自动搜索环境变量PATH

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

Linux进程退出、等待和替换_第2张图片

事实上,只有execve是真正的系统调用,其他五个函数最终都调用execve。

Linux进程退出、等待和替换_第3张图片

execvp:

#include 
#include 
#include 

int main()
{
    printf("exec before:\n");
    char *const argv[] = {
        "ls",
        "-l",
        NULL
    };
   if( execvp("/bin/ls", argv) == -1)
   {
       perror("execvp\n");
   };
    printf("exec after:\n");

    return 0;
}
所有的exec族函数

#include 
#include 
#include 

int main()
{
  //  execl("/bin/ls", "ls", "-l", NULL);     //路径,argv[]
  //  execl("ls", "ls", "-l", NULL);    //err;
    //execlp("ls", "ls", "-l", NULL);             //自动检查路径,  argv[0], argv[1]-看做占位符
 //   char *argv[] = {"ls", "-l", NULL};	
 //   execv("/bin/ls", argv);			//带e的需要自己组装环境变量
    execvp("ls", argv);				//无需写全路径

    char *argv[] = {"myenv", NULL};
    char *envp[] = {"aa=bb", "cc=dd", "ee=ff", NULL};
    execve("./myenv", argv, envp);		//需自己组装环境变量
    perror("execl");

}

利用以上知识就可以实现一个简单的系统命令的调用,和简单的shell

#include 
#include 
#include 
#include 
#include 
#include 

int my_system(const char* str)
{
    if(NULL == str)
    {
        return 1;
    }
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    if(0 == pid)
    {
        execlp("/bin/sh", "sh", "-c", str, NULL);
        exit(127);
    }
    int status;
    while(-1 == waitpid(pid, &status, 0))  //获取等待码
    {
        if(errno == EINTR)      //信号打断
                {
                    continue;
                }
        return -1;
    }
    return WEXITSTATUS(status);      
}

int main()
{
    
    my_system("clear");
}

简易的shell:

思路:需要循环的进行,获取命令,解析命令,建立一个子进程(fork()),替换一个子进程(execvp()),父进程等待子进程退出(wait())。


#include 
#include 
#include 
#include 
#include 

void do_shell(int argc, char *argv[])
{
    pid_t pid = fork();
    if(pid == 0){
        if(execvp(argv[0], argv) == -1) //如果替换成功会自动退出,失败打印错误
        { 
            perror("execvp");           
            exit(1);            //调用失败退出
        }
    }else if(pid == -1)
    {
        perror("fork");     //子进程创建失败
        exit(1);
    }
    else{
        wait(NULL);         //清理子进程
    }
}

void do_parse(char *buf){
    int argc = 0;
    char *argv[8];
    int i = 0;
    int status = 0;
    for( i = 0; buf[i] != 0; ++i)
    {
        if(!isspace(buf[i]) && status == 0) //是字符
        {
            status = 1;
            argv[argc++] = &buf[i];
            
        }else if(isspace(buf[i]))
        {
            status = 0;
            buf[i] = 0;
        }
    }
    argv[argc] = NULL;
    
    do_shell(argc, argv);
}

int main()
{
    char buf[1024] = {};
    while(1)
    {
        memset(buf, 0x00, sizeof buf);
        printf("czf-shell:>");
        scanf("%[^\n]%*c", buf);
        if(strncmp(buf, "exit", 4) == 0){
            exit(0);
        }
       do_parse(buf);
    
    }
}







你可能感兴趣的:(Linux)