1、用户进程与内存管理
父子进程共享同一页面,如果这个页面设置为可写状态,那么两个进程同时写一个页面会造成混乱,所以此时页表表项后三位设置为101,U/S=1,
R/W=0,P=1。对应代码如下:
代码路径:mm/memory.c
int copy_page_tables(unsigned long from,unsigned long to,long size)
{
...
for( ; size-->0 ; from_dir++,to_dir++) {
if (1 & *to_dir)
panic("copy_page_tables: already exist");
if (!(1 & *from_dir))
continue;
from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
if (!(to_page_table = (unsigned long *) get_free_page()))
return -1; /* Out of memory, see freeing */
*to_dir = ((unsigned long) to_page_table) | 7;
nr = (from==0)?0xA0:1024;
for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
this_page = *from_page_table;
if (!(1 & this_page))
continue;
this_page &= ~2;//页表项中的R/W位被设置为0,~2为101
*to_page_table = this_page;
if (this_page > LOW_MEM) {
*from_page_table = this_page;
this_page -= LOW_MEM;
this_page >>= 12;
mem_map[this_page]++;//父子进程共享,引用计数记录在mem_map中,累加为2
...
}
如果父子进程都要写页面,该怎么办呢?
假设进程A是父进程,B是子进程。进程A要往共享页面写数据,因为该页面为只读,会产生页写保护,页写保护为进程A申请新页面,并且引用计数递
减1,并让进程A的页表中指向原页面的页表项改为指向新页面,并将其使用的属性从“只读”改变为“可读可写”,一切准备就绪后,将原页面中的内容复制到新页面中。具体结果如下图:
进程A执行一段时间后,就该轮到它的子进程-进程B执行了。进程B仍然使用着原页面。假设也要在原页面总进程写操作,但是现在原页面的属性仍然
是“只读”的,这一点在进程A创建B时就是这样设置的,一直都没有改变过。所以在这种情况下,又需要进程页写保护,由于原页面的引用计数已经被消减为1了 ,所以现在就要将院页面的属性设置为“可读可写”。如下图:
页写保护,代码路径:mm/memory.c
void un_wp_page(unsigned long * table_entry)
{
unsigned long old_page,new_page;
old_page = 0xfffff000 & *table_entry;
if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {//发现原页面引用计数为1,不用共享了
*table_entry |= 2;//010,R/W位设置为1,可读可写
invalidate();
return;
}
if (!(new_page=get_free_page()))//申请到新页面
oom();
if (old_page >= LOW_MEM)
mem_map[MAP_NR(old_page)]--;//页面引用计数递减1
*table_entry = new_page | 7;//111,标志着新页面可读可写了
invalidate();
copy_page(old_page,new_page);//将原页面内容复制给进程A新申请的页面
}
进程切换,一是时间片到了,切换,且只有在3特权级下才能切换,0特权级下不能切换;二是等待硬盘读盘时,要切换到其他进程。
代码路径:kernel/sched.c
void do_timer(long cpl)
{
...
if ((--current->counter)>0) return;//判断时间片是否消减为0
current->counter=0;
if (!cpl) return;//只有在3特权级下才能切换,0特权级下不能切换
schedule();
}
缺页中断是因为页表项中P位为0。
创建子进程时,不能用父进程的时间片复制给子进程的时间片,因为父进程的时间片不断减少,应该用优先级赋值给时间片。
p->counter=p->priority;
另外我加载shell进程的eip可能是就是shell进程的逻辑偏移,而不是原来我分析的0。
2、缓冲区和多进程操作文件
之所以需要缓冲区,是因为有了缓冲区后,数据交换快。你可能会问,进程内存空间和硬盘交换数据,中间加了一个缓冲区,由于缓冲区也是内存,只是徒劳增加了一个中介,其实则不然,快的原因是因为缓冲区共享。如果A把硬盘数据读到了缓冲区,那么不会立即释放,此时B也可以共享缓冲区,也就加快了速度。
那么问题就变成了如何让数据在缓冲区中停留的时间尽可能长?
首先通过建立hash表,getblk时候首先查询hash表中是否要申请的缓冲块。如果没有,那么在申请一个b_count为0缓冲块,而且申请后要放入链表的最后,这样做,下次申请时会从头申请,这样就使刚申请的缓冲块尽可能多的不被马上重新申请。
b_update针对进程方向,它的作用是,告诉内核,只要缓冲块中b_update字段被设置为1,缓冲块的数据已经是数据块中最新的,就可以放心地支持进程共享缓冲块的数据。
b_dirt是针对硬盘方向的。只要缓冲块的b_dirt字段被设置为1,就是告诉内核,这个缓冲块中的内容已经被进程方向的数据改写了,最终需要同步到硬盘上。
(1)进程的本意是要读文件中这个数据块到进程空间。(2)申请一个缓冲块与硬盘数据块绑定,但硬盘数据并没有同步更新到缓冲块,b_update为0。(3)此时进程开始读取缓冲块的数据,那就都是垃圾数据。
(1)进程的本意是把进程空间一部分数据写到已有数据的硬盘数据块。(2)申请一个缓冲块与硬盘数据块绑定,但硬盘数据并没有同步更新到缓冲块,b_update为0。(3)将进程空间的一部分数据写到缓冲块中,但是后一部分是垃圾数据,当同步到外部硬盘时,垃圾数据也被写到硬盘数据块中了。
(1)新建硬盘数据块。(2)申请一个缓冲块与硬盘数据块绑定,b_update设置为1,缓冲块的数据全部清零。(3)此时新建的不可能读,只有写,将进程空间一部分数据写到缓冲块,其余部分为0,同步到新建硬盘数据块中,没有问题。
(1)缓冲块和硬盘数据块数据一致了,b_update为1。(2)往缓冲块中写入新的数据,此时b_dirt为1,但b_update还应该为1,因为此时并不妨碍进程读(虽然和硬盘上的数据不一致,但迟早是要一致,因为b_dirt为1)和写缓冲块。
i_dirt=1表示i节点已经被更新,后来同步i节点的时候i_dirt设置为0,b_dirt=1。
i_update没有用,因为这些文件管理信息在硬盘上都是以数据块的形式存在的,它们以块的形式载入缓冲区,也就是会用b_update。
s_dirt没有用,因为进程全部从super_block[8]中读取信息,并没有往表项中写入数据。
b_count表示缓冲块被进程占用的数量,新建为1,释放减1,共享加1。
i_count表示inode_table[32]对应节点的引用数量。
b_lock表示不能在缓冲块数据同步到硬盘块的同时,还在向这个缓冲块中新的数据。
b_wait在缓冲块被加锁的过程中,而且无论有多少进程申请到了这个缓冲块,都不能立即操作该缓冲块,都要挂起,并切换到其他进程去执行。这就需要记录有哪些进程因为等待这个缓冲块的解锁而被挂起了,这就需要b_wait。同步到硬盘块后,首先解锁,然后唤醒等待队列。
i_lock、i_wait、s_lock、s_wait同b_lock、b_wait一样。
request如果申请不到请求项,那么利用waiting挂起,end_request中wake_up,那么等待请求项的进程被唤醒。
getblk函数如下:
#define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)
struct buffer_head * getblk(int dev,int block)
{
struct buffer_head * tmp, * bh;
repeat:
if ((bh = get_hash_table(dev,block)))
return bh;
tmp = free_list;
do {
if (tmp->b_count)
continue;
if (!bh || BADNESS(tmp)<BADNESS(bh)) {//优先选择b_lock为1
bh = tmp;
if (!BADNESS(tmp))
break;
}
/* and repeat until we find something good */
} while ((tmp = tmp->b_next_free) != free_list);
if (!bh) {//最终也没有找到b_count为0的缓冲块
sleep_on(&buffer_wait);//当前进程只好挂起
goto repeat;
}
}
如果找到了b_count为0,如果b_lock和b_dirt均为0,那就是在好不过了,如果不为0,优先选择b_lock为1的,其次再选择b_dirt为1的,因为b_lock为1,说明缓冲块正在跟硬盘交互数据,交互完了,最终轮到当前进程使用。而b_dirt为1,说明在建立新的绑定关系之前,肯定需要把数据同步到硬盘,同步的时候肯定要加锁-b_lock为1.所以,选择b_lock为1比选择b_dirt为1的,少等待由b_dirt为1到b_lock为1的时间。