21.进程0创建进程1时,为进程1建立了task_struct及内核栈,第一个页表,分别位于物理内存16MB顶端倒数第一页、第二页。请问,这两个页究竟占用的是谁的线性地址空间,内核、进程0、进程1、还是没有占用任何线性地址空间?说明理由(可以图示)并给出代码证据。

p92

第一页task_struct 就是进程1的

第二页是和进程0一样的线性地址空间?

22.假设:经过一段时间的运行,操作系统中已经有5个进程在运行,且内核分别为进程4、进程5分别创建了第一个页表,这两个页表在谁的线性地址空间?用图表示这两个页表在线性地址空间和物理地址空间的映射关系。

23.代码中的”ljmp %0\n\t” 很奇怪,按理说jmp指令跳转到得位置应该是一条指令的地址,可是这行代码却跳到了”m” (*&__tmp.a),这明明是一个数据的地址,更奇怪的,这行代码竟然能正确执行。请论述其中的道理。

p106\127

include/linux/sched.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* switch_to(n) should switch tasks to task nr n, first
* checking that n isn't the current task, in which case it does nothing.
* This also clears the TS-flag if the task we switched to has used
* tha math co-processor latest.
*/
#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,_current\n\t" \
"je 1f\n\t" \
"movw %%dx,%1\n\t" \
"xchgl %%ecx,_current\n\t" \ //强行切到进程0
"ljmp %0\n\t" \ //第一次执行完 for pause 这一行执行完了执行 _syscall0的 if(__res >=0) || 看这里 copy_process: p->tss.eip = eip;
"cmpl %%ecx,_last_task_used_math\n\t" \ //进程0回到for pause
"jne 1f\n\t" \
"clts\n" \
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \ //IA32 任务切换
"d" (_TSS(n)),"c" ((long) task[n])); \
}

​ ljmp通过CPU的任务门机制并为实际使用任务门,它把CPU的各个寄存器值保存在当前进程的TSS中,将要转换的进程的TSS数据以及LDT的代码段、数据段描述符数据恢复给CPU的各个寄存器,从而实现进程的切换

​ a代表EIP、b对应cs

24.进程0开始创建进程1,调用fork(),跟踪代码时我们发现,fork代码执行了两次,第一次,执行fork代码后,跳过init()直接执行了for(;;) pause(),第二次执行fork代码后,执行了init()。奇怪的是,我们在代码中并没有看到向转向fork的goto语句,也没有看到循环语句,是什么原因导致fork反复执行?请说明理由(可以图示),并给出代码证据。

p2 p107

​ 这个题的话,需要追踪一下fork的流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
init/main.c

if (!fork()) { /* we count on this going ok */
init(); //进程1
}

static inline _syscall0(int,fork)

进入到include/unistd.h

#define _syscall0(type,name) \\
type name(void) \\
{ \\
long __res; \\
__asm__ volatile ("int $0x80" \\ //int 0x80到哪呀, syscall 在sched_init那里,执行完,特权变0
: "=a" (__res) \\
: "0" (__NR_##name)); \\ //把name 贴过来
if (__res >= 0) \\
return (type) __res; \\
errno = -__res; \\
return -1; \\
}


然后通过int $0x80
int $0x80 p71 系统调用总入口

int $0x80
kernel/system_call.s
.align 2
_system_call:
cmpl $nr_system_calls-1,%eax ;核实独立访问?
ja bad_sys_call
push %ds ;对齐??
push %es
push %fs
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
call _sys_call_table(,%eax,4) ; 从这里过去的
pushl %eax
movl _current,%eax
cmpl $0,state(%eax) # state
jne reschedule
cmpl $0,counter(%eax) # counter
je reschedule
ret_from_sys_call:
movl _current,%eax # task[0] cannot have signals
cmpl _task,%eax
je 3f
cmpw $0x0f,CS(%esp) # was old code segment supervisor ?
jne 3f
cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ?
jne 3f
movl signal(%eax),%ebx
movl blocked(%eax),%ecx
notl %ecx
andl %ebx,%ecx
bsfl %ecx,%ecx
je 3f
btrl %ecx,%ebx
movl %ebx,signal(%eax)
incl %ecx
pushl %ecx
call _do_signal
popl %eax
3: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %fs
pop %es
pop %ds
iret

_sys_call_table是在这里:include/linux/sys.h

extern int sys_fork(); //对应 kernel/system_call.s中的_sys_fork

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,....}


call的函数对应着这个:kernel/system_call.s

.align 2 ;对齐的意思
_sys_fork:
call _find_empty_process ; kernel/fork.c 寻找空的进程任务号 task[i]
testl %eax,%eax ;1
js 1f ; f d 前后的意思
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process ;操作系统核心函数!!!复制进程!!!!!!
addl $20,%esp
1: ret

​ 注意,当_sys_fork执行完之后,会回到 _system_call 继续往下执行,执行到下面一条语句的时候跳转

​ je 3f ; 如果当前进程是进程0,跳转到下面的3处执行,现在是进程0,所以跳转

​ 3处最后的iret会把ss、esp、eflags、cs、eip弹出,eip存储的是_syscall0中int $0x80的下一行,也就是if (__res >= 0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
.align 2
_system_call:
...........
call _sys_call_table(,%eax,4) ; 从这里过去的
pushl %eax ; 回到这里!!
movl _current,%eax
cmpl $0,state(%eax) # state
jne reschedule
cmpl $0,counter(%eax) # counter
je reschedule
ret_from_sys_call:
movl _current,%eax # task[0] cannot have signals
cmpl _task,%eax
je 3f ; 如果当前进程是进程0,跳转到下面的3处执行,现在是进程0,所以跳转
cmpw $0x0f,CS(%esp) # was old code segment supervisor ?
jne 3f
cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ?
jne 3f
movl signal(%eax),%ebx
movl blocked(%eax),%ecx
notl %ecx
andl %ebx,%ecx
bsfl %ecx,%ecx
je 3f
btrl %ecx,%ebx
movl %ebx,signal(%eax)
incl %ecx
pushl %ecx
call _do_signal
popl %eax
3: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %fs
pop %es
pop %ds
iret

​ 这个时候,res就是eax,eax是哪里得到的呢? 是在_system_call的时候(也就是int $0x80的时候),call完了sys_fork的时候,sys_fork返回时,执行的pushl %eax, sys_fork的返回值是last_pid,也就是创建的的pid,此时就是1

1
2
3
4
5
6
kernel/system_call.s
.align 2
_system_call:
....
call _sys_call_table(,%eax,4) ; 从这里过去的
pushl %eax
1
2
3
4
5
6
7
8
9
10
11
12
13
#define _syscall0(type,name) \\
type name(void) \\
{ \\
long __res; \\
__asm__ volatile ("int $0x80" \\ //int 0x80到哪呀, syscall 在sched_init那里,执行完,特权变0
: "=a" (__res) \\
: "0" (__NR_##name)); \\ //把name 贴过来
if (__res >= 0) \\
return (type) __res; \\
errno = -__res; \\
return -1; \\
}

​ 调用完了fork回到main,fork的返回值是1,所以!fork()是0,不进入到里面执行,进入到下面的pause

1
2
3
4
5
if (! fork()) {		/* we count on this going ok */
init(); //进程1
}

for(;;) pause(); //这一行是进程0的代码
1
2
3
4
5
6
7
8
kernel/sched.h

int sys_pause(void)
{
current->state = TASK_INTERRUPTIBLE;
schedule();
return 0;
}

​ 进入schedule调度函数

25、打开保护模式、分页后,线性地址到物理地址是如何转换的?

26、getblk函数中,申请空闲缓冲块的标准就是b_count为0,而申请到之后,为什么在wait_on_buffer(bh)后又执行if(bh->b_count)来判断b_count是否为0?

p114

​ 这个要看wait_on_buffer函数的功能了,它里面有sleep_on函数,而sleep_on函数里面包含了schedule函数,虽然现在的缓冲块是合适的,但是有可能在睡眠阶段的时候被别的任务占用,所以在使用之前需要判断是否被修改了,修改的话就需要等待解锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fs/buffer.c

static inline void wait_on_buffer(struct buffer_head * bh)
{
cli();
while (bh->b_lock)
sleep_on(&bh->b_wait); //bh在哪? bh全局的,buf init那里
//不要傻等,切进程, 有主动轮询变为被动响应
sti();
}


kernel/sched.h
void sleep_on(struct task_struct **p)
{
struct task_struct *tmp;
// 有的是请求项 有的是缓冲块等待队列
if (!p)
return;
if (current == &(init_task.task))
panic("task[0] trying to sleep");
tmp = *p; // bh->b_wait
*p = current;
current->state = TASK_UNINTERRUPTIBLE;
schedule();
if (tmp)
tmp->state=0;
}

27、b_dirt已经被置为1的缓冲块,同步前能够被进程继续读、写?给出代码证据。

p331

​ 要回答这个问题先得了解下b_uptodate,b_uptodate设置为1的时候(P326),标志着缓冲块中的数据是基于硬盘数据块的,内核可以放心地支持进程与缓冲块进行数据交互.

​ 此时,读操作不会改写缓冲块的数据,所以不会影响硬盘数据块的内容,如果写的话,就需要改变缓冲块的内容,此时,将b_dirt置为1,在同步前,当然可以继续读写了..我觉得这很显然.

​ 老师大概想表达的意思是,b_dirt虽然被置为1了,但是在此之前,硬盘->缓冲块这一路径已经同步过了,没有改写的部分是同步的,改写的那肯定就是改写的,所以不影响我们继续读写, b_uptodate仍然设置为1,它为1就标志着我们仍然可以读写

代码证据

​ 想要的证据是说读写都不需要检查b_dirt位嘛?或者说改变了b_dirt不会改变b_uptodate?

P331file_write

P314file_read

P330bread getblk

1

28、分析panic函数的源代码,根据你学过的操作系统知识,完整、准确的判断panic函数所起的作用。假如操作系统设计为支持内核进程(始终运行在0特权级的进程),你将如何改进panic函数?

赵炯p175

kernel/panic.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* linux/kernel/panic.c
*
* (C) 1991 Linus Torvalds
*/

/*
* This function is used through-out the kernel (includeinh mm and fs)
* to indicate a major problem.
*/
#include <linux/kernel.h>
#include <linux/sched.h>

void sys_sync(void); /* it's really int */

volatile void panic(const char * s)
{
printk("Kernel panic: %s\n\r",s);
if (current == task[0])
printk("In swapper task - not syncing\n\r");
else
sys_sync();
for(;;);
}

sys_sync 在 fs/buffer.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int sys_sync(void)
{
int i;
struct buffer_head * bh;

sync_inodes(); /* write out inodes into buffers */
bh = start_buffer;
for (i=0 ; i<NR_BUFFERS ; i++,bh++) {
wait_on_buffer(bh);
if (bh->b_dirt)
ll_rw_block(WRITE,bh);
}
return 0;
}

29、详细分析进程调度的全过程。考虑所有可能(signal、alarm除外)

p103 p125

30、wait_on_buffer函数中为什么不用if()而是用while()?

​ 因为它可能会执行很多次呀,要进行轮询,if只能判断一次,那为啥可能会执行很多次呢? 因为操作系统是很复杂,存在很多种可能,其中一种就是:

​ 很多歌进程都在等待同一个缓冲块,在缓冲块同步完毕的时候,唤醒各个等待进程到轮转到某一个进程的过程中,很有可能此时的缓冲块又被其他进程占用了,并且被加上了锁. 如果用if的话,只判断一次,就会出现错误,while的话则会重新判断.

1
2
3
4
5
6
7
8
9
10
fs/buffer.c

static inline void wait_on_buffer(struct buffer_head * bh)
{
cli();
while (bh->b_lock)
sleep_on(&bh->b_wait); //bh在哪? bh全局的,buf init那里
//不要傻等,切进程, 有主动轮询变为被动响应
sti();
}

31、操作系统如何利用b_uptodate保证缓冲块数据的正确性?new_block (int dev)函数新申请一个缓冲块后,并没有读盘,b_uptodate却被置1,是否会引起数据混乱?详细分析理由。

p325、328、329

​ b_uptodate针对进程方向,它的作用是告诉内核,只要缓冲块的b_uptodate位1,则缓冲块的数据就是数据块中最新的了,可以放心地支持进程共享缓冲块的数据.反之如果为0,就提醒内核缓冲块并没有用绑定的数据块中的数据更新,不支持进程共享该缓冲块

​ new_block是在设备商申请一个新的数据块,那么数据块里面此时是脏数据,不用管(是要管的,需要清零),那为啥把b_uptodate设置为1呢,因为缓冲块里也是脏数据,缓冲块和数据块都是脏数据,根本就不需要同步,同步也是浪费时间和精力

​ 下面进行详细的分析,新建的数据块只能用于两种用途,一种是存储文件的内容,一种是存储文件的i_zone的间接块管理信息.

​ 如果是存储文件的内容的话,就是上面所说,都是垃圾数据,不需要同步,不需要清零的其实,问题已经解决(或者说本身没有问题)

​ 如果是存储i_zone的间接块管理信息,则必须将缓冲块清零,表示没有索引间接数据块,否则垃圾数据会导致索引错误,破坏文件操作的正确性,这个时候虽然缓冲块和硬盘数据块的数据不一致,但和第一种情况一样,b_uptodate设置为1即可

1
2
3
4
5
6
7
8
9
10
11
12
13
fs/Bitmap.c
int new_block(int dev)
{
.........
if (bh->b_count != 1)
panic("new block: count is != 1");
clear_block(bh->b_data);
bh->b_uptodate = 1;
bh->b_dirt = 1;
brelse(bh);
return j;
}

32、add_request()函数中有下列代码   其中的xxx是什么意思?

p121

赵炯的书p202

​ 测试块设备的当前请求项指针是不是为空(也就是没有请求项,设备空闲),如果是的话,就会设置该新建的请求项为当前请求项,作为请求项链表的表头

kernel/blk_dev/ll_rw_block.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* add-request adds a request to the linked list.
* It disables interrupts so that it can muck with the
* request-lists in peace.
*/
static void add_request(struct blk_dev_struct * dev, struct request * req)
{
.....
if (!(tmp = dev->current_request)) {
dev->current_request = req;
sti();
(dev->request_fn)(); //
return;
}
.....
}

问的是,以下代码
if (!(tmp = dev->current_request)) {
dev->current_request = req;

33、do_hd_request()函数中dev的含义始终一样吗?

p122

赵炯p195

kernel/blk_dev/hd.c

34、read_intr()函数中,下列代码是什么意思?为什么这样做?

p131

​ read_intr()函数会将已经读到硬盘缓存中的数据复制到刚才被锁定的那个缓冲块中(注意,锁定的意思是阻止进程方面的操作,而不是阻止外设方面的操作)

​ 但是一次不一定就读完呀,所以就会有下面的代码,来判断请求项对应的缓冲块的数据是否读完了,如果没有读完的话,内核将再次把read_intr()绑定在硬盘中断服务程序上,以待下次使用,之后中断服务程序返回

​ 其实还没太理解这个–,是减的什么东西

kernel/blk_dev/hd.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static void read_intr(void)
{
if (win_result()) {
bad_rw_intr();
do_hd_request();
return;
}
port_read(HD_DATA,CURRENT->buffer,256);
CURRENT->errors = 0;
CURRENT->buffer += 512;
CURRENT->sector++;
if (--CURRENT->nr_sectors) {
do_hd = &read_intr; //再来一次
return;
}
end_request(1); //
do_hd_request();
}

问的是这一段代码是什么意思
if (--CURRENT->nr_sectors) {
do_hd = &read_intr; //再来一次
return;
}

​ 请求项的结构体在哪?

kernel/lkd_drv/blk.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* Ok, this is an expanded form so that we can use the same
* request for paging requests when that is implemented. In
* paging, 'bh' is NULL, and 'waiting' is used to wait for
* read/write completion.
*/
struct request {
int dev; /* -1 if no request */
int cmd; /* READ or WRITE */
int errors;
unsigned long sector;
unsigned long nr_sectors;
char * buffer;
struct task_struct * waiting;
struct buffer_head * bh;
struct request * next;
};

35、bread()函数代码中为什么要做第二次if (bh->b_uptodate)判断?

p112、134. 赵炯 p342

第一次是在找有没有被使用过的

fs/buffer.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* bread() reads a specified block and returns the buffer that contains
* it. It returns NULL if the block was unreadable.
*/
struct buffer_head * bread(int dev,int block)
{
struct buffer_head * bh;
//
if (!(bh=getblk(dev,block))) //找不到就应该继续等,所以不应该为空
panic("bread: getblk returned NULL\n");
if (bh->b_uptodate)
return bh;
ll_rw_block(READ,bh); //开始读写硬盘了,硬盘驱动
wait_on_buffer(bh);
if (bh->b_uptodate)
return bh;
brelse(bh);
return NULL;
}

36、getblk()函数中,两次调用wait_on_buffer()函数,两次的意思一样吗?

p125 bread里面一次

37、getblk()函数中    do {        if (tmp->b_count)            continue;        if (!bh || BADNESS(tmp)<BADNESS(bh)) {            bh = tmp;            if (!BADNESS(tmp))                break;        }/* and repeat until we find something good */    } while ((tmp = tmp->b_next_free) != free_list);说明什么情况下执行continue、break。

38、make_request()函数        if (req < request) {        if (rw_ahead) {            unlock_buffer(bh);            return;        }        sleep_on(&wait_for_request);        goto repeat;

其中的sleep_on(&wait_for_request)是谁在等?等什么?

参考资料

《Linux内核设计的艺术 第二版》 新设计团队

《Linux内核完全注释》 赵炯

《IA32》 手册 第三卷

https://www.likecs.com/show-204742912.html

https://github.com/sunym1993/flash-linux0.11-talk