linux sleeping进程多_Linux多进程编程入门(代码)

e491946f8f68486aa356eb4d6ac83fe5.png

源自CSDN

CSDN-专业IT技术社区-登录​blog.csdn.net

一、基础知识

1.1、进程的概念

一个程序文件, 只是一堆待执行的代码和部分待处理的数据,它们只有被加载到内存中,然后让CPU逐条执行其代码,根据代码做出相应的动作,才形成一个真正“活的”、动态的进程(Process)

因此, 进程是一个动态变化的过程,是一出有始有终的戏,而程序文件只是这一系列动作的原始蓝本,是一个静态的剧本

  1. 进程就是程序在内存中动态执行的过程
  2. 进程是系统资源管理的最小的单位
  3. 进程是动态的概念,创建—运行--消亡
  4. 每个进程有4G独立的进程空间,其中0-3G是用户空间,3G-4G是内核空间。每个进程也有4G地址空间的,仅仅是地址空间,不是实际的内存,需要使用时,向系统申请
  5. 进程是独立可调度的任务,绝大多数的操作系统都支持多进程

1.2、多进程(任务)并行的实现

进程我们了解了,其实就可以说是一个后台运行的任务, 而多进程(多任务)并行就好比如我们的电脑可以上谷歌浏览器、微信、网易云音乐 ,它们都是一个个任务,在执行各自的功能, 看上去它们“同时”一起运行

但是实际上,对于一个单核CPU来讲,从宏观上是并行的,而从微观上是串行的,它使用时间片划分周期调用来实现,每个任务在一段时间内会分到一段时间片(占cpu的时间),在这段时间内该任务只能运行时间片长度,每个任务执行一点每个任务执行一点,从而达到“同时”的效果

而每个任务是如何调度和切换的? 这是由系统调度器来实现 , 所以使得任务有多种状态,如下

 就绪状态: 未占到CPU, 进程准备好了,等待系统调度器调度。
 运行状态: 占到CPU  , 已经开始运行。
 暂停状态: 没占,收到外部暂停信号,暂停运行 (不在参与任务调度)
 挂起(睡眠)状态: IO资源不满足, 导致进程睡眠。 (不在参与任务调度)(例如键盘输入)
 僵尸状态: 进程已经结束, 但是资源(内存、硬件接口)没有回收。

096f951bc473ba61b670bdea8d4f8c5b.png

就这样, 任务被切来切去, 我一点你一点(指的是cpu抢占时间),最后达到宏观上的并行运行​​

1.3、重要指令

(1) ps ——— 查看进程信息

    ps aux ——— 显示系统所有的进程
    ps -elf  ———   显示系统所有的进程(通用)

(2)top ————   动态查看进程信息

(3)pstree ———— 查看父子关系结构的进程

(4)kill -9 进程号   ———   杀死进程。(通过信号)

1.4、父子进程和进程ID

Linux中的进程都是由其它进程启动。如果进程a启动了进程b, 所以称a是b的父进程, b是a的子进程
Linux启动时,0进程启动1号进程(init )和2号进程(内核线程), 0号进程退出, 其它进程是由1、2直接或间接产生1号进程(init ) 所有用户进程的祖先2号进程(内核线程) 内核进程的祖先

进程号 PID (process ID)(类型pid_t , 什么什么_t 都是正整数) :
每个任务拥有唯一ID, 由操作系统管理和分配
每个进程创建会分配一个ID , 结束会取消一个ID
取消的那个ID会延时重复使用 , 但不会同时出现同一个ID
相关函数:

        函数getpid() 获取本进程的ID

        函数getppid() 获取父进程的ID (get perent pid)

(小彩蛋) 想让程序在后台运行执行指令

./执行文件 &

二、多进程编程

2.1创建子进程 (fork/vfork 叉子)

2.1.1 fork

d47e2c4f35574f1a2cff3786b9f45385.png

相信大家有用过叉子,它有“一分为二”的形状, 而我们今天要介绍的是fork函数 , 它也有这样的能力,调用fork函数时,复制父进程的进程空间来创建子进程

e9d749de48a0b086e1d4e0a3b5e9ba54.png

上图是父进程的进程空间,其中代码段是不会不复制到子进程的,而是共享,其它段需要复制,属于写拷贝 (即只有改的时候, 才需要拷贝),这样提高效率, 节省资源,总而言之,相当于克隆了一个自己

现在我们要让它们分别干不同的事,在fork函数执行完毕后,则有两个进程,一个是子进程,一个是父进程,在子进程中,fork函数返回0,在父进程中,fork返回子进程的进程ID,因此, 我们可以通过fork返回的值来判断当前进程是子进程还是父进程,从而让它们同时干不同的事情

示例如下(fork):

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

int main()
{
    printf("+++process %d start running!ppid = %dn",getpid(),getppid());

    pid_t pid = fork();

    if(pid)//父进程
    {
        printf("parent:process %d start running!ppid = %dn",getpid(),getppid());
        //do something
        //...
    }
    else//子进程
    {
        printf("child:process %d start running!ppid = %dn",getpid(),getppid());
        //do something
        //...
        exit(0);
    }

    exit(0);
}

注意⚠️:如果父进程提前结束,那么子进程就会变成孤儿进程 (知识点在下一节2.3)

2.1.2 vfork

另外还有一个函数vfork,但是子进程和父进程是不能同时运行的,由于函数不复制父进程的进程空间, 而是抢占父进程的资源, 导致父进程堵塞, 无法继续运行,子进程完成后, 父进程才能继续运行

示例如下(vfork):

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

int main()
{
    printf("+++process %d start running!ppid = %dn",getpid(),getppid());

    pid_t pid = vfork();

    if(pid){//父进程
        printf("parent:process %d start running!ppid = %dn",getpid(),getppid());
        printf("parent:process %d finish running!ppid = %dn",getpid(),getppid());
    }
    else{//子进程
        printf("child:process %d start running!ppid = %dn",getpid(),getppid());
        printf("child:process %d finsish running!ppid = %dn",getpid(),getppid());
        exit(0);
    }

    exit(0);
}

2.2进程结束

学会创建子进程后

我们来学习一下进程结束

不管是子进程还是父进程

进程的退出分为正常退出和异常退出


2.2.1正常退出

进程的正常退出有四种,如下:

1、return 只是代表函数的结束, 返回到函数调用的地方。

2、进程的所有线程都结束。

3、exit() 代表整个进程的结束,无论当前执行到哪一行代码, 只要遇到exit() , 这个进程就会马上结束。

4、 _exit() 或者 _Exit() 是系统调用函数。

_exit() / _Exit 和 exit 的区别:

_exit() / _Exit 是 系统调用函数, exit 是库函数

exit 它是通过调用_exit()来实现退出的

但exit() 多干了两件事情: 清空缓冲区、调用退出处理函数

退出处理函数:

进程正常退出,且调用exit()函数,会自动调用退出处理函数

退出处理函数可以做一些清理工作

需要先登记才生效,退出处理函数保存在退出处理函数栈中(先进后出的原则)

示例如下(退出处理函数):

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

void func1(void)
{
    printf("%sn",__func__);
}

void func2(void)
{
    printf("%sn",__func__);
}

void func3(void)
{
    printf("%sn",__func__);
}

int main()
{
    atexit(func1);//先登记
    atexit(func2);
    atexit(func3);

    printf("hello!");

    exit(0);
    //_exit(0); //无法调用退出处理函数
    //return 0; //无法调用退出处理函数
}

输出结果:

hello!func3
func2
func1

2.2.2异常退出

进程除了正常退出, 还有异常退出

1、被信号打断( ctrl + c ,段错误 , kill -9)

2、最后线程(主线程)被取消。

2.3等待进程结束并资源回收

子进程退出时, 不管是正常还是异常, 父进程会收到信号

子进程退出后,内存上的资源必须是父进程负责回收

但是有时候会出现下面两种情况 :

1、子进程先结束, 会通知父进程(通过信号), 让父进程回收资源 , 如果父进程不处理信号, 子进程则变成僵尸进程

2、父进程先结束,子进程就会变成孤儿进程, 就会由1号进程(init )负责回收,但在实际编程中要避免这种情况, 因为1号进程很忙

2.3.1 wait函数

wait函数等待子进程的结束信号

它是阻塞函数,只有任意一个子进程结束,它才能继续往下执行,否则卡住那里等

它获得结束子进程的PID以及 退出状态/退出码 , 并且回收子进程的内存资源

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

pid_t wait(int * status);

status是传出参数, 传出退出状态/ 退出码

355515c2218331b0ab29e7a820938847.png

演示代码(wait):

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

int main()
{
    pid_t pid = fork();
    if(!pid){//子1
        printf("child %d start running!n",getpid());
        sleep(10);
        printf("child exit!n");
        exit(10);
    }

    pid = fork();
    if(!pid){//子2
        printf("child %d start running!n",getpid());
        sleep(15);
        printf("child exit!n");
        exit(30);
    }

    //父进程
    //等待子进程结束
    int status = 0;
    pid_t pid1 = 0;
    pid1 = wait(&status);//等待任意一个子进程的结束
    if(WIFEXITED(status)){
        printf("%d正常结束!退出码 = %dn",pid1,WEXITSTATUS(status));
    }
    if(WIFSIGNALED(status)){
        printf("%d被信号打断!信号 = %dn",pid1,WTERMSIG(status));
    }

    pid1 = wait(&status);//等待任意一个子进程的结束
    if(WIFEXITED(status)){
        printf("%d正常结束!退出码 = %dn",pid1,WEXITSTATUS(status));
    }
    if(WIFSIGNALED(status)){
        printf("%d被信号打断!信号 = %dn",pid1,WTERMSIG(status));
    }

    return 0;
}

2.3.2 waitpid 函数

wait函数的加强版

可以选择等指定哪个子进程 , 还可以选择等待方式(可以选择堵塞、不堵塞)

37d298bd1e8d2fdba1a8fb6d20218e88.png

代码示例(waitpid):

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

int main()
{
    pid_t pid1 = fork();
    if(!pid1){//子1
        printf("child1  %d start running!n",getpid());
        sleep(1);
        printf("child1 exit!n");
        exit(10);
    }

    pid_t pid2 = fork();
    if(!pid2){//子2
        printf("child2  %d start running!n",getpid());
        while(1);
        printf("child2 exit!n");
        exit(30);
    }
    
    //父进程
    //等待子进程结束
    int status = 0;
    pid_t pid = 0;
    printf("等待child2结束 n");
    pid = waitpid(pid2,&status,0);//指定子进程,堵塞
    if(pid==0){
        printf("没有等到子进程!n");
        return -1;
    }
    printf("child2结束 n");

	
    if(WIFEXITED(status)){
        printf("%d正常结束!退出码 = %dn",pid1,WEXITSTATUS(status));
    }
    if(WIFSIGNALED(status)){
        printf("%d被信号打断!信号 = %dn",pid1,WTERMSIG(status));
    }


    return 0;
}

编译

gcc 5waitpid.c -o 5waitpid

在后台运行

./5waitpid &

输出

等待child2结束 
child1  3966 start running!
child2  3967 start running!
child1 exit!

回车几下

waitpid此时还在等child2退出

用信号杀死进程

kill -9 3967

输出

child2结束 
3966被信号打断!信号 = 9
[1]+  Done                    ./5waitpid

可见,我们已经成功指定哪个子进程

(小彩蛋)

有时候忘记回收子进程资源

或者你的代码不是很完美

那么

该怎么一键收回父进程所有的僵尸子进程的资源呢?

while(waitpid(-1, NULL, WNOHANG)> 0);

2.4替换进程内容(exec系列函数)

fork/ vfork 产生的子进程内容和父进程完全一致, 但是在很多时候, 我们希望新的子进程去执行全新的程序

而exec系列函数就提供了这样的功能,使用一个程序去替换进程的内容 (不会产生新的进程,是替换 )

单独使用没有意义,一般 是和fork/ vfork 连用。 用fork/ vfork 产生子进程, 然后用exec替代

使用vfork堵塞父进程, 抢了资源, 但是使用exec后, 子进程替换了内容, 便不抢占资源了,父进程继续执行,不用等子进程

e384cb65041c2b853fbce75b175e2d34.png

exec系列函数太多

这里讲常用的一个

代码如下(execl):

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

int main(int argc,char *argv[],char *env[])
{
    printf("pid = %dn",getpid());
    pid_t pid = vfork();
    if(!pid){//子
        printf("child pid = %dn",getpid());
        int res = execl("./tst","tst",NULL);//用这个程序替换掉子进程内容
        if(res==-1){
            perror("execl");
        }

        exit(0);
    }

    //父
    printf("parent running!n");
    sleep(1);//....
    wait(NULL);
    printf("%d end!n",getpid());
    return 0;
}

8680c389c9c2515f3c1ce3c73693f8f6.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值