进程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++)
{
;
}
}
}
这三个进程执行顺序为:进程A先执行,之后进程B执行,最后进程C执行。这三个进程没有父子关系。
进程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\n\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\n");
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\n\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();
}
加锁,发出请求后,硬盘就开始工作了,把硬盘中的数据独到硬盘缓冲区,每读完一块就会发出中断。
程序接着往下执行到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执行。与此同时,硬盘也正在向数据寄存器端口中传递数据。
进程B的执行流程与进程A大致一致,不过open_namei中获取的hello.txt文件的i节点有所不同,找到了现成的hello.txt文件的i节点,引用计数增加。
还有一处不同点就是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,与此同时,硬盘也正在向数据寄存器端口中传递数据。
进程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执行。与此同时,硬盘也正在向数据寄存器端口中传递数据。
此时进程A、进程B、进程C都已经被挂起了,系统中所有的进程又都处于非就绪态了。所以默认切换到进程0去执行,知道数据读取完毕,硬盘产生中断。
硬盘中断产生后,中断服务程序将开始工作,此时硬盘已经将指定的数据全部载入缓冲块。中断服务程序开始工作后,将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\n\r");
printk("dev %04x, block %d\n\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\n\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++);
至此,就全部分析完了。