Linux进程控制

1. fork()

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程

#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1

1.1那么fork创建子进程时,操作系统都做了什么呢?

1.复制父进程: 操作系统会创建一个新的子进程,该子进程是父进程的一个副本。子进程将会继承父进程的代码、数据、堆栈、文件描述符等信息。

2.分配进程ID(PID): 操作系统会为新的子进程分配一个唯一的进程ID(PID)。父进程和子进程都有不同的PID。

3.复制文件描述符表: 子进程会复制父进程的文件描述符表。这意味着子进程可以访问与父进程相同的打开文件、网络连接等资源。

4.复制内存映像: 子进程会复制父进程的内存映像,包括代码段、数据段和堆栈。这样,子进程和父进程可以开始在不同的内存空间中执行。

5.创建唯一的资源: 操作系统会为子进程创建一些唯一的资源,如计时器、信号处理等。

6.设置返回值: 在父进程和子进程中,fork 函数会返回不同的值。在父进程中,它返回子进程的PID。在子进程中,它返回0,表示这是子进程。

7.开始执行子进程: 子进程从 fork 函数调用的位置开始执行。这意味着子进程会执行与父进程相同的代码。
在这里插入图片描述

1.2 父子进程和CPU中的EIP(指令指针)之间存在一定的关系

当一个程序(进程)在执行时,CPU会通过EIP来跟踪下一条要执行的指令的内存地址。当遇到函数调用、分支语句或系统调用等情况时,CPU会根据程序的逻辑跳转到相应的地址执行。

在创建子进程时,通过fork()系统调用,操作系统会复制父进程的代码段、数据段和堆栈等信息给子进程。这意味着子进程会拥有与父进程相同的代码和数据。

在fork()调用之后,父进程和子进程会在不同的内存空间中独立执行。它们各自拥有自己的EIP,用于跟踪各自的执行状态。父进程和子进程的EIP会根据各自的代码逻辑独立地进行跳转和执行。

因此,父进程和子进程的EIP是相互独立的,它们在执行过程中不会相互影响。每个进程都有自己的EIP,用于指示下一条要执行的指令的地址。所以这也就是为什么子进程只会执行fork函数之后位置的代码。

1.3 fork的常规用法有哪些?

1.创建子进程:最常见的用法是使用fork()创建一个子进程。父进程调用fork()后,会创建一个与父进程几乎完全相同的子进程。子进程从fork()调用的位置开始执行,而父进程继续执行后续的代码。

2.并行处理:通过fork()可以实现并行处理任务。父进程可以将任务分配给多个子进程,每个子进程独立执行任务,从而实现并行处理,提高程序的执行效率。

3.进程间通信:通过fork()创建的子进程可以用于进程间通信。父进程和子进程可以通过管道、共享内存、消息队列等机制进行通信,实现数据的交换和共享。

4.守护进程:守护进程是在后台运行的长期运行的进程,通常通过fork()创建。父进程可以通过fork()创建一个子进程,并在子进程中执行守护进程的任务,而父进程则可以继续执行其他任务或退出。

5.多进程编程:fork()可以用于多进程编程,例如使用多个子进程同时处理不同的任务,或者使用子进程执行特定的功能,从而实现更复杂的程序逻辑。

这些是fork()的一些常规用法,但并不限于此。fork()是进程创建和管理的基础,可以根据具体的需求和场景进行灵活的应用。

1.4 fork调用失败的原因有哪些?

fork()调用可能会失败,导致返回-1。以下是一些可能导致fork()调用失败的原因:

1.系统资源不足:当系统中的进程数量已经达到了操作系统的限制时,fork()调用可能会失败。这可能是由于系统内存不足、进程数量达到上限或者其他资源限制导致的。

2.进程数量限制:操作系统可能对每个用户或每个进程组设置了最大进程数量的限制。当达到这个限制时,fork()调用可能会失败。

3.虚拟内存不足:当系统的虚拟内存空间不足以容纳新的进程时,fork()调用可能会失败。

4.权限不足:如果当前进程没有足够的权限来创建新的进程,例如缺少适当的权限或者超过了进程数量限制,fork()调用也会失败。

5.系统错误:其他系统级错误,如内核错误或其他底层问题,也可能导致fork()调用失败。

在fork()调用失败时,通常会使用perror()函数打印错误信息,并根据具体的错误原因采取适当的处理措施。

fork函数返回值
子进程返回0,
父进程返回的是子进程的pid

1.5.写时拷贝

写时拷贝(Copy-on-Write,COW)是一种内存管理技术,用于在创建子进程时延迟复制父进程的内存内容。在写时拷贝中,当父进程创建子进程时,子进程会与父进程共享相同的物理内存页。

在写时拷贝的情况下,当父进程或子进程尝试修改共享的内存页时,操作系统会执行实际的内存复制操作。这样,父进程和子进程就会拥有各自的独立内存副本,而不会相互干扰。

写时拷贝的主要优势在于节省内存和提高性能。在创建子进程时,不需要立即复制整个父进程的内存空间,而是共享相同的物理内存页。这样可以减少内存的使用量,并且在父进程和子进程之间切换时,不需要进行大量的内存复制操作,提高了性能。

总结来说,写时拷贝是一种延迟复制的技术,用于在创建子进程时共享父进程的内存,只有在需要修改共享内存时才进行实际的复制操作,以提高内存利用率和性能。
在这里插入图片描述

2.进程终止

2.1进程常见退出方法

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

  1. 从main返回
  2. 调用exit
  3. _exit

异常退出:
ctrl + c,信号终止

echo $?命令用于显示上一个执行的命令的退出状态码(或称为返回值)。在Unix/Linux系统中,每个命令在执行完毕后都会返回一个退出状态码,用于表示命令执行的结果。

$?是一个特殊的变量,用于存储上一个命令的退出状态码。通过在命令行中执行echo $?,可以打印出上一个命令的退出状态码。

退出状态码通常是一个整数值,其中0表示命令成功执行,而非零值表示命令执行失败或出现错误。具体的退出状态码的含义可以根据不同的命令而有所不同,通常会在命令的文档或手册中进行说明。

echo $?命令对于调试和脚本编写非常有用,可以根据上一个命令的退出状态码来进行条件判断或错误处理。

2.2 _exit函数和exit函数退出

_exit()函数和exit()函数都用于终止进程,但它们之间有一些区别。

_exit()函数:

1._exit()函数是一个系统调用,用于立即终止进程的执行。

2.它不会执行任何清理操作,包括不会刷新缓冲区、关闭文件描述符等。

3._exit()函数的原型为void _exit(int status),其中status参数是进程的退出状态码。

4.进程的退出状态码可以通过父进程的wait()或waitpid()系统调用来获取。

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

exit()函数:

1.exit()函数是一个库函数,用于正常终止进程的执行。

2.在调用exit()函数之前,会执行一些清理操作,例如刷新缓冲区、关闭文件描述符等。

3.exit()函数的原型为void exit(int status),其中status参数是进程的退出状态码。

4.进程的退出状态码可以通过父进程的wait()或waitpid()系统调用来获取。

exit最后也会调用_exit, 但在调用exit之前,还做了其他工作:

1.执行用户通过 atexit或on_exit定义的清理函数。

2.关闭所有打开的流,所有的缓存数据均被写入

3.调用_exit

总结:

1._exit()函数是一个系统调用,立即终止进程的执行,不执行清理操作。
2.exit()函数是一个库函数,正常终止进程的执行,执行清理操作后退出。
3.两者都接受一个退出状态码作为参数,用于表示进程的退出状态。
4.进程的退出状态码可以通过父进程的wait()或waitpid()系统调用来获取。
5.exit也是通过调用_exit来实现的
在这里插入图片描述

2.3 return退出

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

在C语言中,main函数的返回值类型通常是int类型。根据C语言标准,main函数的返回值可以是0或者非零的整数。返回0表示程序成功地执行完毕,而非零的返回值通常用于表示程序执行过程中的错误或异常情况。

非零的返回值可以用于向调用程序或操作系统报告错误信息或状态。例如,当程序需要在执行过程中发生错误时,可以返回一个非零值来指示错误的类型或代码。这样,调用程序或操作系统可以根据返回值来采取相应的措施,比如输出错误信息、终止程序或进行其他处理。

在实际应用中,非零的返回值可以根据具体需求进行定义和使用。不同的程序可能会定义不同的非零返回值来表示不同的错误或状态。一般来说,返回值的具体含义和用途是由程序员根据程序的逻辑和需求来决定的。

需要注意的是,main函数的返回值只能是整数类型,不能返回其他类型的值。如果需要返回其他类型的值,可以通过全局变量、指针参数或其他方式来实现。

3.进程等待

3.1什么是进程等待?

进程等待是指一个进程在执行过程中暂停自己的执行,等待某个特定的条件满足后再继续执行。进程等待的必要性主要体现在以下几个方面:

同步操作:在多进程或多线程的环境中,进程之间可能需要进行协调和同步。例如,一个进程可能需要等待其他进程完成某个任务后才能继续执行,或者需要等待某个共享资源的释放。进程等待可以确保进程之间的操作按照正确的顺序进行,避免数据竞争和不一致的结果。

资源管理:进程等待还可以用于管理系统资源的分配和释放。当一个进程需要使用某个资源时,如果该资源已经被其他进程占用,那么该进程可以选择等待资源的释放,而不是一直占用CPU资源进行忙等待。这样可以提高系统的资源利用率和效率。

阻塞操作:有些操作需要等待一段时间才能完成,例如网络通信、文件读写等。在这种情况下,进程可以选择等待操作完成后再继续执行,而不是一直占用CPU资源进行忙等待。这样可以避免资源的浪费,提高系统的响应速度。

3.2进程等待的方法

wait()和waitpid()
wait()
pid_t wait(int*status);

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

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

waitpid()
pid_ t waitpid(pid_t pid, int *status, int options);

返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:
pid:

如果pid等于(pid_t)-1,则请求任何子进程的状态。在这方面,waitpid()等同于wait()。

如果pid大于0,则指定要请求状态的单个子进程的进程ID。

如果pid为0,则请求任何进程组ID与调用进程相同的子进程的状态。

如果pid小于(pid_t)-1,则请求任何进程组ID等于pid的绝对值的子进程的状态

status:

WIFEXITED(stat_val):如果状态是由正常终止的子进程返回的,则评估为非零值。

WEXITSTATUS(stat_val):如果WIFEXITED(stat_val)的值非零,则该宏评估为子进程传递给_exit()或exit()的状态参数的低8位,或者子进程从main()返回的值。

WIFSIGNALED(stat_val):如果状态是由未被捕获的信号终止的子进程返回的,则评估为非零值(参见<signal.h>)。

WTERMSIG(stat_val):如果WIFSIGNALED(stat_val)的值非零,则该宏评估为导致子进程终止的信号编号。

WIFSTOPPED(stat_val):如果状态是由当前停止的子进程返回的,则评估为非零值。

WSTOPSIG(stat_val):如果WIFSTOPPED(stat_val)的值非零,则该宏评估为导致子进程停止的信号编号。

WIFCONTINUED(stat_val):如果状态是由从作业控制停止中继续的子进程返回的,则评估为非零值。

options:

WCONTINUED:waitpid()函数将报告由pid指定的任何继续运行的子进程的状态,只要该子进程自从作业控制停止后其状态尚未被报告。

WNOHANG:如果status对于由pid指定的任何子进程不立即可用,waitpid()函数将不会挂起调用线程的执行(父进程非阻塞等待)。

WUNTRACED:任何由pid指定的已停止的子进程的状态,且自从它们停止后其状态尚未被报告,也将被报告给请求进程。

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立即出错返回。

3.3 获取子进程status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16bit)
在这里插入图片描述
结合这张图片我们可以知晓可以用于从状态字中提取信号编号和退出码。

信号编号 status & 0x7F(WIFEXITED):这个表达式使用了位运算与操作符(&)和一个掩码(0x7F)。掩码0x7F的二进制表示为01111111,它的作用是将状态字中的高位清零,只保留最低的7位。这样做的目的是提取信号编号,因为信号编号通常存储在状态字的最低位。

退出码 (status >> 8) & 0xFF(WEXITSTATUS):这个表达式使用了右移操作符(>>)和位运算与操作符(&),以及一个掩码(0xFF)。首先,status >> 8将状态字向右移动8位,将退出码移动到最低位。然后,位运算与操作符&与掩码0xFF进行与操作,将高位清零,只保留最低的8位。这样做的目的是提取退出码,因为退出码通常存储在状态字的高8位。

综上所述,这种方式通过使用位运算和掩码,从状态字中提取信号编号和退出码。

3.4进程等待演示

  #include <stdio.h>
  #include <unistd.h>
  #include <stdlib.h>
  #include <sys/wait.h>                                                              
  int main()
  {
    pid_t id=fork();
    if (id < 0)//fork()失败
    {
      perror("fork");
      exit(1);
    }
    else if(0== id)//子进程
    {
      int count= 5;
      while(count--)
      {
        printf("我是子进程,pid:%d,ppid:%d,count:%d\n",getpid(),getppid(),count);
        sleep(1);
      }
      exit(7);
    }
  else
    {
        int status= 0;//定义一个变量用于储存子进程的状态
        printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());
        pid_t ret=wait(&status);
        if ( ret > 0 && ( status & 0X7F ) == 0 )// 正常退出
        { 
          printf("等待子进程成功,子进程退出码:%d\n", (status>> 8)&0XFF);
        } 
        else if( ret > 0 ) // 异常退出
        { 
          printf("等待子进程失败,子进程收到的信号编号: %d\n", status&0X7F );
        }
    }
  }   

在这里插入图片描述
第一次运行让其正常终止,所以没有信号编号的返回,正常走exit函数退出,退出码为7,即为我们自己定义的退出码。
第二次运行期间我们使用kill -9 子进程pid 命令终止子进程,信号编号返回9,此时的退出码并无意义,因为程序非正常退出。

常见的信号编号如下:

SIGHUP (1): 终端挂起或控制进程终止。

SIGINT (2): 中断信号,通常是Ctrl+C。

SIGQUIT (3): 退出信号,通常是Ctrl+\,会产生核心转储。

SIGILL (4): 非法指令。

SIGABRT (6): 终止信号,通常是abort()函数发出的信号。

SIGFPE (8): 浮点异常。

SIGKILL (9): 强制终止,不能被忽略、阻塞或捕获。

SIGSEGV (11): 段错误,试图访问无法访问的内存。

SIGPIPE (13): 管道破裂。

SIGALRM (14): 定时器超时。

SIGTERM (15): 终止信号,常用于请求进程正常终止。

SIGUSR1 (10): 用户自定义信号1。

SIGUSR2 (12): 用户自定义信号2。

SIGCHLD (17): 子进程状态改变,例如子进程终止时发送给父进程。

SIGCONT (18): 继续执行一个已停止的进程。

SIGSTOP (19): 停止信号,用于停止进程的执行。

SIGTSTP (20): 终端停止信号,通常是Ctrl+Z。

SIGTTIN (21): 后台进程试图从控制终端读取。

SIGTTOU (22): 后台进程试图向控制终端写入。

SIGBUS (7): 总线错误,试图访问不属于你的内存地址。

进程的阻塞等待方式

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
  pid_t id=fork();
  if (id < 0)//fork()失败
  {
    perror("fork");
    exit(1);
  }
  else if(0== id)//子进程
  {
    int count= 5;
    while(count--)
    {
      printf("我是子进程,pid:%d,ppid:%d,count:%d\n",getpid(),getppid(),count);
      sleep(1);
    }
    exit(7);
  }
  else
  {
      int status= 0;//定义一个变量用于储存子进程的状态
      printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());
      pid_t ret=waitpid(-1,&status,0);
      if ( ret > 0 && ( status & 0X7F ) == 0 )// 正常退出
      { 
        printf("等待子进程成功,子进程退出码:%d\n", (status>> 8)&0XFF);
      } 
      else if( ret > 0 ) // 异常退出
      { 
        printf("等待子进程失败,子进程收到的信号编号: %d\n", status&0X7F );
      }
  }
}

运行结果
在这里插入图片描述

进程的非阻塞等待方式

  #include <stdio.h>
  #include <unistd.h>
  #include <stdlib.h>
  #include <sys/wait.h>                                                              
  int main()
  {
    pid_t id=fork();
    if (id < 0)//fork()失败
    {
      perror("fork");
      exit(1);
    }
    else if(0== id)//子进程
    {
      int count= 5;
      while(count--)
      {
        printf("我是子进程,pid:%d,ppid:%d,count:%d\n",getpid(),getppid(),count);
        sleep(1);
      }
      exit(7);
    }
  else
    {
        int status= 0;//定义一个变量用于储存子进程的状态
        printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());
        pid_t ret= 0;
        do
        {
         ret=waitpid(-1,&status,WNOHANG);
         if(0== ret)
         {
           printf("子进程未结束。\n");
         }
         sleep(1);
        }while(0==ret);
        if ( ret > 0 && ( status & 0X7F ) == 0 )// 正常退出
       { 
         printf("等待子进程成功,子进程退出码:%d\n", (status>> 8)&0XFF);
       } 
       else // 异常退出
       { 
         printf("等待子进程失败,子进程收到的信号编号: %d\n", status&0X7F );
       }
   }  
  }    

运行结果
在这里插入图片描述

进程的阻塞等待方式和进程的非阻塞等待方式有什么区别
进程的阻塞等待方式和进程的非阻塞等待方式是两种不同的等待子进程状态变化的方式:

阻塞等待:当父进程调用等待函数(如wait、waitpid等)等待子进程退出时,父进程会一直阻塞(即挂起自己的执行),直到子进程退出或发生其他指定的状态变化。在等待期间,父进程不会继续执行其他任务。

非阻塞等待:当父进程调用非阻塞等待函数(如waitpid函数的使用WNOHANG标志)等待子进程退出时,父进程会继续执行自己的任务,不会被阻塞。父进程会立即返回等待函数,无论子进程的状态是否发生变化。非阻塞等待允许父进程在等待子进程的同时继续执行其他任务。

4.进程程序替换

4.1.替换原理

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

环境变量未变。
在这里插入图片描述

4.2替换函数

#include <unistd.h>

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

函数简要说明:

execl:

原型:int execl(const char *path, const char *arg, …);

功能:用于执行指定路径的可执行文件,第一个参数是要执行的程序的路径,后面的参数是传递给新程序的命令行参数,以NULL为结束标志。

示例:execl(“/bin/ls”, “ls”, “-l”, NULL);

execlp:

原型:int execlp(const char *file, const char *arg, …);

功能:类似于execl,但是它会在系统的路径中搜索可执行文件。

示例:execlp(“ls”, “ls”, “-l”, NULL);

execle:

原型:int execle(const char *path, const char *arg, …, char *const
envp[]);

功能:类似于execl,但是可以指定新程序的环境变量。最后一个参数是一个指向环境变量的指针数组,以NULL为结束标志。

示例:char *const envp[] = {“PATH=/bin”, NULL}; execle(“/bin/ls”, “ls”,
“-l”, NULL, envp);

execv:

原型:int execv(const char *path, char *const argv[]);

功能:类似于execl,但是参数传递方式是使用一个指向参数字符串数组的指针。第一个参数是要执行的程序的路径,第二个参数是指向参数字符串数组的指针,以NULL为结束标志。

示例:char *const argv[] = {“ls”, “-l”, NULL}; execv(“/bin/ls”, argv);

execvp:

原型:int execvp(const char *file, char *const argv[]);

功能:类似于execv,但是它会在系统的路径中搜索可执行文件。

示例:char *const argv[] = {“ls”, “-l”, NULL}; execvp(“ls”, argv);

execvpe:

原型:int execvpe(const char *path, char *const argv[], char *const
envp[])

功能:类似于execv,但是可以指定新程序的环境变量,最后一个参数是一个指向环境变量的指针数组,以NULL为结束标志。

示例:char *const argv[] = {“ls”, “-l”, NULL}; char *const envp[] =
{“PATH=/bin”, NULL}; execve(“/bin/ls”, argv, envp);

这些函数通常用于在一个进程内部启动一个新的程序,新程序取代当前进程的执行。执行成功则不会返回,失败则会返回-1并设置errno。它们通常用于在C程序中执行其他程序,比如在Shell中运行命令。

exec调用举例

#include <unistd.h>
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve
在这里插入图片描述

4.3命名理解

l(list) : 表示参数采用列表

v(vector) : 参数用数组

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

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

在这里插入图片描述

5.制作简易shell

shell描述
在这里插入图片描述
所以要写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork)
  4. 替换子进程(execvp)
  5. 父进程等待子进程退出(wait)

实现代码:

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

#define NUM 1024
#define SIZE 32
#define SEP " "

char cmd_line[NUM];//命令字符
char *g_argv[SIZE];//打散后的命令

int main()
{
  while(1)
  {
    //1.打印提示信息
    printf("[myshell #]:");
    fflush(stdout);//刷新
    memset(cmd_line,'\0',sizeof cmd_line);//初始化
    //2.获取键盘输入
    if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)//获取为空
    {
      continue;
    }
    cmd_line[strlen(cmd_line)-1]='\0';
    //3.命令行解析
    g_argv[0]= strtok(cmd_line,SEP);//strtok第一次使用需原始字符
    int index = 1;
    while(g_argv[index++]= strtok(NULL,SEP));//第二次可为NULL
    //4.TODO,内置命令
    //父进程执行cd 命令
    if(strcmp(g_argv[0],"cd")== 0)
    {
      if(g_argv[1]!= NULL)chdir(g_argv[1]);
      continue;
    }
    //5.fork()
    pid_t id= fork();
    if(0== id)//child
    {
      printf("子进程执行:\n");
      execvp(g_argv[0],g_argv);
      exit(1);
    }
    //father
    int status = 0;
    pid_t ret = waitpid(id,&status,0);
    if ( ret > 0 && ( status & 0X7F ) == 0 )// 正常退出
        { 
          printf("等待子进程成功,子进程退出码:%d\n", (status>> 8)&0XFF);
        } 
        else if( ret > 0 ) // 异常退出
        { 
          printf("等待子进程失败,子进程收到的信号编号: %d\n", status&0X7F );
        } 
  }  
}

测试
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值