Linux编程–进程的基本使用

1、创建进程的函数为fork

  • 子进程会在当前进程的基础上创建
  • 创建的子程与当前进程都会执行进程后的代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(){

    pid_t pid = getpid();
    printf("pid:%u\n" ,pid);

    // 从此处开始,后面的代码会被执行2次
    pid_t fork_pid = fork();
    if (fork_pid == -1){
        perror("fork error:");
        exit(1);
    }else if (fork_pid == 0){
        // 为0是返回给子线程,表明线程创建成功
        // 当前操作在子线程中执行
        printf("子进程:pid:%u, ppid:%u\n", getpid(), getppid());
    }else{
        // 父线程中返回的是子线程id号
        printf("父进程:pid:%u, ppid:%u\n", getpid(), getppid());
    }
    printf("fork:%u\n", fork_pid);
    return 0;
}

输出结果为:

pid:30963
父进程:pid:30963, ppid:11330
fork:30964
子进程:pid:30964, ppid:30963
fork:0

2、循环创建进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(){
    printf("父进程id:%u\n", getpid());
    int i;
    for(i = 0;i < 5; i++){
        pid_t pid = fork();
        if (pid == -1){
            perror("fork error:");
        }else if(pid == 0){
            // 子进程跳出循环,以免再次创建进程,指数级增长
            printf("第%d个子进程,id:%u\n", i+1, getpid());
            break;
        }else{
            sleep(1);
        }
    }
    return 0;
}

输出结果为:

父进程id:19575
第1个子进程,id:19576
第2个子进程,id:19577
第3个子进程,id:19581
第4个子进程,id:19585
第5个子进程,id:19589

3、进程共享

  • 不同进程之间,内存空间相互独立,互不影响
  • fork时:
    • 相同点:全局变量、数据段、代码段、栈、堆、环境变量、用户ID、主目录、进程工作目录等;
    • 不同点:进程id、fork返回值、父进程id、进程运行时间、闹钟(定时器)、未决信号集等;
  • fork后:
    • 读时共享,写时复制的原则
    • 先执行子进程还是父进程是不确定的,取决于内核所使用的调度算法;
  • 共享文件描述符
  • 共享mmap建立的映射区

4、gdb

  • set follow-fork-mode
    • parent —> 跟踪父进程
    • child —> 跟踪子进程

5、exec函数簇

extern char **environ;
// 第一个参数为函数路径,接下来的参数为程序的转入参数从argv[0]开始
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
// execle中的e指的是environ,环境变量表
int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[] */);
// 相对execl函数,execlp加载了环境变量中的PATH,可通过环境变量寻找执行目标
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
// execv中的v指的是argv,
int execv(const char *path, char *const argv[]);

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

int execvP(const char *file, const char *search_path, char *const argv[]);
  • 该函数的作用在于使子进程执行指定的程序,而非沿着父进程继续往下重复执行
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    int main(){
      // 创建一个子进程
      pid_t pid = fork();
      if (pid == -1){
          perror("fork error:");
          exit(1);
      }else if(pid == 0){
          // 子进程执行ls命令以及ls命令需要的输入入参数
          //execl("/bin/ls", "ls", "-l", "-a", NULL);
          //execlp("ls", "ls", "-l", "-a", NULL);
          char* argv[] = {"ls", "-l", "-a", NULL};
          execv("/bin/ls", argv);
      }else{
          // 父进程
          sleep(1);
          printf("pid:%u\n", getpid());
      }
      return 0;
    }
    

    执行结果:

    total 24
    drwxrwxr-x 2 parallels parallels 4096 Jan  6 11:33 .
    drwxrwxr-x 8 parallels parallels 4096 Jan  6 11:13 ..
    -rwxrwxr-x 1 parallels parallels 8560 Jan  6 11:33 a.out
    -rw-rw-r-- 1 parallels parallels  333 Jan  6 11:33 exec.c
    pid:11479
    
  • fork、exec与dup2的综合使用
    • 要求实现ls结果输出到文件中,终端可直接使用ls -l > out.txt方式完成
    • dup2是dupto的意思,文件描述符的拷贝
    • 文件描述符表前三个0、1、2分别表示stdin、stdout、stderr
    • 如果需要将输出结果转移到文件,可以通过dup2将stdout指向某个特定的fd
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    int main(){
      pid_t pid = fork();
      if (pid == -1){
          perror("fork error");
          exit(1);
      }else if(pid > 0){
          sleep(1);
      }else{
          // 子进程
          int fd = open("file.txt", O_CREAT | O_RDWR, 0664);
          // 将文件描述符stdout,dupto fd,即将原本打印在终端的信息写入fd
          int ret = dup2(fd, STDOUT_FILENO);
          if (ret == -1){
              perror("dup2");
              exit(1);
          }
          // 注意:该函数如果没有出错则无返回值,即意味着后续代码不会执行!!!
          execlp("ls", "ls", "-l", "-a", NULL);
          // 如果execlp执行成功,不会来到这里
          close(fd);
      }
      return 0;
    }
    

6、孤独进程

  • 父进程先于子进程结束,子进程将处于无人管辖的状态,此时子进程会被系统收入“孤独院”,此后子进程的父进程为孤儿院进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
    // 创建一个新进程
    pid_t pid = fork();
    if (pid == -1){
        perror("fork error:");
        exit(1);
    }else if (pid == 0){
        // 子进程
        int i;
        for (i = 0; i < 5; i++){
            // 父进程结束后,父进程默认为init进程,一般为1,所以有孤儿进程都会被挂在此
            printf("I am child, pid=%u, ppid=%u\n", getpid(), getppid());
            sleep(1);
        }
    }else{
        // 父进程
        sleep(2);
        printf("I am father, pid=%u, I will died\n", getpid());
    }


    return 0;
}

执行结果

I am child, pid=2570, ppid=2569
I am child, pid=2570, ppid=2569
I am father, pid=2569, I will died
I am child, pid=2570, ppid=1
parallels@ubuntu:~/Linux/process$ I am child, pid=2570, ppid=1
I am child, pid=2570, ppid=1

7、僵尸进程

  • 子进程结束后,父进程没有对子进程进行相关处理,此时子进程即为僵尸进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
    pid_t pid = fork();
    if (pid == -1){
        perror("fork error");
        exit(1);
    }else if (pid == 0){
        // 子进程
        sleep(2);
        printf("I am child, pid=%u, ppid=%u, I will died...\n", getpid(), getppid());
    }else{
        // 父进程
        int i;
        for (i = 0; i < 10; i++){
            printf("I am father ,pid=%u\n", getpid());
            sleep(1);
        }
    }
    return 0;
}

执行结果

I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429
I am child, pid=4430, ppid=4429, I will died...
I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429

在子进程结束后,父进程结束前,查看进程

parallels@ubuntu:~$ ps -ax | grep a.out
 4429 pts/0    S+     0:00 ./a.out
 4430 pts/0    Z+     0:00 [a.out] <defunct>
 4452 pts/1    S+     0:00 grep --color=auto a.out

8、wait函数

  • pid_t wait(int *wstatus);
    • 阻塞等待子进程
    • 回收子进程资源
    • 获取子进程结束状态
    • WIFEXITED—>WEXITSTATUS( ) // 获取子进程退出状态
    • WIFSIGNALED —> WTERMSIG( ) // 获取子进程终止信号编号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.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){
        // 子进程
        sleep(2);
        printf("I am child, pid=%u, ppid=%u, I will died...\n", getpid(), getppid());
    }else{
        // 父进程
        int wstatus;// 函数传入值
        int ret = wait(&wstatus);
        if (ret == -1){
            perror("wait error");
            exit(1);
        }
        // WIFEXITED()判断是否正常退出
        if (WIFEXITED(wstatus)){
            printf("exited with %d\n", WEXITSTATUS(wstatus));
        }
        // WIFSIGNALED()判断是否信号退出(异常退出)
        if (WIFSIGNALED(wstatus)){
            printf("signaled with %d\n", WTERMSIG(wstatus));
        }
        // WIFSTOPPED()判断是否中止
        if (WIFSTOPPED(wstatus)){
            WSTOPSIG(wstatus);// 停止信号值
        }
        // WIFCONTINUED()判断是否继续
        if (WIFCONTINUED(wstatus)){

        }
        int i;
        for (i = 0; i < 10; i++){
            printf("I am father ,pid=%u\n", getpid());
            sleep(1);
        }
    }
    return 0;
}

正常退出结果:

I am child, pid=9474, ppid=9473, I will died...
exited with 0
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473

异常退出结果:

signaled with 9
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031

9、pid_t waitpid(pid_t pid, int wstatus, int option)

  • pid > 0 —> 指定进程id回收
  • pid = -1 —> 回收任意子进程,等同于wait( )
  • pid = 0 —> 回收本组任意子进程,默认父子进程同组
  • pid < -1 —> 回收该进程的任意子进程
  • option = 0 –> 阻塞回收
  • option = WNOHANG —> 非阻塞回收
  • 返回值
    • 成功:pid
    • 失败:-1
    • option=WNOHANG并且子进程尚未结束,返回0
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(){
    int i;
    pid_t pid;
    for(i = 0; i < 5; i++){
        pid = fork();
        if (pid == -1){
            perror("fork error:");
            exit(1);
        }else if (pid == 0){
            // 第1个子进程比父进程慢3秒,后续每个子进程都比前一个慢1秒
            sleep(i+3);
            break;
        }
    }
    if (i < 5){
        // 子进程
        printf("I am child pid = %u\n", getpid());
    }else{
        // 父进程
        printf("I am father pid = %u\n", getpid());
        // waitpid(-1, NULL, 0)相当于wait(NULL)
        // 第一个参数为pid,-1:回收任意子线程、>0:回收指定线程
        // 第三个参数设为WNOHANG则为非阻塞立即执行,如果没有子线程或子线程正在运行返回0
        // 循环执行waitpid(-1, NULL, 0);则会循环取子线程
        pid_t pid = waitpid(-1, NULL, 0);
        printf("%d\n", pid);
        //wait(NULL);
    }
    return 0;
}

Leave a Reply