Linux的进程详解(进程创建函数fork和vfork的区别,资源回收函数wait,进程的状态(孤儿进程,僵尸进程),加载进程函数popen)

 目录

什么是进程

 Linux下操作进程的相关命令

进程的状态(生老病死)

创建进程系统api介绍:

fork()

父进程和子进程的区别

vfork()

进程的状态补充:

孤儿进程

僵尸进程

回收进程资源api介绍:

wait()

waitpid()

exit()

popen


什么是进程

        一个程序是由源代码在编译后产生的,格式为ELF的,存储于硬盘的文件,在Linux中,程序文件的格式都是ELF,这些文件在被执行的瞬间,就被载入内存,所谓的载入内存,就是将数据段、代码段这些运行时必要的资源拷贝到内存,另外系统会再分配相应的栈、堆等内存空间给这个进程,使之成为一个动态的实体。而这个动态的实体,程序中的代码和数据,被加载到内存中运行的过程,就叫进程,程序是静态的,进程是动态的

 Linux下操作进程的相关命令

ps 查看当前终端运行的进程

ps -e 查看当前系统的所有进程

./ 程序名 运行当前目录下的程序 

top 查看当前Linux系统的进程运行情况

pstree 查看进程树

killall 杀死所有进程 后面加名字即为杀死所有叫这个名字的进程

kill 杀死一个进程 后面跟进程的pid即为杀死指定的pid进程

killall/kill -STOP 进程名/进程PID #暂停进程

killall/kill -CONT 进程名/进程PID #继续运行继承

进程的状态(生老病死)

文字是从某本书摘抄的,我觉得写的非常好(当然图也是)

创建进程系统api介绍:
fork()
 #include <sys/types.h>
 #include <unistd.h>

 pid_t fork(void);

创建一个子进程

成功时返回两个返回值

返回子进程pid,返回子进程pid时,执行父进程

返回值0,返回值为0时,执行子进程

创建失败时返回一个值为-1

返回值-1时不创建子进程

pid_t pid;  //pid_t跟int含义相同

if(pid == 0)
{
    //子进程的代码  
}
if(pid > 0)
{
    //父进程的代码
}
父进程和子进程的区别

父子进程的以下属性在创建之初完全一样:
A) 实际UID和GID,以及有效UID和GID。
B) 所有环境变量。
C) 进程组ID和会话ID。
D) 当前工作路径。
E) 打开的文件。
F) 信号响应函数。
G) 整个内存空间,包括栈、堆、数据段、代码段、标准IO的缓冲区等等。
而以下属性,父子进程是不一样的:
A) 进程号PID。PID是身份证号码,哪怕亲如父子,也要区分开。
B) 记录锁。父进程对某文件加了把锁,子进程不会继承这把锁。
C) 挂起的信号。这是所谓“悬而未决”的信号,等待着进程的响应,子进程不会继承这些信号。


子进程只会执行fork()语句下面的程序段,不会执行fork上的程序段

引用:”的确子进程包含有和父进程一样的代码和数据(虽然一样但的确是自己的一份)。 但别忘了,子进程复制的不仅是父进程的代码和数据,还包括状态,这个状态就包含有PC指针寄存器的值。 也就是说子进程创建完成后,他和父进程一样,PC指针都指向下一条语句, 因此子进程是从自身创建完成后的地方继续运行 ,而父进程运行过得代码将不再运行。“

参考文献:为什么fork创建子进程后,父进程中运行过的代码在子进程中不再运行了_fokk一个子进程后,父进程变量的值在子进程中仍然存在对吗-CSDN博客

分析下面的demo,判断printf打印了几次

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

int main()
{
    fork();
    fork();
    fork();
    printf("15678\n"); 
}

printf共打印8次,可以用一个阻塞函数阻塞程序运行,然后在Linux终端输入pstree查看原因


vfork()

创建一个子进程并阻塞父进程

fork()和vfork()的区别

1.资源复制方式:

fork:创建子进程时,子进程会复制父进程的数据段、堆栈段和代码段。虽然现代Linux通过写时复制(Copy-on-Write, COW)技术优化了这一过程,但本质上,父子进程拥有独立的地址空间。

vfork:子进程与父进程共享数据段。vfork创建的子进程基本上是一个轻量级进程,它不会复制父进程的地址空间,而是直接共享。

2.执行顺序:

fork:父子进程的执行顺序是不确定的。操作系统可能会先调度父进程或子进程运行。

vfork:保证子进程先运行。在子进程调用exec或exit之前,父进程不会运行。这确保了在子进程执行完毕或替换为新程序之前,父进程不会继续执行。

1.vfrok 保证子进程先执行,父进程后执行 

2.fork 父子进程的内存独立,vfrok 父子进程共享数据段 (常量,静态数据,全局变量)。

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

// 全局变量,存储数据段
int value = 0;

int main()
{
    // 1.创建一个子进程
    pid_t pid = vfork(); // 共享数据段
    //pid_t pid = fork(); // 父子进程的内存完全独立
    if (pid == 0)       // 子进程
    {
        while (1)
        {
            printf("%d 我是子进程pid是%d  我的爸爸是%d\n", value, getpid(), getppid());
            value++;
            sleep(1);
            if (value == 10)
            {
                printf("子进程结束\n");
                exit(0); // 子进程结束
            }
        }
    }

    //sleep(12);

    printf("父进程运行\n"); // 父进程被阻塞了,直到子进程结束
    if (pid > 0)            // 父进程
    {
        while (1)
        {
            printf("%d 我是父亲进程pid是%d  我的儿子是%d\n", value, getpid(), pid);
            value--;
            sleep(1);
        }
    }
}


可以看到在执行过程中父进程中的value的值受子进程的影响

进程的状态补充:
孤儿进程

如果一个子进程的父进程比子进程先死亡,那么这个子进程就会变成孤儿进程,由系统进程(init进程)进行管理

要防止孤儿进程的产生,父进程必须比子进程后死亡

僵尸进程

子进程比父进程先死亡,并且父进程未回收子进程的资源。那么子进程就会变成僵尸进程。
创建僵尸进程

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{

    for (int i = 0; i < 10; i++)
    {
        int pid = fork(); // fork 后父子进程谁先执行是不确定的,由操作系统调度决定
        if (pid == 0)
        {

            printf("我是子进程:%d ,我的父亲是:%d ,子进程死亡\n", getpid(), getppid());
            return 0; // 结束子进程
        }
    }

    printf("父亲继续运行,等待任意键结束\n");
    getchar(); // 挂起态

    printf("父进程结束\n");
    return 0;
}

使用ps -e查看僵尸进程

回收进程资源api介绍:
wait()

回收进程资源

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

pid_t wait(int *wstatus);

成功时返回回收资源的进程的pid号

失败时返回-1

wait一次回收一次任一资源(回收的资源是随机的)

可以通过wait获取进程的退出状态

有在wait(int *status)里放一个整形指针,如果 status 不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内,如果不关心终止状态,则可将该参数指定为空指针,如wait(NULL)

死亡原因由以下宏定义获取


因为wait执行一次只回收一次资源,所以想把所有子进程资源都回收需要不断循环
 

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

int main()
{
    // 1.创建一个子进程
    pid_t pid = fork();

    if (pid == 0) // 子进程
    {
        int i = 0;
        while (1)
        {
            printf("子进程执行中 %d\n", i++);
            if (i == 20)
            {
                printf("子进程结束\n");
                // 退出子进程
                return 5;
            }
            sleep(1);
        }
    }

    while (1) 
    {
        ret = wait(NULL);
        if (ret == -1) {
            if (errno == EINTR) {    // 返回值为-1的时候有两种情况,一种是没有子进程了,还有一种是被中断了
                continue;                  //如果是被中断了就continue继续执行
            }
            break;
        }
    }

    printf("当前子进程资源已全部回收"\n);
    printf("父进程为 %d\n", getpid());
}


通过wait获取退出状态

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

int main()
{
    // 1.创建一个子进程
    pid_t pid = fork();

    if (pid == 0) // 子进程
    {
        int i = 0;
        while (1)
        {
            printf("子进程执行中 %d\n", i++);
            if (i == 20)
            {
                printf("子进程结束\n");
                // 退出子进程,设置子进程退出状态
                return 5;
            }
            sleep(1);
        }
    }

    if (pid > 0) // 父进程
    {
        int status;
        printf("等待子进程结束\n");
        wait(&status);
        // 获取子进程的退出状态 (死亡原因)
        if (WIFEXITED(status)) // 判断子进程是否正常退出
        {
            printf("子进程正常退出\n");
            printf("退出状态码: %d\n", WEXITSTATUS(status)); // 获取子进程的return 值
        }

        // 获取是否被信号杀死
        if (WIFSIGNALED(status))
        {
            printf("子进程被信号杀死\n");
            printf("杀死子进程的信号: %d\n", WTERMSIG(status)); // 获取杀死子进程的信号
        }
    }
}

waitpid()

pid_t waitpid(pid_t pid, int *wstatus, int options); 等待指定的子进程结束

pid:等待的进程pid  
wstatus:进程的退出状态 
options:等待属性设置阻塞与非阻塞  ,默认为 0 阻塞 

exit()

void _exit(int status);
void exit(int status);

status 子进程的退出值
没有返回值

如果子进程正常退出,则 status 一般为 0。
如果子进程异常退出,则 status 一般为非 0

exit()退出时,会自动冲洗(fush)标准IO总残留的数据到内核,如果进程注册了“退出处理函数”还会自动执行这些函数。
而_exit()会直接退出。

popen

加载一个进程,并创建一个通信管道 (文件流指针)

FILE *popen(const char *command, const char *type);
command:需要加载的程序   
type:加载的权限   
"r"  :读取  
 "w" :写入 
 "e" :可执行
 返回值: 成功  文件流指针    
              失败   NULL                     
                             
int pclose(FILE *stream);  //关闭文件流指针

通过popen输入Linux命令行命令

#include <stdio.h>
int main()
{
    // 1.加载 ls -l 命令(程序)
    FILE *pf = popen("ls -l", "r");
    if (pf == NULL){
        printf("popen error\n");
        return -1;
    }
    else{
        printf("加载成功\n");
    }
    // 读取 ls -l  程序加载后的输出结果
    while (1)
    {
        char buf[1024] = {0};
        char *ret = fgets(buf, 1024, pf);
        if (ret == NULL)
        {
            break;
        }
        printf("%s\n", buf);
    }
    // 关闭程序
    pclose(pf);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值