Linuxカーネル設計のアート-バッファブロックのプロセス待機キューについて

10929 ワード

プロセスAはリードディスクプロセスであり、helloを目的とする.txtファイルの100バイトはbuffer[100]に読み込まれる.
コードは次のとおりです.
void FunA();
void main()
{
	...
	FunA();
	...
}

void FunA()
{
	char buffer[100];
	int i,j;
	int fd = open("/mnt/user/user1/user2/hello.txt",O_RDWR,0644);
	read(fd,buffer,sizeof(buffer));
	close(fd);
	
	for(i=0;i<1000000;i++)
	{
		for(j=0;i<100000;j++)
		{
			;
		}
	}
}

プロセスBもリードディスクプロセスであり、helloを目的とする.txtファイルの200バイトはbuffer[200]に読み込まれます.
void FunB();
void main()
{
	...
	FunB();
	...
}

void FunB()
{
	char buffer[200];
	int i,j;
	int fd = open("/mnt/user/user1/user2/hello.txt",O_RDWR,0644);
	read(fd,buffer,sizeof(buffer));
	close(fd);
	
	for(i=0;i<1000000;i++)
	{
		for(j=0;i<100000;j++)
		{
			;
		}
	}
}
   
プロセスCはディスクを書くプロセスで、helloを目的としています.txtファイルにstr[]の文字「ABCDE」を書き込みます.
コードは次のとおりです.
void FunC();
void main()
{
	...
	FunC();
	...
}

void FunC()
{
	char str1[]="ABCDE";
	int i,j;
	int fd = open("/mnt/user/user1/user2/hello.txt",O_RDWR,0644);
	write(fd,str1,strlen(str1));
	close(fd);
	
	for(i=0;i<1000000;i++)
	{
		for(j=0;i<100000;j++)
		{
			;
		}
	}
}

この3つのプロセスの実行順序は、プロセスAが先に実行され、その後、プロセスBが実行され、最後にプロセスCが実行される.この3つのプロセスには親子関係はありません.
プロセスAが起動するとopen関数が実行され、最終的にsys_にマッピングされます.Open関数領域で実行します.
コードパス:fs/open.c
nt sys_open(const char * filename,int flag,int mode)
{
	.../ file, inode
	(current->filp[fd]=f)->f_count++;
	if ((i=open_namei(filename,flag,mode,&inode))<0) {
	        ...
	}
        ...
	f->f_mode = inode->i_mode;
	f->f_flags = flag;
	f->f_count = 1;
	f->f_inode = inode;
	f->f_pos = 0;
	return (fd);
}
以降read関数の実行が開始され、read関数は最終的にsys_にマッピングされます.read()関数は実行されます.
コードパス:fs/read_write.c
int sys_read(unsigned int fd,char * buf,int count)
{
	struct file * file;
	struct m_inode * inode;

	if (fd>=NR_OPEN || count<0 || !(file=current->filp[fd]))
		return -EINVAL;
	...
	inode = file->f_inode;
	...
	if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode)) {
		if (count+file->f_pos > inode->i_size)
			count = inode->i_size - file->f_pos;
		if (count<=0)
			return 0;
		return file_read(inode,file,buf,count);
	}
	printk("(Read)inode->i_mode=%06o
\r",inode->i_mode); return -EINVAL; }
       
その後sys_read関数呼び出しfile_read()でファイルの内容を読み出します.
コードパス:fs/file_dev.c
int file_read(struct m_inode * inode, struct file * filp, char * buf, int count)
{
	int left,chars,nr;
	struct buffer_head * bh;

	if ((left=count)<=0)
		return 0;
	while (left) {
		if ((nr = bmap(inode,(filp->f_pos)/BLOCK_SIZE))) {
			if (!(bh=bread(inode->i_dev,nr)))
				break;
		} else
			bh = NULL;
		nr = filp->f_pos % BLOCK_SIZE;
		chars = MIN( BLOCK_SIZE-nr , left );
		filp->f_pos += chars;
		left -= chars;
		if (bh) {
			char * p = nr + bh->b_data;
			while (chars-->0)
				put_fs_byte(*(p++),buf++);
			brelse(bh);
		} else {
			while (chars-->0)
				put_fs_byte(0,buf++);
		}
	}
	inode->i_atime = CURRENT_TIME;
	return (count-left)?(count-left):-ERROR;
}
       
file_read()関数はbread()関数を呼び出してハードディスクからデータを読み出す.
コードパス:fs/buffer.c
struct buffer_head * bread(int dev,int block)
{
	struct buffer_head * bh;

	if (!(bh=getblk(dev,block)))// 
		panic("bread: getblk returned NULL
"); if (bh->b_uptodate)//uptodate 0 return bh; ll_rw_block(READ,bh);// , wait_on_buffer(bh); if (bh->b_uptodate) return bh; brelse(bh); return NULL; }
getblkは空きバッファブロックを申請し、ll_を呼び出した.rw_blockは、バッファブロックをロックしてリクエストアイテムにバインドし、リードディスクコマンドを送信します.
コードパス:kernel/blk_drv/ll_rw_block.c
void ll_rw_block(int rw, struct buffer_head * bh)
{
	unsigned int major;

	if ((major=MAJOR(bh->b_dev)) >= NR_BLK_DEV ||
	!(blk_dev[major].request_fn)) {
		printk("Trying to read nonexistent block-device
\r"); return; } make_request(major,rw,bh); }
static void make_request(int major,int rw, struct buffer_head * bh)
{
	...
	lock_buffer(bh);// 
        if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) {// , uptodate 1, , B C  
            unlock_buffer(bh);  
            return;  
        } 
	...
	add_request(major+blk_dev,req);// 
}
static inline void lock_buffer(struct buffer_head * bh)
{
	cli();
	while (bh->b_lock)// , 
		sleep_on(&bh->b_wait);
	bh->b_lock=1;
	sti();
}
ロックをかけて、要求を出した後、ハードディスクは仕事を始めて、ハードディスクの中のデータをハードディスクのバッファに1つだけ持って行って、1つを読むたびに中断を出します.
プログラムはwait_まで実行されますon_buffer.
コードパス:fs/buffer.c
static inline void wait_on_buffer(struct buffer_head * bh)
{
	cli();
	while (bh->b_lock)// 
		sleep_on(&bh->b_wait);
	sti();
}
コードパス:kernel/sched.c
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;// tmp NULL
	*p = current;//bh->wait A task_struct 
	current->state = TASK_UNINTERRUPTIBLE;// A 
	schedule();// 
	if (tmp)
		tmp->state=0;
}
プロセスAが保留された後、scheduleが呼び出され、プロセスB実行に切り替わる.同時に、ハードディスク(HDD)もデータレジスタポートにデータを転送しています.
プロセスBの実行フローはプロセスAとほぼ一致するがopen_nameiで取得したhello.txtファイルのiノードが異なり、既存のhelloが見つかりました.txtファイルのiノードは、参照カウントが増加します.
もう1つの違いはgetblkであり、バッファブロックを申請すると、ハッシュテーブルに指定されたバッファブロックが見つかり、直接戻ることができます.
実行するll_rw_block時、make_を実行しますrequestを実行し、lock_を実行します.bufferの場合、コードは次のとおりです.
static inline void lock_buffer(struct buffer_head * bh)
{
	cli();
	while (bh->b_lock)// , 
		sleep_on(&bh->b_wait);
	bh->b_lock=1;
	sti();
}
      
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;// tmp A task_struct 
	*p = current;//bh->wait B task_struct 
	current->state = TASK_UNINTERRUPTIBLE;// B 
	schedule();// 
	if (tmp)
		tmp->state=0;
}
その後、プロセスCに切り替え、
同時に、ハードディスク(HDD)もデータレジスタポートにデータを転送しています.
プロセスCの大まかなプロセスはプロセスBと同じであるが、write呼び出しsys_write、そしてfile_を呼び出しますwrite.コードは次のとおりです.
コードパス:fs/file_dev.c
int file_write(struct m_inode * inode, struct file * filp, char * buf, int count)
{
	off_t pos;
	int block,c;
	struct buffer_head * bh;
	char * p;
	int i=0;

/*
 * ok, append may not work when many processes are writing at the same time
 * but so what. That way leads to madness anyway.
 */
	if (filp->f_flags & O_APPEND)
		pos = inode->i_size;
	else
		pos = filp->f_pos;
	while (i<count) {
		if (!(block = create_block(inode,pos/BLOCK_SIZE)))
			break;
		if (!(bh=bread(inode->i_dev,block)))
			break;
		c = pos % BLOCK_SIZE;
		p = c + bh->b_data;
		bh->b_dirt = 1;
		c = BLOCK_SIZE-c;
		if (c > count-i) c = count-i;
		pos += c;
		if (pos > inode->i_size) {
			inode->i_size = pos;
			inode->i_dirt = 1;
		}
		i += c;
		while (c-->0)
			*(p++) = get_fs_byte(buf++);
		brelse(bh);
	}
	inode->i_mtime = CURRENT_TIME;
	if (!(filp->f_flags & O_APPEND)) {
		filp->f_pos = pos;
		inode->i_ctime = CURRENT_TIME;
	}
	return (i?i:-1);
}

プロセスBと同様にbread->ll_rw_block->make_request->lock_buffer->sleep_on、コードは以下の通りです.
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;// tmp B task_struct 
	*p = current;//bh->wait C task_struct 
	current->state = TASK_UNINTERRUPTIBLE;// C 
	schedule();// 
	if (tmp)
		tmp->state=0;
}
プロセスCが保留された後、schedule関数が呼び出され、システムに準備されたプロセスがないため、プロセス0に切り替えて実行されます.同時に、ハードディスク(HDD)もデータレジスタポートにデータを転送しています.
このとき、プロセスA、プロセスB、プロセスCはすでに保留されており、システム内のすべてのプロセスは非準備状態にある.したがって、デフォルトではプロセス0に切り替えて実行し、データの読み取りが完了し、ハードディスク(HDD)が中断することを知っています.
ハードディスク(HDD)の割り込みが発生すると、割り込みサービスプログラムが動作し始め、指定したデータがすべてバッファにロードされます.割り込みサービスプログラムが動作を開始したら、bhバッファブロックをロック解除し、wake_を呼び出します.up関数は、bhのwaitフィールドに対応するプロセス(プロセスC)を起動します.実行コードは次のとおりです.
コードパス:kernel/blk_drv/blk.h
static inline void end_request(int uptodate)
{
	DEVICE_OFF(CURRENT->dev);
	if (CURRENT->bh) {
		CURRENT->bh->b_uptodate = uptodate;//update 1
		unlock_buffer(CURRENT->bh);// , 
	}
	if (!uptodate) {
		printk(DEVICE_NAME " I/O error
\r"); printk("dev %04x, block %d
\r",CURRENT->dev, CURRENT->bh->b_blocknr); } wake_up(&CURRENT->waiting); wake_up(&wait_for_request); CURRENT->dev = -1; CURRENT = CURRENT->next; }
コードパス:kernel/blk_drv/ll_rw_blk.c
static inline void unlock_buffer(struct buffer_head * bh)
{
	if (!bh->b_lock)
		printk("ll_rw_block.c: buffer not locked
\r"); bh->b_lock = 0; wake_up(&bh->b_wait); }
コードパス:kernel/sched.c
void wake_up(struct task_struct **p)
{
	if (p && *p) {
		(**p).state=0;// C 
		*p=NULL;//bh->wait NULL
	}
}
中断サービスプログラムが終了した後、再びプロセス0に戻り、準備完了のプロセスCに切り替え、プロセスCはsleep_on関数では、schedule関数を呼び出すプロセスが次のように切り替えられます.
void sleep_on(struct task_struct **p)
{
	...
	current->state = TASK_UNINTERRUPTIBLE;// C 
	schedule();// 
	if (tmp)
		tmp->state=0;// B 
}
プロセスC、次にmake_を実行するrequestは、上記のコード注釈のように、この場合は書き込みで汚れていないため、bread関数が返され、次のように真の書き込みコードが実行されます.
while (c-->0)
			*(p++) = get_fs_byte(buf++);
以降プロセスCのユーザプログラムに戻り、タイムスライスを消費する
	for(i=0;i<1000000;i++)
	{
		for(j=0;i<100000;j++)
		{
			;
		}
	}
プロセスCのタイムスライスは0に削減され、プロセスを切り替えるには、BとCだけが準備完了状態になり、プロセスCのタイムスライスが切れたため、プロセスBに切り替えられます.プロセスBもsleep_on関数で切り替えられているのは、次のコードを見てください.
void sleep_on(struct task_struct **p)
{
	...
	current->state = TASK_UNINTERRUPTIBLE;// B 
	schedule();// 
	if (tmp)
		tmp->state=0;// A 
}
       
プロセスBは、次にmake_を実行するrequestは、上記のコード注釈のように、この場合は読み取りでuptodateが1であるため、bread関数が戻って、次のように真の読み取りコードを実行します.
while (chars-->0)
				put_fs_byte(*(p++),buf++);
        
その後、プロセスCのユーザプログラムに戻り、タイムスライスを消費して0になるまでプロセスAに切り替わる.同じくsleep_on関数で切り取りました.
void sleep_on(struct task_struct **p)
{
	...
	current->state = TASK_UNINTERRUPTIBLE;// A 
	schedule();// 
	if (tmp)// NULL 
		tmp->state=0;
}
プロセスA
実行完了wait_on_buffer、だからbread関数は戻って、本当のリードコードを実行して、以下のようにします:
while (chars-->0)
				put_fs_byte(*(p++),buf++);
これで、すべての分析が完了しました.