Linux:进程控制

想要有顺序地学习Linux,入口在这里哦:Linux:目录索引

进程创建, 等待, 终止,使用代码实现

1.进程的创建:pid_t fork(void)

在看具体的实例之前,首先要知道有关fork函数的以下几点:
1.fork后,父进程和子进程交替运行
2.如果父进程死亡,子进程活着,子进程叫孤儿进程孤儿进程托管给1号进程
3.如果父进程活着,子进程死亡,子进程叫僵尸进程,僵尸进程会占用少量系统资源,僵尸进程是有害的;父进程结束,僵尸进程才会和父进程同时消失
4.父进程的fork函数返回创建出来的子进程的ID,子进程的fork函数返回0,关于为什么子进程的fork函数返回0,大致的解释是因为fork函数的返回值由寄存器eax保存,而子进程中的fork函数的结尾处会把寄存器eax置为0,详解可参看该链接:关于fork()父子进程返回值的问题

1.演示僵尸进程

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

int main()
{
    int i=0;
    pid_t pid = fork();
    if(pid == 0)//子进程中的fork函数返回值会是0,才会满足if条件,执行该部分代码
    {
        for(i=1;i<4;i++)//子进程运行3秒
        {
            printf("子进程正在执行%d秒\n",i);
            sleep(1);
        }
        exit(0);//执行完这部分就让子进程结束,不再执行后面的代码
    }
    for(i=1;i<10;i++)//父进程运行9秒
    {
        printf("父进程正在执行%d秒\n",i);
        sleep(1);
    }
}

这里写图片描述

2.演示孤儿进程

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

int main()
{
    int i=0;
    pid_t pid = fork();
    if(pid == 0)//子进程中的fork函数返回值会是0,才会满足if条件,执行该部分代码
    {
        for(i=1;i<10;i++)//子进程运行9秒
        {
            printf("子进程正在执行%d秒\n",i);
            sleep(1);
        }
        exit(0);//执行完这部分就让子进程结束,不再执行后面的代码
    }
    for(i=1;i<4;i++)//父进程运行3秒
    {
        printf("父进程正在执行%d秒\n",i);
        sleep(1);
    }
}

这里写图片描述

3.再深一层理解fork函数,父进程、子进程、孙子进程

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

int main()
{
    int i=0;
    pid_t pid1=fork();//获取父进程创建的子进程的ID
    if(pid1 == 0)
    {   
        for(i=1;i<4;i++)
        {
            printf("子进程正在执行第%d秒\n",i);
            sleep(1);
        }
        pid_t pid2=fork();//获取子进程创建的孙子进程的ID
        if(pid2 == 0)
        {
            for(i=1;i<4;i++)
            {
                printf("孙子进程正在执行第%d秒\n",i);
                sleep(1);
            }
        }
    }
    for(i=1;i<4;i++)
    {
        printf("父进程正在执行第%d秒\n",i);
        sleep(1);
    }
    return 0;
}

这里写图片描述
这里写图片描述

2.进程的等待(阻塞):pid_t wait(int* status)

看完上面进程创建的例子,你就会知道如何简单地控制进程的“寿命”,无非就是设置某个进程的大概运行时间,比如sleep();
上面的“演示孤儿进程”中,我们让子进程的运行时间比父进程的运行时间长,从而产生孤儿进程,了解这个后,我们就可以引申出“进程等待wait()”这个函数啦!
一句话概括wait函数的作用:在子进程结束之前,父进程处于阻塞状态,不运行,直到子进程结束,父进程才开始运行,并且开始回收僵尸进程
同样,在看具体实例之前,我们先要知道wait函数的以下几点:
pid_t wait(int* status);
1.这个函数会阻塞(等待),直到有子进程退出才返回
2.该函数返回后会得到一个进程是如何死亡的,即status,这设计到返回码的知识点,下面补充
3.★wait函数是专门给父进程回收僵尸进程的,活着的子进程是不会需要wait的,虽然父进程死亡的时候也会回收僵尸进程,但是这个时候父进程也就完了,不符合我们的使用目的
4.返回值:被回收的子进程的ID,出错返回-1

1.面试小题:如何使多个进程并发运行并且不存在僵尸进程

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

int main()
{
    pid_t pid=fork();
    if(pid == 0)//父进程不进入if
    {
        if(fork() > 0)//子进程创建孙子进程,并且子进程会进入这个if
        {
            exit(0);//子进程结束
        }
        for(;;)//孙子执行该死循环
        {
            printf("孙子\n");
            sleep(1);
        }
    }
    else//父进程进入else
    {
        wait(NULL);//父进程等待子进程死亡
        for(;;)//子进程死亡后,父进程开始运行,执行该死循环
        {
            printf("爷爷\n");
            sleep(1);
        }
    }
}
//这段代码实现的功能是:
//父进程创建子进程,子进程创建孙子进程
//杀死子进程,父进程中的wait函数处理掉僵尸进程
//父进程和孙子进程仍存活,并发运行,只不过此时孙子进程属于孤儿进程

这里写图片描述

2.返回码

回到上面提到的“wait函数返回后会得到一个进程是如何死亡的,即status”
pid_t wait(int* status);

eg:
int num;
wait(&num);
//num会被赋值为某个子进程的退出码
//其实这个num存储了某个进程的退出信息
//而第8位到第16位这八个位用来存储退出码的信息
//验证方式如下:
printf("%d\n",((num>>8)&0xFF));
//eg:exit(3),打印值为3
//因为只有八个位表示退出码,且是无符号整型,所以范围是0~255

3.wait函数的亲戚waitpid

pid_t waitpid(pid_t pid, int *status, int options);
第一个参数pid的情况:
1.pid > 0:等待进程id等于pid的子进程死亡
2.pid = 0:调用者进程所在进程组的任意一个子进程死亡
3.pid = -1:等待任意一个子进程死亡
4.pid < -1:等待进程组id等于|pid|的进程组中任意一个子进程死亡

3.进程的终止:正常退出和异常退出

1.销毁进程的过程:

1.释放资源,例如内存,进程中打开的文件等
2.记账信息,例如某个程序在系统上何时运行,运行所用资源等
3.将进程设置成僵尸状态
4.转存储调度,即将CPU让给别的进程使用

2.进程退出的方法:

正常退出的方式:
1.main函数退出
★2.exit退出
异常退出的方式:所以异常退出都是由信号引起的,即系统发送信号杀死进程
1.ctrl + c
2.abort 跟exit功能相似
3.kill

3.exit退出的过程

1.执行退出处理函数
2.刷新缓存
3.调用_exit
补充:每个函数都有自己的属性
eg:

void fun(void)  __attribute__((constructor));
//声明该函数,并且使该函数的执行在main函数前
void fun(void)  __attribute__((destructor));
//声明该函数,并且使该函数的执行在main函数后

编写自主shell

详见:Linux:模拟实现shell

fork、execvp、popen和system的比较

1.fork创建子进程,以及对写时拷贝的解释

fork() 一个程序一调用fork函数,系统就为一个新的进程准备了前述三个段:
1.首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的,对于数据段和堆栈段,系统则复制一份给新的进程,这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。而如果两个进程要共享什么数据的话,就要使用另一套函数(shmget,shmat,shmdt等)来操作。
2.现在,已经是两个进程了,对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零,这样,对于程序,只要判断fork函数的返回值,就知道自己是处于父进程还是子进程中。
3.事实上,目前大多数的unix系统在实现上并没有作真正的copy。一般的,CPU都是以“页”为单位分配空间的,象INTEL的CPU,其一页在通常情况下是4K字节大小,而无论是数据段还是堆栈段都是由许多“页”构成的,fork函数复制这两个段,只是“逻辑”上的,并非“物理”上的,也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区 别,系统就将有区别的“页”从物理上也分开。系统在空间上的开销就可以达到最小。

2.exec修改子进程

对于exec系列函数 一个进程一旦调用exec类函数,它本身就“死亡”了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。不过exec类函数中有的还允许继承环境变量之类的信息,这个通过exec系列函数中的一部分函数的参数可以得到。

3.popen

对于popen函数,他会通过command参数重新启动shell命令,并建立两个进程间的管道通信.
详见:popen函数_Linux C 中文函数手册

4.system

对于system函数,它也会重新启动shell命令,当执行完毕后,程序会继续system下一行代码执行.
详见:system函数_Linux C 中文函数手册

注:以上内容摘自:popen system fork exec等函数的区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值