全部博文(142)
分类: LINUX
2011-08-13 21:19:50
1.书第10页,2.3 Linux系统定时不太好理解,看了两遍还是有点晕。其中说“如果被中断的当前进程工作在内核态,即在内核程序中运行时被中断,则do_timer()会立刻退出”。
这里退出后是接着在内核态执行吗?那为什么还要对内核运行时间进行统计呢?
2.还是系统定时这一节的“如果程序添加过定时器”,这里的定时器,指的就是一个函数调用吗?“定时器链表”是什么意思?
3.看了这么久还是不太明白进程运行在用户态和内核态的区别,能通俗地讲一下,什么情况是运行在用户态,什么情况是运行在内核态的吗?
我原来的理解是,执行程序自身代码是运行在用户态,调用系统调用时是运行在内核态
但书第13页,即2.4.3进程初始化那节中有一句话:“它(move_to_user_mode)把main.c程序执行流从内核态(特权级0)移动到了用户态(特权级3)的任务0中继续执行”-----这句话我觉得很迷惑,main.c程序本身就是操作系统的内部代码,怎么还存在内核态和用户态的问题?要不就是我原来对用户态和内核态的理解是不正确的。
4.书第15页,即2.4.5进程调度这一节中,有一句话“如果此时没有其他进程可运行,系统就会选择进程0运行...只要系统空闲就调度进程0运行”,这里进程0被调度运行的作用是什么?
答:挨着理解的顺序来吧
3、什么情况是运行在用户态,什么情况是运行在内核态?
我原来的理解是,执行程序自身代码是运行在用户态,调用系统调用时是运行在内核态
但书第13页,即2.4.3进程初始化那节中有一句话:“它(move_to_user_mode)把main.c程序执行流从内核态(特权级0)移动到了用户态(特权级3)的任务0中继续执行”-----这句话我觉得很迷惑,main.c程序本身就是操作系统的内部代码,怎么还存在内核态和用户态的问题?
其实内核态和用户态的没有本质的区别,只是规定了一种访问权限罢了。最简单的回答是cs, ss这两个段寄存器里的选择子的 RPL,就是最低两位就代表了 CPU 当前所运行的特权级,也被称做 CPL。比如你会在 head.S 看到 0x10,或者在 move_to_user_mode 这个宏里看到 0x17 这些数。这就是段选择子。如果 cs 中是0x10的话,0x10 写成二进制数就是 00010000,注意看最低两位是00,那么,当前CPU的特权级就是0,就叫处于核心态。而如果 cs 中是 0x17(二进制为00010111,最后两位是11),当前特权级就是 3(二进制的11),这就叫处于用户态。
“执行程序自身代码是运行在用户态,调用系统调用时是运行在内核态”这句话没错,但是要搞清楚哪些才是系统调用。你在 Linux 上写程序,使用的系统调用最终会变成 int 0x80 的中断调用。然后通过中断服务程序进入内核空间,包括使用内核代码,访问内核堆栈。Linux 是这样做的,规定中断服务程序运行在内核态,因为内核态对各种资源有充分的使用权限,而其他的程序都运行在用户态,这是为了保护计算机资源不被随便占用。所以写在内核里的程序也不一定是运行在内核态的,除了中断服务程序,其他程序都是运行在用户态的。
开机以后一直运行到main()函数,然后进行各种初始化,这都是运行在内核态里的,因为进入保护模式之后,默认就是在内核态。等到初始化完了,不再需要调度各种资源了,再在内核态里呆着就不合适了,于是使用 iret(在move_to_user_mode这个宏里)这个常用的技巧转到了用户态去了。所以在那之后的程序就都是运行在用户态里的了。直到遇到中断(比如时钟中断)CPU才会重新回到内核态,执行中断服务程序。
1、其中说“如果被中断的当前进程工作在内核态,即在内核程序中运行时被中断,则do_timer()会立刻退出”,这里退出后是接着在内核态执行吗?那为什么还要对内核运行时间进行统计呢?
有了以上的理解,再来看这个问题。首先你要搞清楚中断机制。无论程序运行在什么特权级,中断都是会发生的。而且一旦发生中断就要求CPU马上响应进入中断服务程序。而由上面分析可知中断服务程序肯定都是运行在特权级0(ring0)上的。于是进入中断不外乎两种情况:中断之前,CPU运行在核心态,另外就是运行在用户态。如果是运行在核心态,中断前后都是在RING0上运行,不会发生特权级的变化。如果是运行在用户态,则会有RING3到RING0的变化。do_timer这个函数的参数就是CPL,也就是中断之前CPU的特权级。设想这样一种状况,CPU正在执行另一个必须响应的硬件中断服务程序,这时,时钟中断发生,CPU进入时钟中断服务程序(timer_interrupt),如果在 do_timer里进行了进程调度,让CPU跑去干别的活去了,那么那个硬件中断就无法被及时响应了。这是不允许的,于是 Linux 就采用这样的规则:如果是从RING0进入时钟中断的,那么do_timer就会在 schedule()之前退出(就是上面说的立即退出)继续执行原来的程序,退出之后仍然运行在核心态。如果是从Ring3进入时钟中断的,那就要检查一下当前条件是不是应该进行进程调度。如果满足,则进行进程调度,回到Ring3。
至于时间统计,我想没什么好解释的,如果时钟中断之前程序运行在内核态,那就把内核时间加1,表示一个滴答,如果之前是运行在用户态,那就把用户态的时间加1。记住,时钟中断程序执行时间很短,所以这种统计的误差还是可以容忍的。
2.还是系统定时这一节的
“如果程序添加过定时器”,这里的定时器,指的就是一个函数调用吗?
“定时器链表”是什么意思?
这个东西,光解释是没有用的,你要自己去看代码,定时器是一个函数这种说法不准确,但是可以这么理解着先。“定时器链表”你去看代码,很清楚的。
4、有一句话“如果此时没有其他进程可运行,系统就会选择进程0运行...只要系统空闲就调度进程0运行”
这里进程0被调度运行的作用是什么?
进程0只有 for(;;) pause();这句话而已,pause()是个系统调用,你自己去看 pause() 的代码好了。作用恐怕也就只有告诉CPU说现在很闲,没事可干,你不用瞎跑。我也拿不准,像这样边角的东西,等到大框架搭起来之后再来处理好了。现在还是赶紧弄清楚进程,中断等等问题是最重要的。
还有,不要和理论纠缠太多,多读代码,一边读一边回头看理论。光扑在第二章的理论上,收获不大。
二、对段选择符和段描述符的几点疑问
1.GDT中为什么要设置一个"空描述符"啊?有什么用呢?
2.既然LDT的段选择符等信息都放在LDTR中了,为什么还要在GDT中再存一遍呢?
3.看书上说段寄存器可分为可见部分和隐藏部分,可见部分存放的是段选择符,隐藏部分存放的是段基地址,限长和属性信息,可是段寄存器只有16啊,段选择符就占了14位了,那个只剩两位作隐藏部分,如何能存放这么多信息呢?还是说我的理解有误?
4.我们知道每个段描述符是8字节,64位,对于这64位的格式,其中把基地址分成三部分,把段限长分成两部分,处理器会把它们组合起来.那为什么要分成这么多个部分呢,还不如直接连起来,省得处理器重新组合一遍了...
答:1.GDT中为什么要设置一个"空描述符"啊?有什么用呢?
这应该是 Intel 的规定吧,有什么用就不知道了。
2.既然LDT的段选择符等信息都放在LDTR中了,为什么还要在GDT中再存一遍呢?
因为每个进程都会有自己的局部描述符表,在 task_struct 这个结构中可以看到。为了方便管理,就把每个进程的局部描述符表的地址存入了GDT表。因为GDT只有一个,这样,某个进程的LDT 就可以在 GDT 中使用它的进程号计算得到。当然,如果你不怕麻烦的话,把 LDT 和 TSS的基址存到 task_struct 结构也未尝不可。只是像 lldt 这样的很多宏定义可就不是那么简洁易懂了。
3.看书上说段寄存器可分为可见部分和隐藏部分,可见部分存放的是段选择符,隐藏部分存放的是段基地址,限长和属性信息,可是段寄存器只有16啊,段选择符就占了14位了,那个只剩两位作隐藏部分,如何能存放这么多信息呢?还是说我的理解有误?
16位全是可见部分。隐藏部分其实是从GDT中自动加载过来的。所以隐藏部分应该是XX位(我记不清楚了,应该是64位,与GDT中描述符相对应的)。段选择符无论从什么意义上讲都不可能是14 位。段选择子有三部分信息:选择子在GDT(或者LDT)中的第几项,RPL 和 TI。如果抛去 RPL和 TI 不算的话,那也是 13 位。至于 RPL 和 TI 是什么意思,我想就不用我多嘴了。
4.我们知道每个段描述符是8字节,64位,对于这64位的格式,其中把基地址分成三部分,把段限长分成两部分,处理器会把它们组合起来.那为什么要分成这么多个部分呢,还不如直接连起来,省得处理器重新组合一遍了...
这是为了和以前的芯片兼容。这种事情很常见,a20 地址线,奇怪的键盘映射,都是为了兼容以前的硬件。所以写程序的时候,这些东西都很烦。尤其是不连续的地址,要对着图好好算一下才能保证正确。
补充:GDT相当于是一个总管家,它记录着所有进程的LDT, 但是 LDTR 中只记录着当前进程的LDT. 如果发生了进程调度,那么 LDTR 就要问 GDT 要下一个被调度的 LDT.
LDTR 确实是为了提高访问局部段的速度。
三、1.系统调用的返回值问题
为什么系统调用的返回值在EAX中,是不是C函数的返回值一般会放在EAX中?
2.时钟中断问题
在时钟中断发生后,时钟中断处理程序会关中断,若此时外部硬件有中断,那么在时钟中断处理程序返回后,cpu会发现刚才的中断吗?
3.有关两个函数问题
在书本的P318,129-130行的两函数中,(nr<<1)+FIRST_TSS_ENTRY 和 (nr<<1)+FIRST_LDT_ENTRY 我觉得应该是索引号啊,他们乘8才是地址啊
在书本P316中,67行与68行之间的倒数第2行注释中,他说“第94行调用sys_call_table中sys_fork函数时压入的返回地址”。有吗?难道是压入的是P267,95行的EAX,但这时sys_fork()结束后才压的。
答:1.系统调用处理函数也是函数啊,放到eax里面方便在C中读取。
2.外部硬件中断此时会暂存在8259A的寄存器里面,如果多个相同的外部硬件中断,就会丢失。
3.注意gdt的数据类型,
4 typedef struct desc_struct {
5 unsigned long a,b;
6 } desc_table[256];
7
8 extern unsigned long pg_dir[1024];
9 extern desc_table idt,gdt;
也就是说,一个gdt是两个unsigned long的,所以在加指针的时候,真正加的是(nr<<1)*两个unsigned long的大小
4.压入的应该是sys_fork函数的返回值