您的位置:首页 > 运维架构 > Linux

Linux内核设计的艺术-用户进程与内存管理、缓冲区和多进程操作文件

2014-03-13 10:09 253 查看
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的时间。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐