关于linux早期内核0.11的一些感悟,写成2则问答 以此为记,免得自己忘记!! :)
详细见全文
问:关于linux 0.11中 head.s中有一处我不是很明白,盼望能得到斑竹指点
startup_32:
cld
movl x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
lss _stack_start,%esp
关于其中lss _stack_start,%esp这句 我不是很明白,_stack_start表示什么?
我在另一篇文章中看到说_stack_start是C编译器自动生成的堆栈,那这个堆栈
是会落在什么地方? 在保护模式下ss应该是堆栈段选择子,这里却没有设置SS
的内容。GCC是如何生成这个堆栈的呢?
另外还有一点 关于linux2.4堆栈段和数据段混用同一段的疑问
(SS和DS指向同一个描述符) 这个时候linux2.4中kernel_DS描述符的
type位中的ED设置为0,也就是说该数据段是向上扩展的。那么是否意味着
堆栈也是向上生长的?但是从后面的程序看,堆栈仍然是向下生长的。这两者不是
矛盾吗? IA32手册上说ED位决定数据段(堆栈段)的扩展方向,但是这是否意味着
此数据段内堆栈增长方向也会改变?
答:stack_start在sched.c文件L72行上定义的。
long user_stack[PAGE_SIZE>>2];
struc {
long * a;
short b;
} stack_start = { &user_stack[PAGE_SIZE>>2], 0x10 };
在内核初始化操作过程中被用作内核栈,初始化完成以后将被用作任务0的用户态堆栈。在运行任务0之前它是内核代码栈,以后用作任务0和1的用户态栈。下面结构用于设置堆栈ss:esp(数据段选择符,指针),见head.s,第23行。ss被设置为内核数据段选择符(0x10),指针esp指在user_stack数组最后一项后面。这是因为Intel CPU执行堆栈操作时是先递减堆栈指针sp值,然后在sp指针处保存入栈内容。
描述符类型中的E比特位说明了指定段的扩张方向,而非栈的操作压入方向。对于一般数据段,其基地址是段的开始处。而对于32位栈段来说(如果定义了栈类型段)它的基地址是指明从基地址开始到限长规定的这段内存是不可访问的,即栈段是从限长开始处到线性地址末端(4G)处才是真正的栈的长度。这个E位就是起这个作用。
例如,如果内存空间为10。定义段基地址=3,长度=4。那么若E=0,则段范围是3--7。若动态加长段长度成6,那么范围就是3--9。即段的扩展位置在段的末端。
定义段基地址=3,长度=4。若E=1,则段范围是7--10。若动态加长段长度成6,那么范围就是9--10。即段的扩展位置在前端。
可见E位仅确定了段的扩张方向。与栈的实际操作无关。
Q: 在main.c开头的说明中 书上说了“在内核创建进程将导致没有写时复制…… 防止弄乱了堆栈……”,
我对这段话不太理解“在内核创建进程将导致没有写时复制”这句话,是什么意思呢?
这个和需要用嵌入汇编有什么关系呢?
另外,我有点不明白。
怎么会弄乱堆栈呢? 在什么情况下会弄乱堆栈?
A: 由于创建新进程的过程是通过完全复制父进程代码段和数据段的方式实现的,因此在首次使用fork()创建新进程init时,为了确保新进程用户态堆栈没有进程0的多余信息,要求进程0在创建首个新进程之前不要使用用户态堆栈,也即要求任务0不要调用函数。因此在main.c主程序移动到任务0执行后,任务0中的代码fork()不能以函数形式进行调用。程序中实现的方法是采用gclearcase/" target="_blank" >cc函数内嵌(内联)的形式来执行这个系统调用。参见下面程序第23行。虽然其中的系统中断调用还是避免不了使用堆栈,但是系统调用使用任务的内核态栈,而每个任务都有自己独立的内核态栈,因此系统调用不会影响这里讨论的用户态栈。
另外,在创建新进程init的过程中,系统对其进行了一些特殊处理。在为新进程init复制其父进程(进程0)的页目录和页表项时并没有为它们处于内核区的代码和数据执行写时复制(Copy on Wirte)操作 ,进程0和进程init实际上同时使用着内核代码区内(小于1MB的物理内存)相同的代码和数据物理内存页面,只是执行的代码不在一处,因此实际上它们也同时使用着相同的用户堆栈区。为了不出现冲突问题,就必须要求任务0在整个执行过程中禁止使用到用户堆栈区域,而让进程init能单独使用堆栈。因此pause()也必须采用内嵌函数形式来实现。
当系统中一个进程(例如init进程的子进程,进程2)执行过execve()调用后,进程2的代码和数据区会位于系统的主内存区中,因此系统可以利用写时复制技术来处理其他新进程的创建和执行。
对于Linux来说,所有任务都是在用户模式下运行的