进程 进程的创建 进程的回收

目录

进程

进程的创建        

进程的回收




进程

什么是进程?什么是程序?

        程序就是我们编译好的二进制文件,存放在磁盘上,是静止的。

        而进程是我们把这个程序启动了,加载到了内存上了,是动态的。

每个进程都有属于自己的PID。PID就是一种标识符,可以理解成人的身份证,可以通过这个唯一的PID找的相应的进程。

关于进程有两个概念:一个是并发,一个是并行

        并发是指多个进程在一个CPU上运行

        一个CPU在某一刻只能运行一个进程,那为什么我们看到的感觉好像是每个进程都是在同时运行着呢?那是因为CPU榨干自己,比如把一秒钟分成若干个时间片,一个时间片里面CPU先执行其中一个进程然后时间一到立马停止这个进程去执行其他的进程。这里的停止并不是把进程真正的停止了,而是把进程挂起,过一小会立刻又继续执行它。

        CPU一直重复执行这种步骤,直到把多个进程全都运行完。也就是说为什么遇到死循环代码不会让电脑死机的原因,如果CPU不这么做的话,就是等这个死循环进程结束后再执行下一个进程,就会给人死机的感觉。

        并行是指两个或两个以上的进程在同一时刻发生。

        前面我们讲了一个CPU在某一刻只能运行一个进程,怎么样才能在一个时间片里面实现多个进程的运行呢?那就需要多个CPU来一起操作了。也就是现在的多核处理器了。每个CPU又可以并发多个进程,所以多核处理器的速度相比于一个有了显著提升。

进程一共有五种状态需要了解一下,分别是是初始态,就绪态,运行态,挂起态,终止态。

        初始态到就绪态很短暂基本上是与就绪态结合在一起来看的。

                

        初始态先短暂的迅速到了就绪态,就绪态在获得CPU时间片的时候会进入运行态,当时间片用完的时候又会回到就绪态。当就绪态运行态收到外部发送的信号暂停时,就会进入挂起态,然后再收到让其继续的信号就回到就绪态。注意,运行态挂起后再继续回不到运行态,需要回到就绪态等待CPU时间片才能再次回到运行态。就绪态,运行态,挂起态三种状态都可能因为收到外部的强制停止信号而到达终止态,也有可能是运行结束后到达了终止态。

          例如linux环境下,其他环境下也差不多相同。进程在创建的时候操作系统会为进程分配一个0~4G的地址空间(这是一种虚拟空间,根据进程的实际使用资源确定硬件内存的使用情况,否则几个进程就把真正的内存满了) 。

以32位操作系统为例:

0~4G会分为内核区(3~4G) 用户区(0~3G)

        用户区就是我们可以操作的地方,而内核区我们是没有办法对它进行读写操作的,但是用到它时,系统会给我们提供一个API接口让我们来间接操作。内核区重要的部分是PCB(进程控制块),本质是c语言中的结构体,里面重要的部分就是文件描述符表,文件描述符表存放着打开的文件的描述符,这个描述符都是用数字来代替。默认新打开的进程都有三个已经在的描述符占据着0,1,2分别是标准输入 标准输出 标准错误输出。这些也是可以关闭的,当你关闭了之后就和与之相连的终端设备断开了连接。

        用户区的内容主要有环境变量 命令行参数 栈 堆 未初始化的全局变量 初始化的全局变量 0~4k受保护的地址 共享库 还有最重要的代码段 。

进程的创建

        进程在运行过程中,是可以通过系统提供的api接口创建子进程的。我是这么理解的,创建子进程的目的是为了可以分开干不同的事情。

        api 为   pid_t fork(void);不用将这个返回类型想象的太复杂,就可以单纯的把他理解成int类型,因为pid本质也就是一段数字一样的标识符。

这个函数的返回值 如果成功的话 父进程返回子进程的pid 子进程返回0 调用失败返回-1设置error值

        创建出来的子进程完全复制了父进程的一切,包括虚拟地址空间的那些。但是有一个地方不一样,就是他们的pid是不同的

创建出来的这个子进程是fork()函数调用完成之后的地方开始执行的。

父子进程这两个进程执行的速度是没有优先级的,全看CPU时间片想给谁,谁就先执行。

        题外话:子进程更多用来使用execl函数拉起另一个执行程序,execl函数的原理就是把子进程原有的代码段数据段还有堆 栈替换成你execl指定的路径的程序的代码段数据段还有堆 栈。为了验正是否会替换内核区的PCB的描述符表我做了以下实验:

int main()
{
    pid_t pid;
    int fd= socket(AF_INET,SOCK_STREAM,0);
    int fe= socket(AF_INET,SOCK_STREAM,0);

    execl("/home/wzh/cliondir/wzh",NULL,NULL);

    return 0;
}


wzh:

int main()
{
    pid_t pid;
    int fd= socket(AF_INET,SOCK_STREAM,0);

    printf("%d\n",fd);

    return 0;
}

        这个程序的名字就是library.exe 下面是该程序的运行结果为5,可以看出在execl之前,我打开了两个文件描述符,此时最大文件描述符为 4分别是默认的三个加上我创建的两个012 34 .替换代码段之后输出,最大文件描述符为5,说明是在原来的基础加上的。

进程的回收

        一个进程在结束运行的时候会回收自己用户区的资源,但是内核区的PCB资源自己没办法回收,需要父进程来回收。如果PCB资源一直没有回收,就会越堆越多,形成很大的资源浪费。我们平时运行的进程的父进程是系统提供的,系统负责帮忙回收PCB资源。我们自己创建的子进程,就需要在父进程来帮忙回收。

        进程的死亡会遇到特殊情况,比如说自己还在运行,但是父进程已经死掉。这种称为孤儿进程,会被init进程领养,到时候init进程回收它的资源,这个init进程也是系统提供的。init进程也称1号进程因为它的PID是1。第二种情况是父进程还活着,但是子进程已经死了,并且这个父进程没有对这个子进程进行回收,这个子进程就称为僵尸进程。为什么叫僵尸进程呢?因为使用kill这个强制关闭的信号也没法杀死它,因为它本来就已经死了。但是可以用前面提到的把它的父进程杀死,让init进程来领养它,进行间接的回收资源。

回收进程资源就要用到系统提供给我们的api:

        pid_t wait(int *status);系统提供的这个会阻塞等待,也就是子进程结束后再回收它的资源并获得这个子进程的结束状态。回收到了就返回子进程的pid,如果失败返回-1说明没有子进程可以回收了。可以通过返回值确定有没有回收完子进程。

        pid_t waitpid(pid_t pid, int *status, in options);系统提供的这个是我们平时用的最多的,第一个参数pid有多种情况.

  • pid>0 回收指定pid号的子进程;
  • pid=-1 回收任一子进程;
  • pid=0 回收进程组id与当前进程相同的子进程。
  • pid<-1 回收和这个pid绝对值相等进程组id下的任一子进程。(适合子进程在其他组的情况)

status和上面一样,是个传出参数,根据传出参数有相应的函数可以判断这个子进程正常退出的返回值或者是被信号杀死的这个信号编号。

options用来改变这个函数的两种状态 一种状态是阻塞,一种是非阻塞。阻塞填0,非阻塞填WNOHANG

函数的返回值和wait差不多,但是当设置为非阻塞的时候,有时候会返回0。返回0的时候说明当前子进程还在运行,这个函数也算执行完了,如果之后子进程结束了,需要再调用一次回收函数。

注意,这两个函数一次只能回收一个子进程!!!

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值