Linux进程(2)

一个进程是通过fork出来的

mm指针描述进程的内存资源

fs指针描述进程的文件系统资源

files指针描述进程打开的文件资源

signal指针描述进程的信号资源

每个指针都指向一个结构体

在内核的调度算法上只要看到task_struct就可以调度这个进程,调度算法只认得task_struct结构体

进程p1有自己的task_struct,当p1调用fork()去产生一个子进程p2

那么进程p2也有自己的task_struct,因此也有自己的PID、mm指针、fs指针、files指针、signal指针等

当进程p1创建进程p2的时候,p1有自己的PID、mm指针、fs指针、files指针、signal指针 

刚刚创建进程p2的时候,其实是将p1的task_struct原封不动的复制一份给进程p2的task_struct,所以子进程p2就继承了父进程p1的文件资源,文件系统资源,信号资源等等,然后再进程p2中的任何修改都造成p2和p1的分裂,假设进程p2打开了一个新的文件,那么进程p2中的files指针指向的结构体就多了一个文件,但是进程p1的files指针指向的结构体中是看不到进程p2打开的新的文件的。

又比如:假设进程p2在fork()出来后调用了chdir函数切换了路径,那么两个进程的当前路径就不一样了,然后fs指针指向的fs_struct内容也不一样了。

所有的资源(文件系统资源、文件资源、信号资源)都很好分裂。但是内存资源要用写时分裂。

 fork()出来的是进程,无论动什么资源都要进行分裂。

父子进程的内存分裂是通过写时分裂来实现的。

最开始的时候进程p1还没调用fork()产生子进程的时候,进程p1的虚拟地址和物理地址是通过MMU来映射的,并且MMU中有一个页表寄存器,记录着当前执行的进程的页表的物理地址,这个页表的每一行(4个字节)记录着每一页(一般4KB)虚拟地址对应的映射的物理地址,并且还有其他的二进制位记录着这一页物理地址的访问权限(RWX)和这一页物理地址是用户态还是内核态能访问的。

进程p1还没fork()出子进程的时候进程p1数据段上的虚拟地址通过MMU映射得到的物理地址原则上是可读写的。

当进程p1调用fork()之后进程p1和进程p2看到的虚拟地址和物理地址是一样的,所以进程p1和p2看到的是同一份数据,但是操作系统将这一页(data对应的这一页)虚拟地址的权限改为了只读权限,当进程p1或者进程p2试图去写这一段虚拟地址对应的物理地址的时候,假设进程p2去试图去修改数据段,cpu没有这个权限访问就会产生page fault(缺页中断)中断,然后进入中断为当前访问此数据段的进程p2在内存条中申请一段新的物理地址再将原来的物理地址的内容拷贝到新的物理地址,然后更新进程p2的页表(将虚拟地址映射到新的物理地址,此时进程p1和进程p2看到的虚拟地址是一样的,但是对应的物理地址已经不一样了)并修改进程p1、p2的页表数据段虚拟地址对应物理地址的权限为可读可写。之后进程p1和进程p2都可以去修改各自的数据段上的数据了但是此时彼此的数据都是在各自的物理地址空间中,是两份数据了。

由此可以写时复制的技术严重依赖于MMU(内存管理单元)。如果cpu没有MMU,fork()系统调用是不能工作的。

 

 

 

产生一个新的task_struct然后全部的指针都和p1的task_struct指针指向的是相同的话,那么就叫线程(共享资源。可调度(可调度是因为线程也有个task_struct,调度算法只认得task_struct,只要看到tast_struct就可以调度))

如果产生一个新的task_struct然后所有结构体都从p1的task_struct中拷贝一份出来,然后再慢慢分裂的话就叫进程。 

进程是资源分配的基本单位,线程是调度的基本单位,在一个进程中所有的线程共享进程的资源。

Linux中task_struct和task_struct中可能有三种关系,进程(各自指向不同的资源),线程(指向相同的资源),人妖(一部分相同一部分不同)

总结:主要是task_struct这个结构体如何去理解,才能很好的理解进程和线程的关系。

fork()出来的子进程和父进程不共享数据段和代码段

而vfork()出来的子进程和父进程的task_struct结构的mm指针指向相同的内存空间,所以共享数据段和代码段,然后父进程阻塞知道子进程调用exec或者exit后才唤醒父进程。

在没有MMU的cpu中是没有fork()的只有vfork()

linux的调度器只认得task_struct这个结构体,线程是调度的最小单位所以线程也有task_struct

孤儿进程(父进程先运行结束了子进程还没运行结束)

托孤有两种情况,1、将孤儿进程托孤给init进程(PID为1的进程),2、将孤儿进程托孤给subreaper进程(这个需要进程调用系统调用来使自己成为subreaper进程)

深度睡眠浅度睡眠都是当进程试图去读FIFO而FIFO没有数据可读进程本身调用schdule()来释放CPU然后进入睡眠状态

进程0是进程1(Init进程)的父进程,当进程0  fork()出进程1之后就退出成IDLE进程(所有进程都睡眠的时候就运行这个IDLE进程,就进入了低功耗状态)

猜你喜欢

转载自blog.csdn.net/wllen_/article/details/81516469
今日推荐