Linuxカーネル設計のアート-バッファブロックのプロセス待機キューについて
10929 ワード
プロセスAはリードディスクプロセスであり、helloを目的とする.txtファイルの100バイトはbuffer[100]に読み込まれる.
コードは次のとおりです.
プロセスBもリードディスクプロセスであり、helloを目的とする.txtファイルの200バイトはbuffer[200]に読み込まれます.
プロセスCはディスクを書くプロセスで、helloを目的としています.txtファイルにstr[]の文字「ABCDE」を書き込みます.
コードは次のとおりです.
この3つのプロセスの実行順序は、プロセスAが先に実行され、その後、プロセスBが実行され、最後にプロセスCが実行される.この3つのプロセスには親子関係はありません.
プロセスAが起動するとopen関数が実行され、最終的にsys_にマッピングされます.Open関数領域で実行します.
コードパス:fs/open.c
コードパス:fs/read_write.c
その後sys_read関数呼び出しfile_read()でファイルの内容を読み出します.
コードパス:fs/file_dev.c
file_read()関数はbread()関数を呼び出してハードディスクからデータを読み出す.
コードパス:fs/buffer.c
コードパス:kernel/blk_drv/ll_rw_block.c
プログラムはwait_まで実行されますon_buffer.
コードパス:fs/buffer.c
プロセスBの実行フローはプロセスAとほぼ一致するがopen_nameiで取得したhello.txtファイルのiノードが異なり、既存のhelloが見つかりました.txtファイルのiノードは、参照カウントが増加します.
もう1つの違いはgetblkであり、バッファブロックを申請すると、ハッシュテーブルに指定されたバッファブロックが見つかり、直接戻ることができます.
実行するll_rw_block時、make_を実行しますrequestを実行し、lock_を実行します.bufferの場合、コードは次のとおりです.
同時に、ハードディスク(HDD)もデータレジスタポートにデータを転送しています.
プロセスCの大まかなプロセスはプロセスBと同じであるが、write呼び出しsys_write、そしてfile_を呼び出しますwrite.コードは次のとおりです.
コードパス:fs/file_dev.c
プロセスBと同様にbread->ll_rw_block->make_request->lock_buffer->sleep_on、コードは以下の通りです.
このとき、プロセスA、プロセスB、プロセスCはすでに保留されており、システム内のすべてのプロセスは非準備状態にある.したがって、デフォルトではプロセス0に切り替えて実行し、データの読み取りが完了し、ハードディスク(HDD)が中断することを知っています.
ハードディスク(HDD)の割り込みが発生すると、割り込みサービスプログラムが動作し始め、指定したデータがすべてバッファにロードされます.割り込みサービスプログラムが動作を開始したら、bhバッファブロックをロック解除し、wake_を呼び出します.up関数は、bhのwaitフィールドに対応するプロセス(プロセスC)を起動します.実行コードは次のとおりです.
コードパス:kernel/blk_drv/blk.h
プロセスBは、次にmake_を実行するrequestは、上記のコード注釈のように、この場合は読み取りでuptodateが1であるため、bread関数が戻って、次のように真の読み取りコードを実行します.
その後、プロセスCのユーザプログラムに戻り、タイムスライスを消費して0になるまでプロセスAに切り替わる.同じくsleep_on関数で切り取りました.
実行完了wait_on_buffer、だからbread関数は戻って、本当のリードコードを実行して、以下のようにします:
コードは次のとおりです.
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++);
これで、すべての分析が完了しました.