linux进程调度2

Fork前面创建的是一个进程,假设进程P1,调用fork之后,然后进程P2就被创建,P1是一个task_struct,p2也是一个task_struct,所以在内核里面这是两个task_struct,在内核的调度算法的层面上主要看到一个task_struct,他就会被调度,linux在P1刚刚把P2创建出来的时刻,linux会认为P1和P2之间会有某种内在的联系,前面有提到,task_struct里面有个fs,fs里面是这个进程的root和pwd。Linux里面有这么一个逻辑,当P1把P2刚创建出来的时候,P1就会把描述资源的结构体对拷给P2,举个例子,我P1在C盘上跑,我P1创建了一个P2,我P2不会直接就分到D盘上去了把,P2在被P1创建出来的时刻应该和P1是完全一样的,既然是进程,你的资源和我的资源是一样的,那还叫什么两个进程,进程是封装的单位啊,区分进程的标志就是你的资源不是我的资源,所以不能永远看到一样的资源,那我们要做一件什么样的事情呢,只要谁动资源就要分裂,举个例子,p1里面不是打开了文件链表吗,比如说打开了1 2 3,所以P1把P2刚刚创建的时候,P2里面也有1 2 3,但是不好意思,p2后面如果打开了文件4,那么p1是没有打开文件4的,那么在p2的文件stuct里面就多了文件4,除了内存不好分裂之外,其余的都好分裂,因为内存资源要做写时分裂,所以要找个办法来探测谁在写那一片内存,这就是linux最经典的copy_on_write。


上面代码,一开始有个全局变量10,然后我就调用了fork,这就导致创建了一个新的进程,这个时候父子进程都会去判断这个返回值是不是0,但是只有子进程里面的返回值才是0,所以if进去执行这个child_process,然后我们看一看子进程干了什么,子进程上来就打印data全局变量,这个data全局变量最开始是赋值为10的,然后我这个data全局变量从10改成了20,然后子进程在把这个全局变量再打印一次,父进程过一秒之后再去打印这个全局变量,程序运行结果如下

原理是因为我fork的时候是进程,你动这个资源就要分裂,所以你要写这个data我就要把这个data分裂,分裂的原理是


我最开始只有一个进程P1,我现在假设data的虚拟地址是虚拟地址是virt1,物理地址是phy1,然后通过MMU将虚拟地址查找到物理地址,既然你是一个数据段,那么你数据段的权限就是R+W,Linux 怎么探测谁写呢,做了一个很有意思的事情,变迁,一旦你P1fork处理P2之后,linux做了一个什么样的事情呢,P1和P2看到的虚拟地址和物理地址还是一样的,但是现在P1和P2看到的是同一份data,linux这个时候,就把这一页的权限改变成read_only,一旦页表里面某个地址的权限被改成read_only,CPU一旦去写,无论是P1去写还是P2去写这个read_only的地方,CPU都会收到一个page_fault,就会出现一个缺页中断,这个在中文文档里面叫做缺页中断,缺少页面的中断,无论是谁去写都会发生这个缺页中断,刚才的例子是P2先写,是写不成功的,因为地址是只读的,CPU会发生缺页中断,CPU就会在内存条里面去申请一片新的内存,这个时候P2就得到一个新的物理地址PHY2,linux会把老的PHY1拷贝到新的PHY2,然后去修改子进程的页表,把自己层的虚拟地址1指向这个新的物理地址,这个时候父子进程看到的虚拟地址都是一样的,但是看到的物理地址在内存里面就不一样了,之后linux就会把这两个权限改成RW的,然后父子进程都可以去写这个data了,但是父子进程看到的data完全不是一个data,这就是著名的copy_on_write技术,这个严重依赖于你CPU里面的MMU,在没有MMU的系统里面,是不能执行copy_on_write的,没有fork的,只有vfork,父进程阻塞知道子进程exit或者exec,vfork和fork不一样在于vfork会阻塞父进程,当你P1创建子进程P2的时候,你的P1就会阻塞,在vfork之后有两个很大的不一样,P1的MMstruct不再对拷给P2,P2根本不会用一个单独的mm_struct,p2的mm指针直接执行p1的,p1和p2的指针指向同一个mm_struct,线程就直接共享task_struct,共享资源可以被调度,linux就是这么来调度资源的

一个多线程在内核里面都是一个task_struct,所以在内核空间里面都有各自的一个PID,但是posix标准要求,一个进程里面有多个线程,那么看起来要像一个整体,什么意思呢,就是我getpid的时候get是同一个pid,linux做了一个小小的技巧,你pthread_crate创建出来的话,必然有自己的Pid,但是你linux要求要get同一个pid,所以linux在里面搞yield很假的TGID,他让P1里面的task_struct里面的TGID等于pid1,让P2的TGID也等于pid1,所以你getpid的时候得到的就是tgid,也可以把真正的pid提取出来


这里面的这个gettid就可以把真正的pid给读出来


进程的托孤

P2创建一个子进程P3,但是P2已经死了,P3还没死,P3就变成了孤儿,linux就会完成典型的托孤过程,托孤有两种可能性,要么托孤给Init,要么托孤给中间SUBBREAPER

PR_SET_CHILD_SUBREAPER是linux 3-4加入的新特征,把它设置成为非零值,当前进程就会变成subreaper,会像1号进程那样收养孤儿进程,进程托孤的时候会找最近的subreaper托孤,没有的话就被init托孤,设置的方法是prtcl(PR_SET_CHILD_SUBREAPER.1);


运行上面一段程序 然后TOP


ps aux命令显示如上

看下ps tree 看下进程树,杀死父进程之后子进程就别托孤了

睡眠在内核里面主要以等待队列实现,


既然每一个进程都是别人创建出来的,linux里面有个严肃的问题,每个task_struct都是被fork出来的,那么第一个task_struct是怎么来的呢,,这个init 进程是怎么来的,是被linux 进程0创建的,

看到这个PPID是0,这说明一个问题,1进程的父进程其实就是0进程,但是你在linux下的pstree看不到0进程,因为0进程在创建了1进程之后,就退化成了IDLE进程,,IDLE进程是linux里面的特殊调度类,你所有的进程都停止和睡眠之后,我就去让进程0跑,0进程是最垃圾的,有任何一个进程去跑,0进程就不会跑,你们都不跑了,0进程就会跑,把CPU变成非常低功耗



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值