一、Linux中的task_struct:
进程在内核源码中以数据结构task_struct的形式存在,其中有几个非常常见的属性字段
1、__state是进程的当前状态,-1是unrunnable,0是runnable, >0是stopped
2、Counter是进程拥有的时间片,当一个时钟中断到来,当前占有CPU的进程时间片会消耗1,进程调度函数schedule会会遍历任务队列选择时间片最大的进程上CPU
2.1、参照调度函数源码schedule可知,整体的调度策略是选择最长时间片的进程上CPU,直到进程队列中所有进程的时间片全部耗完,时间片最大的进程会一直占用CPU,直到其时间片被消耗至和第二大的进程,CPU就会轮换在时间片最大的两个进程中,时间片相同时优先选择在队列中靠前的进程,然后更多的进程拥有相同的时间片且均为最大时间片,多个相同时间片之间按照各自在进程队列中的前后顺序上CPU。
2.2所有进程时间片耗尽是触发重新分配时间片的一种情况,老一点的Linux系统中,重新分配时间片按照进程的优先级和当前剩余时间片分配的
3、priority是进程的优先级,关系到进程时间片的分配,Linux中采用的是优先级时间片轮转算法,时间片由公式(*p)->counter = ((*p)->counter >> 1) +(*p)->priority;得出,剩余时间片/2+进程优先级=新时间片
4、Tss段,进程状态描述符,里面存放的就是cpu在进行进程切换时需要保存和改变的寄存器信息
二、Linux系统启动,通过引导程序运行到Linux的main函数中,进行一系列初始化,初始化进程的函数是sched_init();
三、调度初始化函数sched_init的作用:
1、先初始化第一个进程的ldt和tss
2、对进程列表1-63位置为NULL,将各项数据置为0,
3、设置一些寄存器的值,
4、通过set_system_gate设置系统调用的中断,
5、后续进程的创建,调度,销毁都是通过系统调用system_call完成的
四、main中的初始化工作完成以后,状态从内核态转变为用户态
1、初始化函数的执行位于内核态,是为了保证函数成功执行完毕,而不会被抢占CPU
2、当进程处于用户态后,就可以和其他进程竞争CPU了
Main进入用户态后,会调用fork创建0号进程,0号进程是所有进程的父进程,根据fork的作用,这里会返回被fork创建出来的0进程的pid,值为0,然后进入init。
五、Init函数的作用:
1、init中先打开代表标准输入设备的句柄/dev/tty0,这个操作打开了句柄0,然后dup复制句柄得到了标准输出1和错误2两个句柄,这三个句柄对应到同一个设备文件/dev/tty0
2、创建一号进程,创建成功后在进程中关闭之前打开的设备文件
3、一号进程打开“开机自启命令文件”/etc/rc
4、一号进程以argv_rc,envp_rc为参数执行文件/bin/sh,打开了shell,然后一号进程退出,Linux下进程的退出会变成僵尸进程等待父进程回收
5、0号进程使用wait等待退出,wait会返回被回收进程的pid,参数i会得到被回收进程的回收信息,当等到1号进程退出后,0号进程才会继续向下执行。
6、下面的流程中,0号进程继续创建进程,创建的进程中会先关闭原先的句柄,然后重复对句柄的打开操作,并使用与一号进程不同的参数打开sh,可以看到两种入参的差异,但操作上基本一致
六、Init结束,初始化完成以后,0号进程不会结束,它会在没有其他进程运行时被调用,只会执行pause
七、进程创建:
函数fork创建进程的思想
1、在task链表中找到一个进程空位存放当前的进程
2、创建一个task_struct
3、设置task_struct
fork.c文件中有四个函数
void verify_area(void * addr,int size)//验证区域
int copy_mem(int nr,struct task_struct * p)//拷贝内存
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
long ebx,long ecx,long edx,
long fs,long es,long ds,
long eip,long cs,long eflags,long esp,long ss)//拷贝进程
int find_empty_process(void)//寻找空进程
这四个函数会在位于system.s文件中的sys_fork汇编函数中被调用
八、创建进程步骤:
创建进程就是对0号进程或当前进程的复制,就是对进程结构体task_struct和堆栈的复制,将0号进程或当前进程的task_struct深拷贝给新的task_struct,同时深拷贝源进程的堆栈信息
1、函数int find_empty_process(void),为新创建的进程分配一个进程号pid,如果没有进程号了就返回错误码
2、设置完寄存器后,调用copy_process函数,创建新进程的主体,就是task_struct*,
3、将新进程的task_struct*放到task_struct[]中,
4、通过*p = *current;完全深拷贝当前进程的内容,然后使用传入的数据设置新进程的task_struct的内部数据,完成初始化
4.5、拷贝进程中内容时,需要注意*p = *current语句只能拷贝task_struct中的非指针型数据,对于里面的指针型数据还是浅拷贝,所以接下来还需要对指针型内容挨个拷贝
5、拷贝内存,将老进程的ldt(数据段和代码段)拷贝到新进程中
6、检测父进程打开的文件,继承父进程的文件描述符并将父进程中打开文件的计数+1,同样还继承了pwd,root等属性
7、使用set_tss_desc和set_ldt_desc给新进程设置tss信息和ldt信息,有了堆栈,tss,ldt,一个进程就组装完成了
8、将新进程的状态设置为运行状态,返回新进程的pid,进程创建完成