• C语言进程的相关操作


    C语言进程的相关操作

    进程简介

    • 每个进程都有一个非负整数形式到的唯一编号,即PID(Process Identification,进程标识)
    • PID在任何时刻都是唯一的,但是可以重用,当进程终止并被回收以后,其PID就可以为其他进程使用
    • 进程的PID由系统内核根据延迟重用算法生成,以确保新进程的PID不同于最近终止进程到的PID
    • 其中0号进程,叫做交换进程,系统内核中的一部分,所有进程的根进程,磁盘上没有它的可执行文件
    • 1号进程是init进程,在系统自举过程结束时由调度进程创建,读写与系统相关的初始化文件,引导系统至一个特定状态,以超级用户特权运行的普通进程,永不终止
    • 除去调度进程以外,系统中的每个进程都有一个唯一的父进程,对任何一个子进程而言,其父进程的PID即是它的PPID
    • 下面这些函数都包含在unistd.h头文件中
    • pid_t getpid(void);返回调用进程的PID
    • pid_t getppid(void);返回调用进程的父进程的PID
    • uid_t getuid(void);返回调用进程的实际用户ID
    • gid_t getgid(void);返回调用进程的实际组ID
    • uid_t geteuid(void);返回调用进程的有效用户ID
    • gid_t getegid(void);返回调用进程的有效组ID

    创建子进程

    • 创建子进程的函数包含在unistd.h头文件中

    • fork函数

      • pid_t fork(void);
        • 功能:创建调用进程的子进程
        • 返回值:失败返回-1,成功情况下返回的变量在父进程中是PID,在子进程中是0
        • 可以通过这个返回值来执行父进程和子进程
        • 当系统中的总的线程数达到了上限,或者用户的总进程达到了上限,fork函数会失败。
    • 创建子进程示例代码

      #include 
      #include 
      
      int main(void)
      {
          printf("haha\n");
          // 创建子进程
          int pid = fork();
          printf("heihei\n");
          return 0;
      }
      
      /*
      haha
      heihei
      heihei
      */
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

    父子进程间的关系

    • 以下是父子进程中数据相关copy的示例图
      在这里插入图片描述

    • 验证上图

      #include 
      #include 
      #include 
      #include 
      
      
      int global = 100;  // 父进程全局变量->数据区
      
      int main(void)
      {
      int local = 200;  // 父进程局部变量->栈区
      int *heap = malloc(sizeof(int));  // 动态分配内存->堆区
      *heap = 3;
      
      printf("父进程第一次打印: PID->%d %p->%d %p->%d %p->%d\n", getpid(), &global, global, &local, local, heap, *heap);
      // 创建子进程
      pid_t pid = fork();
      if(pid == 0)
      {
      // 子进程操作,数据会从父进程copy一份过来,这里执行++操作
      printf("子进程打印: PID->%d PPID->%d %p->%d %p->%d %p->%d\n", getpid(), getppid(), &global, ++global, &local, ++local, heap, ++*heap);
      return 0;
      }
      sleep(1);  // 这里等1s,让子进程++
      printf("父进程第二次打印: PID->%d %p->%d %p->%d %p->%d\n", getpid(), &global, global, &local, local, heap, *heap);
      
      return 0;
      }
      
      /*
      父进程第一次打印: PID->1674604 0x5577e1acc010->100 0x7ffd4d4bfaa8->200 0x5577e23422a0->3
      子进程打印: PID->1674605 PPID->1674604 0x5577e1acc010->101 0x7ffd4d4bfaa8->201 0x5577e23422a0->4
      父进程第二次打印: PID->1674604 0x5577e1acc010->100 0x7ffd4d4bfaa8->200 0x5577e23422a0->3
      
      这里的父进程和子进程地址一样是虚拟地址里面一样,因为每个进程都有一个独立的虚拟地址池,相互不影响的
      发现子进程跟父进程互相不影响,验证了上图的案例
      */
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
    • 父子进程操作文件,其实是共享一个文件表项的
      在这里插入图片描述

    • 验证上图

      #include 
      #include 
      #include 
      #include 
      #include 
      
      int main(void)
      {
          // 父进程打开文件
          int fd = open("./test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
          if(fd == -1)
          {
              perror("open");
              return -1;
          }
      
          // 父进程写入数据
          char *data = "hello bhlu!";
          if(write(fd, data, strlen(data)) == -1)
          {
              perror("write");
              return -1;
          }
      
          // 创建子进程
          pid_t pid = fork();
          if(pid == 0)
          {
              // 子进程修改文件读写位置
              if(lseek(fd, -5, SEEK_END) == -1)
              {
                  perror("lseek");
                  return -1;
              }
              return 0;
          }
      
          // 再次插入数据,验证子进程修改的读写位置是否生效
          sleep(1);  // 先等1s,让子进程执行完
          data = "linux\n";
          if(write(fd, data, strlen(data)) == -1)
          {
              perror("write");
              return -1;
          }
      
          // 关闭文件
          close(fd);
          return 0;
      }
      
      /*
      cat test.txt
      hello linux
      发现是修改成功的,说明上图是对的,子进程和父进程共用一个文件表项
      */
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56

    进程的终止

    以下内容只是简单的介绍进程的终止,以便理解

    • 进程的终止分为两种

      1. 正常终止:分为三种情况

        1. main函数中正常返回

        2. 使用exit函数终止:exit函数可以在任何函数中执行令进程结束,return语句只有在main函数中执行才能令进程结束

          #include 
          
          void exit(int status);
          /* 
          功能: 令进程终止
          参数: status 进程的退出码,相当于main函数的返回值
          无返回值
          */
          
          /*
          exit函数在终止前会做以下几件收尾工作
          1. 调用实现通过atexit或on_exit函数注册的函数退出函数
          2. 冲刷并关闭所有仍处于打开状态的标准I/O流
          3. 删除所有通过tmpfile函数创建的临时文件
          4. 执行_exit(status);
          使用exit函数令进程终止,通常使用EXIT_SUCCESS和EXIT_FAILUR两个宏
          EXIT_SUCCESS -> 1; EXIT_FAILUR -> 0;
          */
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
        3. 调用_exit/_Exit函数令进程终止

          // _exit函数
          #include 
          
          void _exit(int status);
          /*
          参数: status 进程的退出码,相当于main函数的返回值
          无返回值
          */
          
          // _Exit函数
          #include 
          
          void _Exit(int status);
          /*
          参数: status 进程的退出码,相当于main函数的返回值
          无返回值
          */
          
          /*
          _exit函数在终止前会做以下几件收尾工作
          1. 关闭所有仍处于打开状态的文件描述符
          2. 将调用进程的所有子进程托付过init进程
          3. 向调用进程的父进程发送SIGCHLD(7)信号
          4. 令调用进程终止运行,将status的低八位作为退出码保存在其终止状态中
          */
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
      2. 异常终止

        1. 进程执行了系统认为具有危险性的操作时,或者系统本身发生故障或意外,内核会向进程发送特定的信号

          SIGILL(4) -> 进程试图执行非法指令
          SIGBUS(7) -> 硬件或对齐错误
          SIGEPE(8) -> 浮点异常
          SIGSEGV(11) -> 无效内存访问
          SIGPWR(30) -> 系统供电不足
          
          • 1
          • 2
          • 3
          • 4
          • 5
        2. 人为触发信号

          SIGINT(2) -> Ctrl+c
          SIGQUIT(3) -> Ctrl+\
          SIGKILL(9) -> 不能被捕获或忽略的进程终止信号
          SIGTERM(15) -> 可以被捕获或忽略的进程终止编号
          
          • 1
          • 2
          • 3
          • 4
        3. 向进程自己发送信号

          #include 
          
          void abort(void);
          /*
          功能: 想进城发送SIGABRT(6)信号,该信号默认情况下可以使进程结束
          无返回值
          */
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7

    在使用exit函数或main函数正常退出时,如果注册了atexiton_exit,那就会触发退出函数,以下是示例代码

    #include 
    #include 
    
    void func(void)
    {
        exit(6);
    }
    
    void goto1(void)
    {
        printf("goto1\n");
    }
    
    void goto2(int status, void *arg)
    {
        printf("status = %d\n", status);
        printf("arg = %s\n", (char *)arg);
    }
    
    int main(void)
    {
        atexit(goto1);  // 退出之前执行goto1
        on_exit(goto2, "heihei");  // 退出之前执行goto2,可以传参
        func();
        return 0;
    }
    
    /*
    相当于钩子函数,在退出之前执行,可以进行一些回收操作
    status = 6
    arg = heihei
    goto1
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    回收子进程

    • 如果不回收子进程的话,会导致有很多僵尸进程的存在,从而消耗更多的系统资源。
    • 父进程需要等待子进程到的终止,以继续后续工作
    • 父进程需要了解子进程终止的原因,是正常终止,还是异常终止

    阻塞回收

    • wait函数是用于回收子进程的一个函数,它使用的是阻塞回收,使用它必须包含sys/wait.h头文件

    • wait函数

      • pid_t wait(int *status);

        • 功能:等待和回收任意子进程

        • 参数:status用于输出子进程的终止状态,可置NULL

          • 补充:可以使用以下工具宏分析子进程的终止状态

            if(WIFEXITED(status))
                // 真
                printf("正常终止: 进程退出码是%d\n", WEXITSTATUS(status));
            else
                // 假
                printf("异常终止: 终止进程的信号是%d\n", WTERMSIG(status));
            
            // 下面跟上面判断条件相反
            if(WIFSIGNALED(status))
                // 真
                printf("异常终止: 终止进程的信号是%d\n", WTERMSIG(status));
            else
                // 假
                printf("正常终止: 进程退出码是%d\n", WEXITSTATUS(status));
            
            • 1
            • 2
            • 3
            • 4
            • 5
            • 6
            • 7
            • 8
            • 9
            • 10
            • 11
            • 12
            • 13
            • 14
        • 返回值:成功返回回收的子进程PID,失败返回-1

    • 简单代码示例

      // 子进程的回收
      #include 
      #include 
      #include 
      #include 
      
      int main(void)
      {
          // 创建子进程
          pid_t pid = fork();
          if(pid == -1)
          {
              perror("fork");
              return -1;
          }
          // 子进程相关操作
          if(pid == 0)
          {
              printf("%d进程: 我是子进程!\n", getpid());
              // sleep(5);
              // exit(3);
              // _exit(5);
              // return 2;
              // abort();  // 向进程发送信号异常结束
              // 以下两句会造成内存无效访问,会返回11
              char *p = NULL;
              *p = 123;
          }
          // 父进程等待回收子进程
          printf("%d进程: 我是父进程!\n", getpid());
          int s;  // 用来输出所回收的子进程终止状态
          pid_t childpid = wait(&s);
          if(childpid == -1)
          {
              perror("wait");
              return -1;
          }
          printf("父进程回收了%d进程的僵尸!\n", childpid);
          // 根据返回值判断子进程是否是正常结束
          if(WIFEXITED(s))
              printf("正常结束: %d\n", WEXITSTATUS(s));
          else
              printf("异常结束: %d\n", WTERMSIG(s));
      
          return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
    • 以下代码是一个循环创建5个进程,然后父进程挨个回收

      #include 
      #include 
      #include 
      #include 
      
      int main(void)
      {
          printf("%d进程: 我是父进程!\n--------------------------\n", getpid());
          sleep(1);
          // 创建子进程
          for(int i = 0; i < 5; i++)
          {
              pid_t pid = fork();
              if(pid == -1)
              {
                  perror("fork");
                  return -1;
              }
              // 子进程操作
              if(pid == 0)
              {
                  printf("%d进程: 我是子进程!\n", getpid());
                  sleep(i+1);
                  return i+1;
              }
          }
          
          // 父进程操作: 回收子进程
          while(1)
          {
              int s;  // 用户接收子进程的终止状态
              pid_t childpid = wait(&s);
              if(childpid == -1)
              {
                  if(errno == ECHILD)
                  {
                      printf("没有子进程可以回收了!\n");
                      break;
                  }
                  else
                  {
                      perror("wait");
                      return -1;
                  }
              }
              // 判断子进程的终止状态
              if(WIFEXITED(s))
                  printf("正常结束: %d\n", WEXITSTATUS(s));
              else
                  printf("异常终止: %d\n", WTERMSIG(s));
          }
          return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53

    非阻塞回收

    • waitpid函数一般用于非阻塞回收子进程,还可以回收特定子进程,使用这个函数需要引用sys/wait.h头文件

    • waitpid函数

      • pid_t waitpid(pid_t pid, int *status, int options);
        • 功能:等待并回收任意或特定子进程
        • 参数
          • pid:取-1等待并回收任意子进程,相当于wait函数,>0等待回收特定子进程
          • status:用于输出子进程的终止状态,可置NULL
          • option:0代表阻塞模式,WNOHANG代表非阻塞模式,如果等待的进程还在运行,则返回0
        • 返回值:成功返回回收子进程的PID或者0,失败返回-1
    • 以下是使用非阻塞回收的方法回收子进程

      #include 
      #include 
      #include 
      #include 
      #include 
      
      int main(void)
      {
          printf("%d进程: 我是父进程!\n-------------------------\n", getpid());
          // 创建子进程
          for(int i = 0; i < 5; i++)
          {
              pid_t pid = fork();
              if(pid == -1)
              {
                  perror("fork");
                  return -1;
              }
              // 子进程相关操作
              if(pid == 0)
              {
                  printf("%d进程: 我是子进程!\n", getpid());
                  sleep(i+1);
                  // 三种效果
                  if(i == 3)
                  {
                      abort();
                  }
                  else if(i == 4)
                  {
                      char *p = NULL;
                      *p = 123;
                  }
                  else
                  {
                      return i+1;
                  }
              }
          }
          
          // 父进程回收子进程
          sleep(1);
          while(1)
          {
              int s; // 用于保存进程的终止状态
              pid_t childpid = waitpid(-1, &s, WNOHANG);  // 这里使用的是非阻塞模式
              if(childpid == -1)
              {
                  // 报错或者没有子进程了
                  if(errno == ECHILD)
                  {
                      printf("没有子进程了!\n");
                      break;
                  }
                  else
                  {
                      perror("waitpid");
                      return -1;
                  }
              }
              else if(childpid == 0)
              {
                  // 子进程还在运行
                  printf("子进程在运行,无法回收,先睡会!\n");
                  sleep(2);
              }
              else
              {
                  // 回收成功并判断是否正常终止
                  printf("%d子进程回收成功!\n", childpid);
                  if(WIFEXITED(s))
                      printf("%d进程正常终止, 进程退出码: %d\n\n", childpid, WEXITSTATUS(s));
                  else
                      printf("%d进程异常终止, 终止进程信号: %d\n\n", childpid, WTERMSIG(s));
              }
          }
          return 0;
      }
      
      /*
      代码执行效果
      1761797进程: 我是父进程!
      -------------------------
      1761798进程: 我是子进程!
      1761799进程: 我是子进程!
      1761800进程: 我是子进程!
      1761801进程: 我是子进程!
      1761802进程: 我是子进程!
      子进程在运行,无法回收,先睡会!
      1761798子进程回收成功!
      1761798进程正常终止, 进程退出码: 1
      
      1761799子进程回收成功!
      1761799进程正常终止, 进程退出码: 2
      
      子进程在运行,无法回收,先睡会!
      1761800子进程回收成功!
      1761800进程正常终止, 进程退出码: 3
      
      1761801子进程回收成功!
      1761801进程异常终止, 终止进程信号: 6
      
      子进程在运行,无法回收,先睡会!
      1761802子进程回收成功!
      1761802进程异常终止, 终止进程信号: 11
      
      没有子进程了!
      */
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108

    补充

    • 实际情况下,无论进程是正常终止还是异常终止,都会通过系统内核向其父进程发送一个SIGCHLD(17)信号,我们可以提供一个针对该信号的处理函数,在信号处理函数中异步的方式回收子进程,这样不仅流程简单,回收效率还高,僵尸进程的存活时间也会很短。

    创建新进程

    fork函数不同,这里使用的exec函数是创建一个新的进程,新进程会取代调用自身的进程,新进程覆盖之前的进程地址空间,进程的PID不会改变。
    在这里插入图片描述

    • exec不是一个函数,而是一堆函数,功能一样,用法相似

    • #include

      1. int execl(const char *path, const char *arg, ...);

        execl("/bin/ls", "ls", "-a", "-l", NULL);
        /*
        path使用的是路径名
        使用NULL作为arg的结尾
        失败返回-1,成功不返回
        */
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
      2. int execlp(const char *file, const char *arg, ...);

        execlp("ls", "ls", "-a", "-l", NULL);
        /*
        file使用的是文件名,会从环境变量中一个个的找
        使用NULL作为arg的结尾
        失败返回-1,成功不返回
        */
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
      3. int execle(const char *path, const char *arg, ..., char *const envp[]);

        char *envp[] = {"NAME=bhlu", "AGE=25", NULL};
        execle("/usr/bin/env", "env", NULL, envp);
        /*
        比excel多一个envp,用于设置环境变量,它设置什么,新进程的环境变量就只有什么
        失败返回-1,成功不返回
        环境变量输出:
            NAME=bhlu
            AGE=25
        */
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
      4. int execv(const char *path, char *const argv[]);

        char *argv[] = {"ls", "-a", "-l", NULL};
        execv("/bin/ls", argv);
        /*
        execv系列使用的都是字符指针数组,字符数组是以NULL结尾
        失败返回-1,成功不返回
        */
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
      5. int execvp(const char *file, char *const argv[]);

        char *argv[] = {"ls", "-a", "-l", NULL};
        execvp("ls", argv);
        /*
        跟execv差不多,就第一个参数是文件名
        失败返回-1,成功不返回
        */
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
      6. int execve(const char *path, char *const argv[], char *const envp[]);

        char *argv[] = {"env", NULL};
        char *envp[] = {"NAME=bhlu", "AGE=25", NULL};
        execve("/usr/bin/env", argv, envp);
        /*
        跟execle函数差不多,就是这里的第二个参数是字符指针数组
        失败返回-1,成功不返回
        */
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
    • 后缀不同,代码的含义也不同

      • l:即list,新进程的命令以字符指针列表形式传入,列表以空指针结束
      • p:即path:第一个参数,不包含/,就根据PATH环境变量搜索文件
      • e:即environment:设定环境变量,不指定则从调用进程复制
      • v:即vector:新进程的命令行参数以字符指针数组的形式传入,数组以空指针结束
      • 实际底层最后使用的都是execve函数
    • 使用exec函数基本会将原进程的所有信号、属性、数据等都丢失或者恢复初识状态,只有PIDPPIDUID等会被继承下来。

    • 一般都会先创建一个子进程,然后在子进程中使用exec函数,以下是相关示例

      #include 
      #include 
      
      int main(void)
      {
          // 创建子进程
          pid_t pid = fork();
          if(pid == -1)
          {
              perror("fork");
              return -1;
          }
      
          // 子进程相关操作
          if(pid == 0)
          {
              char *argv[] = {"env", NULL};
              if(execvp("/bin/env", argv) == -1)
              {
                  perror("execvp");
                  return -1;
              }
          }
      
          // 父进程操作
          printf("父进程PID: %d\n", getpid());
          return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28

    system

    • 下面介绍的是c语言执行shell命令的函数

    • #include

      • int system(const char *command);
        • 功能:执行shell命令
        • 参数:shell命令,如果参数取NULL,返回非0表示Shell可用,返回0表示不可用
        • 返回值:成功返回command进程的终止状态, 失败返回-1
    • 代码实例

      #include 
      #include 
      #include 
      
      int main(void)
      {
          int s = system("echo $PATH");
          if(s == -1)
          {
              perror("system");
              return -1;
          }
      
          printf("父进程PID: %d\n", getpid());
          return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • system函数内部调用了vforkexecwaitpid等函数,而且它是标准库函数,可以跨平台使用

      • 如果调用vforkwaitpid函数出错,则返回-1
      • 如果调用exec函数出错,则在子进程中执行exit(127)
      • 如果都成功,会从waitpid获取command进程的终止状态
  • 相关阅读:
    GeneratePress:全局颜色设置教程
    一文了解Spark引擎的优势及应用场景
    2024年山东省职业院校技能大赛中职组 “网络安全”赛项竞赛试题-B卷
    JavaScript-DOM按钮事件
    crash工具分析dma设备内存踩踏(二)
    【微软漏洞分析】MS15-023 Win32k 特权提升漏洞 - CVE-2015-0078 + 绕过(CVE-2015-2527 in MS15-097)
    Spring的资源管理(Resource)
    图的基本概念
    《C++标准库第2版》3.2 虽旧犹新的语言特性 笔记
    ChatGPT的原理
  • 原文地址:https://blog.csdn.net/lubuhan/article/details/133375392