进程与进程函数

目录

进程与程序

1.1进程是什么

1.2程序,进程之间的关系

1.3进程的生存环境

1.4进程的状态转换

1.5关于内核层与用户层

1.6保存和恢复处理器现场

进程原语

2.1fork()

​编辑

2.1.1父子进程的继承

2.1.2父子进程共享fork()栈帧

2.1.3打印进程id和父进程id

2.1.4循环创建进程

2.1.5fork()版本

2.2execl()

2.3wait()

2.4waitpid()


进程与程序

1.1进程是什么

进程是一个任务执行单元(逻辑单元)。

1.2程序,进程之间的关系

程序是静态数据(静态表现)存储在磁盘中。

进程是逻辑执行单位在cpu,内存,磁盘帮助用户使用系统资源完成特定任务。

程序是进程的静态表现,进程是程序的动态表现。

创建一个进程

生成可执行程序

我们发现把可执行程序删除后,进程依旧进行。

如果进程执行时,没有回访文件数据,那么这些文件数据都可以删除,程序文件与进程是没有必然联系的。

1.3进程的生存环境

内核层(系统层)->PCB(进程控制块)pid-----------------------------(概括一下就是:存储进程信息)

用户层->命令行参数(int argc char** argv),环境变量(系统默认),栈(0xc3000),库,堆,DATA(已初始化的全局静态变量),BSS(未初始化的全局数据),Text 代码段------------------------(概括一下就是:存储进程业务逻辑和任务数据)->映射机制,虚拟内存映射到物理内存,内存管理器有一个虚拟内存映射表。

x32位系统用户层和内核层有两种分配方式,0~3G 3~4G,0~2G 2G~4G,32位系统可用3级指针寻址。

x64位系统0~8T 8~16T,64位·系统是四级间接寻页。

每个进程通过映射方式访问物理内存,可以后序通过交换机制将闲置内存给别人使用,最大化利用内存,虚拟内存概念,可以避免用户直接访问物理内存。

每个进程执行时拥有独立的物理内存,进程资源独享。

内核空间映射内存是共享内存。用户空间映射内存是独立内存。

内存的基本单位是page,一般是4k。

内存的权限:PROT_READ只读,PROT_WRITE只写,PROT_EXEC执行,PROT_NONE都不行

1.4进程的状态转换

就绪态:进程准备就绪等待cpu资源而后开始运行。

终止态:进程结束退出,进程资源释放。

运行态:进程得到时间片开始执行。

阻塞态:放弃时间片,可以被强制中断EINTR,阻塞的进程资源在内存中。

挂起态:放弃时间片,无法被强制中断,挂起的进程资源被交换到外存中。

(僵尸态Linux下)

(孤儿态Linux下)

例子:当前进程a占用cpu,现在要把cpu释放交给进程b,怎么办?sleep(0);sleep释放cpu,进程有就近原则就是b,sleep让进程切换成阻塞态放弃cpu。

就绪态可以切换到运行态,运行态可以切换到就绪态。

挂起态可以切换到就绪态,阻塞态可以切换到就绪态。

进程在任意一种状态都能切换到终止态。

1.5关于内核层与用户层

电脑中所有设备都是cpu来访问控制的。

cpu有寄存器,运算器,控制器,译码器。

电脑中所有设备都是cpu来访问控制的,用户层执行以低权限使用cpu系统资源和设备,内核层执行以高权限使用cpu系统资源和设备。(系统调用就是cpu的权限切换)

1.6保存和恢复处理器现场

假设一个进程在执行指令的过程中耗尽时间片,那么会触发中断,保存处理器现场,内核栈->用于保存恢复处理器现场,这样保证再多的进程共享寄存器也不会引发异常。

进程原语

操作系统提供的一些列系统函数接口,让用户完成进程开发。

2.1fork()

父进程调用fork()创建一个子进程。

在Linux操作系统中几乎所有进程都有父亲。

根进程,刚开机就创建出来了。就根进程没父进程。根进程也称为init进程,系统的1号进程。

强亲缘关系:子进程从被创建直到进程结束,父进程全程参与。

如果是多级别的父子进程那么具备一定的亲缘关系(继承)。

fork();返回值类型是pid_t。

父进程从代码的起始位置执行到末尾,子进程从fork之后执行到末尾。

ps -ajx查看亲缘关系。

2.1.1父子进程的继承

在创建子进程之后,父进程将部分内容继承给子进程,子进程执行。

拷贝继承:

子进程可以访问父进程所有资源,无论是堆栈数据还是代码段。

父进程的资源数据完全拷贝给子进程,子进程任意访问修改与父进程无关。

2.1.2父子进程共享fork()栈帧

克隆完之后返回子进程id,父进程接收到然后返回。fork()它自己有个返回值,子进程来的时候最后执行了一个return 0。

因为printf会将输出缓存到缓冲区中,所以有可能在子进程中的输出还没有刷新到屏幕上之前,两个进程都已经执行了后续的代码。所以先输出那个666了。

2.1.3打印进程id和父进程id

getpid()当前进程id,父进程id。

2.1.4循环创建进程

如果在循环中不加是子进程就退出,那会创建出2^{n}-1个进程。

如图:

如果加上就正常了,不能让子进程也进入循环。

2.1.5fork()版本

第一版本:

采用拷贝继承,父进程拷贝资源是强制性的,如果子进程不需要父进程资源,那么这个拷贝开销毫无意义。

第二版本:

使用vfork函数,不会产生用户层拷贝开销,一定需要结合execl函数使用。

第三版本:

可以察觉子进程是否需要资源,读时共享,写时复制。

有一个映射内存,可以让子进程来访问父进程资源,如果子进程尝试访问修改映射内存数据,触发写复制机制,父进程将特定数据拷贝给子进程。写时复制,父子对映射内存数据写,都触发写时复制。父进程修改数据为了保证子进程不异常,子进程要复制之前的数据。

2.2execl()

进程功能重载。

重载可以复制其他进程的功能,无需对程序进行代码实现,重载即可。

which 命令名#寻址命令。

execl(命令的绝对路径,命令第一个参数,命令第二个参数,...,命令第n个参数,哨兵节点(NULL));

execl,预启动命令,重载目标用户层,但是目标程序没有被执行。重载成功后子进程代码被覆盖,最终程序是在重载程序中的代码段结束。如果子进程想要执行自己定义代码,要在fork之后execl之前完成。

重载ls -l

先找到绝对路径。

#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
    pid_t pid;
    pid=fork();
    if(pid>0)
    {
    printf("father pid success %d",getpid());
    pid_t wpid = wait(NULL);
    printf("wait pid success %d",wpid);
    }
    else if(pid==0)
    {
    printf("A\n");
    execl("/usr/bin/ls","ls","-l",NULL);
    printf("B\n");
    exit(4);
    }
    else exit(0);
    return 0;
}

execl可以便于功能拓展,使用极少的代价重载使用复杂功能。可以实现主程序不动的情况下,动态迭代,只需要让主程序定时重载即可。

代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
    pid_t pid;
    pid=fork();
    if(pid>0)
    {
     while(wait(NULL)>0)
     {
         sleep(1);
       pid=fork();
      if(pid==0) goto biu;
     }
    }
 else if(pid==0){
biu:         execl("/home/zzj/doit","doit",NULL);
printf("dddd\n");
 }
    else exit(0);
    return 0;
}   
#include<stdio.h>
int main()
{
    printf("hhhhhhh\n");
}

改了之后就这样。

2.3wait()

进程结束后需要对其进行回收,否则该进程会称为僵尸进程,引发内存泄漏。

当进程结束后,PCB未被释放,导致内存泄漏,这种现象被称为僵尸进程。。

Linux或Unix操作系统中,子进程只能由父进程回收。exit(0)调用操作系统中_EXIT(),用户空间内存被完全释放,也会释放部分内核空间,但是残留PCB导致内存泄漏,父进程负责回收释放子进程PCB,对子进程进行验尸操作,了解子进程的退出原因。

wait()函数返回值是pid_t。回收成功返回僵尸进程的pid。没有子进程尝试回收,wait立即失败返回。wait函数是阻塞回收函数,等待子进程结束后立即回收,它可以回收任意一个子进程,每次回收一个僵尸进程。验尸:pid_t zpid=wait(int * status);可以传出子进程退出原因,不验尸:pid_t zpid=wait(NULL);

让子进程变为僵尸进程,然后父进程回收

这里看见进程已经被回收了。

2.4waitpid()

高级回收函数。

pid_t zpid=waitpid(pid_t pid,int* status,WNOHANG);

返回值>0回收成功,返回值为僵尸的pid。返回时-1,表示回收失败,返回值0,表示当前子进程未结束,非阻塞返回0.。

pid_t pid; pid=-1(回收任意子进程),pid>0(指定回收一个子进程),pid=0(同组回收),pid<-1(跨组回收)。

非阻塞回收代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
    pid_t pid;
    int i;
    for( i=0;i<5;i++)
    {
    pid=fork();
    //printf("%d\n",pid);
    if(pid==0)break;
    }
    if(pid>0)
    {//printf("%d\n",pid);
    printf("father pid %d\n",getpid());
     pid_t wpid;
     while((wpid=waitpid(-1,NULL,WNOHANG))!=-1)
     {
         if(wpid>0)
         {
             printf("waitpid success %d\n",wpid);
         }
         else if(wpid==0){

         printf("I am running\n");
         usleep(500000);
         }
     }
    }
    else if(pid==0)
    {
    printf("son pid %d\n",getpid());
    sleep(i);
    exit(i);
    }
  else exit(0);
return 0;
}

结果,首先父进程创建子进程,上面提到过fork会给两个返回值,一个是给父进程的子进程id,一个是给子进程返回的0,返回0之后会立即执行else if那,而父进程等创建完最后一个29866>0,执行if>0,所以输出是乱序的。

非阻塞回收比阻塞回收版本有更高的自由度,可以执行自身代码段。

无论是阻塞回收还是非阻塞回收都称为主动回收,这并不合理在子进程未结束前父进程产生大量回收开销,后序讲解被动回收方案。

  • 26
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值