LinuxC进程通信-子进程

本文详细探讨了进程的基本概念,包括程序与进程的关系、进程分类、进程查看与控制方法,以及父进程子进程、孤儿进程和僵尸进程的管理。重点讲解了fork、exec系列函数的使用,进程的正常与异常退出,以及子进程的回收机制。
摘要由CSDN通过智能技术生成

进程的基本概念:
1、程序与进程
程序就是存储在磁盘上的可执行文件,当程序被加载到进程中开始运行时就叫做进程,
一个程序可以被多次加载生成多个进程,进程就是处于活动状态的计算机程序
2、进程的分类
进程一般分为三种类型:交换进程、批处理进程、守护进程
守护进程一般处于活跃状态,运行于后台,由操作系统在开机时通过启动脚本自动创建
3、查看进程
简单形式: ps 显示当前用户的有终端控制的进程信息
列表形式: ps -auxw 显示出所有进程的详细信息
a 所有用户有终端控制的进程
x 无终端控制的进程
u 显示进程的详细信息
w 以更宽的列长显示

            USER    进程属主
            PID     进程号
            %CPU    CPU的使用率
            %MEM    内存的使用率
            VSZ     虚拟内存使用的字节数
            RSS     物理内存使用的字节数
            TTY     终端设备号     ?表示无终端控制
            STAT    进程状态
            *    O 就绪,等待被调度
            *    R 运行态,在Linux中没有O,就绪也用R表示
            *    S 可被唤醒的睡眠,例如系统中断、获取资源、收到信号时也可以唤醒该进程,转入运行状态
            *    D 不可被唤醒的睡眠,只能被系统唤醒
                T 暂停态,收到SIGSTOP信号进入暂停态,收到SIGCONT信号转入运行态
                W 等待内存分页(Linux 2.6 后废除)
                X 死亡态
            *    Z 僵尸态 僵死
                < 高优先级
                N 低优先级
                l 多线程的进程
                s 进程的领导者
                L 有内存页被锁在内存中
                + 位于后台的进程组

            START   进程启动的时间
            TIME    进程运行的时间
            COMMAND 启动进程的命令
        
        4、父进程、子进程、孤儿进程和僵尸进程
            一个进程是可以被另一个进程创建,创建者叫做父进程,被创建者叫做子进程,子进程
            被父亲进程启动后会在操作系统的统一调度下同时运行
            僵尸进程:该进程已死亡,但是它的父进程没有立即地回收它的相关资源,该进程就会进入
            到僵尸状态
            孤儿进程:父进程先与子进程结束,子进程就变成了孤儿进程,孤儿进程会被孤儿院(init 守护进程)
            领养,init就是孤儿进程的父进程
        
        5、进程表示符
            每个进程都有一个非负整数表示唯一的标识,即进程ID/PID 
            进程ID在任意时刻内都是唯一的,但是可以重用,进程一旦结束后它的ID会被系统回收,但是过一段时间后才
            重新分配给其它新创建的进程使用(延时重用)
            #include <sys/types.h>
            #include <unistd.h>

            pid_t getpid(void);
            功能:获取当前进程的ID
            pid_t getppid(void);
            功能:获取父进程ID 
            inti进程的ID永远是1

创建进程:
#include <unistd.h>
pid_t fork(void);
功能:创建子进程
返回值:一次调用两次返回,子进程返回0,父进程会返回子进程的ID,当进程的数量超过系统的
限制时会创建失败,会返回-1给调用者

该函数调用后父子进程各自独立运行,谁先返回不确定,但是可以通过睡眠确定让哪个进程先执行
可以根据返回值的不同,让父子进程进入不同的分支语句,执行不同的代码

通过fork函数创建的子进程会拷贝父进程的数据(数据段、bss段、堆、栈、IO流缓冲区),与父进程共享代码
子进程,会继承父进程的信号处理方式

通过fork函数创建的子进程可以共享父进程的文件描述符

    注意:为了防止因为共享代码fork会导致产生不想要的子进程,所以使用pause睡眠的方法
    让子进程不受其他fork的影响


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

pid_t vfork(void);
功能:以加载可执行文件的方式来创建子进程
返回值:子进程返回0,父进程返回子进程的pid

子进程先返回,但是此时子进程并没有创建成功,需要加载一个可执行程序来替代当前子进程的所有资源,
替换完成后子进程才算创建成功,此时父进程才能返回

使用exec系列函数加载可执行文件:
extern char **environ;

int execl(const char *path, const char *arg, ...);
path:可执行文件的路径
arg:命令行参数,一般第一个是可执行文件的名字,至少要有一个,一般以NULL结尾
execl("a.out","a.out","-1","-s",NULL);

int execlp(const char *file, const char *arg, ...);
file:可执行文件的名字,会根据PATH环境变量的值查找同名的可执行文件
    可以添加PATH的值
        1、vim ~/.bashrc
        2、在末尾添加export PATH=$PATH:/home/ubuntu 
        3、保存后退出,source ~/.bashrc 生效
        4、把可执行文件添加到目录中
arg:数组指针

int execle(const char *path, const char *arg,..., char * const envp[]);
path:可执行文件的路径
arg:指针数组
envp:环境变量表,父进程可以在加载子进程时,把一张环境变量表传递给子进程

int execv(const char *path, char *const argv[]);
path:可执行的文件路径
argv:指针数组

int execvp(const char *file, char *const argv[]);
file:可执行文件的名字,会根据PATH环境变量的值查找同名的可执行文件
argv:指针数组

int execvpe(const char *file, char *const argv[],char *const envp[]);
file:可执行文件的名字,会根据PATH环境变量的值查找同名的可执行文件
argv:指针数组
envp:环境变量表

    注意:exec系统函数执行正常是不会返回的,当加载可执行文件时才会返回-1.通过exec系统函数
    创建的子进程不会继承父进程的信号处理函数,但是能够继承父进程的信号屏蔽集
    fork vfork 创建的子进程,都可以使用exec系统函数加载可执行文件

进程的正常退出:
1、在main函数中执行return n,该返回值可以被父进程接收到
2、进程调用了exit函数,该函数是标准库函数
#include <stdlib.h>
void exit(int status);
功能:在任何时候调用此函数都可以结束进程
status:结束状态码
EXIT_SUCCESS
EXIT_FAILURE
效果与main函数中return的返回值一样
返回值:该函数不会返回

    int atexit(void (*function)(void));
    功能:注册一个进程结束时要执行的函数

    int on_exit(void (*function)(int , void *), void *arg);
    功能:注册一个进程结束时要执行的函数


    进程退出前完成:
        1、想调用atexit/on_exit函数注册的函数,注册多个函数时,执行顺序与注册顺序相反(可重复绑定)
        2、冲刷并关闭所有的打开状态的标准IO流
        3、该函数的实现调用了_exit/_Exit 
3、手动调用_exit/_Exit 
    #include <unistd.h>
    void _exit(int status);
    功能:结束进程,由系统提供

    #include <stdlib.h>
    void _Exit(int status);
    功能:结束进程,由标准库提供
    1、它们的参数都会被父进程获取到
    2、进程结束前会关闭所有打开状态下的文件描述符(系统IO)
    3、会向父进程发送SIGCHLD信号
    4、该函数不会返回
4、进程的最后一个线程执行返回语句
5、进程的最后一个线程调用了pthread_exit函数

进程的异常退出:
1、进程调用了abort函数,产生产生SIGABRT
2、进程接收到某些信号,可以是其他进程发送的,也可能是自己的错误操作导致的
3、集成的最后一个线程收到"取消"操作,并且还做出响应
这三种结束方式,会让父进程无法获取子进程的状态结束码,所以才叫做异常终止

注意:无论进程是如何结束的,它们最后都会执行一段代码,关闭所有打开的文件描述符,释放内部。

子进程的回收:
对于任何结束方式,都希望能够让父进程知道,通过wait、waitpid函数可以知道子进程是如何结束的
以及结束状态码
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:等待子进程结束,并获取结束状态码
返回值:结束的子进程的ID号
1、如果所有的子进程还在运行,则阻塞
2、如果有一个子进程结束了,立即返回该子进程的状态码和pid
3、如果没有进程则立即返回-1

    WIFEXITED(status)   判断进程是否是正常返回,如果是返回真
    WEXITSTATUS(status) 如果进程是正常结束的,才可以获取到正确的结束状态码,只获取低八位
    WIFSIGNALED(status) 判断进程是否异常结束,如果是返回真
    WTERMSIG(status)    如果进程是异常结束的,可以获取到杀进程的信号

    注意:由于wait函数可能会阻塞,因此不适合在正常的业务逻辑中调用函数,可以为SIGCHLD信号注册
    一个信号处理函数,在处理函数中调用wait,因为子进程结束后都会向父进程发送该信号。

pid_t waitpid(pid_t pid, int *status, int options);
功能:指定回收某个或某些进程
pid:
    < -1    等待abs(pid) 进程组的进程结束
    -1      等待任意子进程结束,功能与wait等价
    0       等待同组的任意进程结束
    >0      等待该进程结束
status:结束状态码,与wait的等价
options:
    WNOHANG     非阻塞模式,如果要等待的进程没有一个结束,立即结束
    WUNTRACED   如果有进程处于暂停状态,则返回该进程的状态码
    WCONTINUED  如果有进程从暂停转为继续运行,则返回该进程的状态

    WIFSTOPPED(status)  判断进程是否处于暂停状态,如果是返回真
    WSTOPSIG(status)    如果有进程处于暂停状态,则返回导致暂停的信号
    WIFCONTINUED(status) 判断该进程是否由暂停转为继续运行,如果是返回真


int system(const char *command);
功能:执行一个可执行文件,本质是创建一个子进程去加载可执行文件
返回值:子进程结束后才返回
该函数的实现底层调用了fork、exec系列函数,waitpid函数,其实让进程创建了一个子进程,
然后让子进程去加载了command可执行文件
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值