笔记重点:
重新分析进程的创建,发现最初的笔记还是比较简单,说起进程创建,不得不先了解进程0的创建。
- 进程0之后创建的所有进程都是以这个进程为模板进行创建,进程0的创建过程是在系统启动的main.c中进行的,在系统初始化之前,手动设置这个进程0,更重要的是这个进程是在内核状态下切换到用户态运行的,所以之后的进程也都在用户态下运行;
- 其次需要理解进程的控制信息(PCB)和进程本身运行的内存空间的分配情况,这点在代码中要了解透彻,进程内核态的堆栈和用户态的堆栈的的分配是分开,同时需要了解他们的分布情况,这对了解用户态下进程如何调用系统调用的理解至关重要;同时一个进程的虚拟内存分布和实际的物理内存分配是分开的,理解这一点对理解linux的内存分配方式以及缺页中断的理解,写时复制机制的理解都很重要;
- 最后在谈到进程的调度,在此版本的调度中可以说非常简单,但是在进程调度却是linux系统一个非常大且重要的主题。
根据一下问题来看笔记
- 进程占多大的线形地址空间
- 进程实际分配多少物理内存
- 创建进程的开销在哪里
一. 从fork系统调用开始
kernel/sys_call.s第222行1
2
3
4
5
6
7
8
9
10
11
12_sys_fork:
call _find_empty_process #为新进程分配id
testl %eax,%eax #测试是否分配了id
js 1f #id若为负数则返回
push %gs #压入copy_process需要的参数
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process #调用copy_process函数
addl $20,%esp #丢弃压栈内容
1: ret
二. copy_process函数分析
作用: 复制当前进程的代码段和数据段以及环境
这里需要说明的是每个任务的线性地址为64M,每个任务的线性地址不重叠。
1. copy_process函数重点
- 函数中新进程的状态改变,创建时设置为TASK_UNINTERRUPTIBLE,完成创建后,态设置为task_running
- 新进程的内核堆栈的设置以及新进程的返回值
1 | p->tss.ep0 = PAGE_SIZE+ (long)p; //设置新进程的内核堆栈 |
下表是任务状态段(tss)的字段表格,这里可以参考任务状态段的描述,tss是task_struct中的一个字段.
进程的内核堆栈和用户堆栈的区别,内核堆栈分配在分配给task_struct结构的一页内存的顶端,也就是地址(long)p + PAGE_SIZE的位置,
进程的内核堆栈示意如图:
进程的用户堆栈示意图:
- 设置进程的tss段和ldt段
代码参考fork.c第130行1
2
3
4
5//gdt为gdt表的首地址,nr<<1表示每个任务有两项状态段和局部段
//p->tss表示tss在task_struct中的偏移
//p->ldt表示ldt在task_struct中的偏移
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
每个任务都有一个tss段和ldt段,存放在gdt表中,如下图所示:
2. copy_mem函数重点
上面代码重点分析copy_mem函数中get_base函数的意思,首先来看段描述符的格式如下图:
1 |
|
- copy_mem函数的get_limit(0x0f)中0x0f的意思,段选择子的格式如下:
get_limit(0x0f)中的0x0f是段选择子,0x0f为 0000 0000 0000 1111,指定了LDT表中具有RPL=3,索引值为1,T1位为1,指定LDT表
- copy_mem函数代码分析:
- 拷贝当前进程的页目录和页表给新进程
- 设置好新进程的ldt表
1 | int copy_mem(int nr,struct task_struct * p) |
三. 进程管理(创建)依赖内存管理子系统
copy_process的第77行,用来分配一页物理内存(通常是4k)给新进程
1 | struct task_struct* p; |