linux -- 多进程编程基础

一、进程创建

1.1、创建进程

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);
pid_t vfork(void);

两者执行成功返回大于等于0的值,失败返回 -1
返回大于0的值(子进程的pid)表示在父进程
返回0表示在子进程
并发异步执行

后面的函数若无说明,则头文件和fork()一样

  • fork()
    子进程会复制父进程的所有资源,采用写时复制
    父子进程内存空间相互独立
    父子进程执行顺序不确定,由cpu调度决定
    若父进程先结束,则子进程会成为孤儿进程,被init进程收养
  • vfork()
    子进程共享父进程的内存空间(全局变量,堆栈),并于父进程执行
    创建进程系统开销更小
    执行顺序固定容易发生死锁,
    共享内存空间容易造成进程间同步错误

1.2、获取进程号

pid_t getpid(void);
pid_t getppid(void);
  • getpid
    获取当前进程的进程号
    成功返回调用进程的进程号,失败返回 -1
  • getppid
    获取进程的进程号
    成功返回父进程号,失败返回 -1

在linux中 父进程的父进程是shell ,可以通过以下命令查看shell的进程号

$ ps -aux | grep bash

1.3、让子进程执行新任务

由于子进程的代码是从父进程拷贝来的,所以一般情况下所做工作与父进程一样
可以用execve函数在进程中运行另一个程序

int execvp(const char *file, char *const argv[]);
  • 参数含义
    file: 待运行的程序名
    argv[] : 运行时的参数
    (可以有多个,以NULL结尾,第一个参数一般同file)

  • 返回值
    成功 无返回
    失败 -1

1.3.1 测试 execvp

  • 先写个hello程序

    #include <stdio.h>
    #include <unistd.h>
    int main(int argc,char* argv[]) {
        printf("\n******************************\n");
        if(argc < 2){
            printf("Too few parameters !\n");
        }
        else if(argc == 2){
            printf("Program name = \"%s\"\n",argv[0]);
            printf("The parameter is \"%s\"\n",argv[1]);
            printf("pid = %d   ppid = %d\n",getpid(),getppid());
        } else{
            printf("Too many parameters !\n");
       }
        printf("******************************\n\n");
        return 0;
    }
    
    $ gcc -o hello hello.c
    
  • execvp程序

    #include <stdio.h>
    #include <unistd.h>
    int main() {
        pid_t pid;
        pid = fork();
        if(pid<0){
            perror("fork");
        }
        else if(pid == 0){
    		//子进程
            printf("\nin child process,pid = %d\n",getpid());
            char *file = "./hello";
            char *argv[] = {file,"hello word",NULL};
            printf("use execvp\n");
            int ret = execvp(file,argv);
            printf("ret = %d\n",ret);
        }
        else{
            //父进程
            sleep(1);
            printf("in parent process,pid = %d\n",getpid());
        }
        return 0;
    }
    

    子进程先结束
    在这里插入图片描述
    父进程先结束 (调整sleep函数)
    在这里插入图片描述

  • 说明

    1、execvp调用并没有生成新进程
    2、一旦调用该函数,进程本身就结束了(子进程最后一段没有打印)
    3、调用该函数的进程只会保留进程ID,但对系统来说还是同一个进程
    4、exec类函数还有5个 他们都是调用 execve 这个系统调用来实现的
    5、在父进程结束后的子进程成为孤儿进程 会被init进程接收,init进程pid为 1

二、进程退出和等待

2.1、进程退出

2.1.1、_exit()

void _exit(int status);
  • status的值
    0: 正常退出,非0:异常退出
  • 函数功能
    1、关闭进程打开的所有文件描述符、目录描述符
    2、清除进程使用的内存空间
    3、将该进程的 ppid 设置为init 进程的pid
    4、向父进程发送SIGCHLD信号
    5、如果父进程调用wait或waitpid来等待子进程结束,则唤醒父进程,取得终止进程的status
    6、结束进程
  • 不会刷新IO缓存
    return 将控制权交给主调函数
    exit、_exit 将控制权交给系统

2.1.2、exit , on_exit

#include <stdlib.h>

void exit(int status);
int on_exit(void (*function)(int , void *), void *arg);
  • exit
    用法和_exit类似 但处理机制不一样 _exit只是exit要调用函数中的一个
    刷新IO缓存 (调用fflush)

  • on_exit
    注册一个进程正常终止前的处理函数 对_exit不生效
    返回值:成功 0,失败 非0
    function:表示要注册的函数 arg:表示要传入的指针,可以为NULL

  • 简单测试

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    char fun(int status, void *arg){
      printf("status = %d,arg = %s\n",status,(char*)arg);
      return '0';
    }
    int main() {
      on_exit((void *)fun,(void *)"hello");
      on_exit((void *)fun,NULL);
    
      printf("on_exit test\n");
      exit(123);
    }
    

    在这里插入图片描述

  • 说明

    1、 如果注释掉exit(),status会等于 0
    2、将exit换为_exit on_exit会失败
    3、推测注册方式为压栈

2.2、僵尸进程和进程等待

2.2.1、僵尸进程

  • 含义
    子进程比父进程终止:子进程为孤儿进程 ,这进程没马
    子进程比父进程终止:子进程为僵尸进程

  • 查看

    使用以下命令查看僵尸进程 STAT 中 Z+表示僵尸进程

    $ ps -u | grep Z
    
  • 处理
    1、父进程调用wait或waitpid函数(调用会阻塞自己),回收
    2、父进程将信号处理函数设为SIG_IGN 让内核去处理子进程
    3、fork两次,结束一级子进程,令二级子进程成为孤儿进程,让init去清理(不好操作)

2.2.2、进程等待函数

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
  • 参数说明
    wstatus:用于存放子进程的终止状态, 可以为NULL
    pid:子进程的进程号 若为 -1 表示等待任 一 子进程
    options:包括 WNOHANG、WUNTRACED、WCONTINUED (waitpid)
  • 返回值
    大于0 :子进程的进程号
    等于0:不阻塞 (waitpid)
    -1 出错
  • 测试

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <wait.h>
    
    int main() {
        int status1 = -2;
        int status2 = -2;
        pid_t pid;
        pid = fork();
        if(pid<0){
            perror("fork");
        }
        else if(pid == 0){
        	//子进程 1
            printf("in child1 process : pid = %d  ppid = %d\n",getpid(),getppid());
            _exit(0);//正常退出
        }
        else
        {
        	//父进程
           pid =  fork();
            if(pid<0){
                perror("fork");
            } else if(pid == 0){
            	//子进程 2
                printf("in child2 process : pid = %d  ppid = %d\n",getpid(),getppid());
                _exit(-1);//错误退出
            }
            else{
                //父进程
                printf("in parent process,pid = %d\n",getpid());
    
                printf("cpid = %d \t", wait(&status1));
                printf("status1 = %d\n", status1);
                sleep(1);//让子进程先结束
                
                printf("cpid = %d \t",wait(&status2));
                //printf("cpid = %d \t",waitpid(-1,&status2,0));
                printf("status2 = %d\n",status2);
            }
        }
        return 0;
    }
    
    

    在这里插入图片描述

  • 说明

    • wait(&wstatus) 相当于 waitpid(-1, &wstatus, 0)
    • 一个wait()只等待一个子进程结束
    • 若子进程未结束,则阻塞自己,直到有进程退出
    • 若子进程已经结束,调用wait依然可以回收

部分笔记来源:mooc

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值