进程通过系统调用,从而进入中断处理,中断处理从系统调用表里找到sys_read函数执行。
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;
if (!count)
return 0;
verify_area(buf,count);
inode = file->f_inode;
// 该文件描述符对应的是一个管道文件,并且是读端则读管道
if (inode->i_pipe)
return (file->f_mode&1)?read_pipe(inode,buf,count):-EIO;
if (S_ISCHR(inode->i_mode))
return rw_char(READ,inode->i_zone[0],buf,count,&file->f_pos);
if (S_ISBLK(inode->i_mode))
return block_read(inode->i_zone[0],&file->f_pos,buf,count);
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;
}
我们这里只分析普通文件的读写。所以我们继续看file_read函数。
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) {
// bmap取得该文件偏移对应的硬盘块号,然后读进来
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++); //复制到buf里
brelse(bh);
} else {
while (chars-->0)
put_fs_byte(0,buf++);
}
}
// 更新访问时间
inode->i_atime = CURRENT_TIME;
// 返回读取的长度,如果一个都没读则返回错误
return (count-left)?(count-left):-ERROR;
}
这个函数主要的操作是从底层读取对应文件的对应数据。这里的底层首先是buffer缓存,如果没有的话需要去硬盘读。我们看bread函数。
struct buffer_head * bread(int dev,int block)
{
struct buffer_head * bh;
// 先从buffer链表中获取一个buffer
if (!(bh=getblk(dev,block)))
panic("bread: getblk returned NULL\n");
// 之前已经读取过并且有效,则直接返回
if (bh->b_uptodate)
return bh;
// 返回读取硬盘的数据
ll_rw_block(READ,bh);
//ll_rw_block会锁住bh,所以会先阻塞在这然后等待唤醒
wait_on_buffer(bh);
// 底层读取数据成功后会更新该字段为1,否则就是读取出错了
if (bh->b_uptodate)
return bh;
brelse(bh);
return NULL;
}
bread函数首先从缓存里读取需要的数据,如果有并且是有效的即最新的数据。则直接返回。如果没有的话,就调用ll_rw_block函数到硬盘读。我们看一下ll_rw_block及相关函数。
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)
{
struct request * req;
int rw_ahead;
/* WRITEA/READA is special case - it is not really needed, so if the */
/* buffer is locked, we just forget about it, else it's a normal read */
if (rw_ahead = (rw == READA || rw == WRITEA)) {
// 预读写的时候,buffer被锁则直接返回,因为预读本身不是必须的
if (bh->b_lock)
return;
if (rw == READA)
rw = READ;
else
rw = WRITE;
}
if (rw!=READ && rw!=WRITE)
panic("Bad block dev command, must be R/W/RA/WA");
// 锁住buffer导致bread阻塞
lock_buffer(bh);
/*
写但数据块装载后还没有被修改过
读但内容和硬盘的内容是一致的
*/
if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) {
unlock_buffer(bh);
return;
}
repeat:
/* we don't allow the write-requests to fill up the queue completely:
* we want some room for reads: they take precedence. The last third
* of the requests are only for reads.
*/
// 请求队列1/3用于读,2/3用于写
if (rw == READ)
req = request+NR_REQUEST;
else
req = request+((NR_REQUEST*2)/3);
/* find an empty request */
while (--req >= request)
// 小于0说明该结构没有被使用
if (req->dev<0)
break;
/* if none found, sleep on new requests: check for rw_ahead */
// 没有找到可用的请求结构
if (req < request) {
// 预读写则直接返回
if (rw_ahead) {
unlock_buffer(bh);
return;
}
// 阻塞等待可用的请求结构
sleep_on(&wait_for_request);
// 被唤醒后重新查找
goto repeat;
}
/* fill up the request-info, and add it to the queue */
req->dev = bh->b_dev;
req->cmd = rw;
req->errors=0;
req->sector = bh->b_blocknr<<1; // 一块等于两个扇区所以乘以2,即左移1位,比如要读地10块,则读取第二十个扇区
req->nr_sectors = 2;// 一块等于两个扇区,即读取的扇区是2
req->buffer = bh->b_data;
req->waiting = NULL;
req->bh = bh;
req->next = NULL;
// 插入请求队列
add_request(major+blk_dev,req);
}
static void add_request(struct blk_dev_struct * dev, struct request * req)
{
struct request * tmp;
req->next = NULL;
cli();
if (req->bh)
req->bh->b_dirt = 0;
// 当前没有请求项,开始处理请求
if (!(tmp = dev->current_request)) {
dev->current_request = req;
sti();
(dev->request_fn)();
return;
}
// 电梯算法插入相应的位置
for ( ; tmp->next ; tmp=tmp->next)
if ((IN_ORDER(tmp,req) ||
!IN_ORDER(tmp,tmp->next)) &&
IN_ORDER(req,tmp->next))
break;
req->next=tmp->next;
tmp->next=req;
sti();
}
我们看到,这里是给一个队列插入了一个请求节点。那么这个队列是啥呢?继续看驱动程序的代码。系统有一张表,保存了驱动程序需要处理的请求和处理函数。
struct blk_dev_struct {
void (*request_fn)(void);
struct request * current_request;
};
struct blk_dev_struct blk_dev[NR_BLK_DEV] = {
{ NULL, NULL }, /* no_dev */
{ NULL, NULL }, /* dev mem */
{ NULL, NULL }, /* dev fd */
{ NULL, NULL }, /* dev hd */
{ NULL, NULL }, /* dev ttyx */
{ NULL, NULL }, /* dev tty */
{ NULL, NULL } /* dev lp */
};
struct request {
int dev; /* -1 if no request */
int cmd; /* READ or WRITE */
int errors;
unsigned long sector;
unsigned long nr_sectors;
char * buffer;
struct task_struct * waiting;
struct buffer_head * bh;
struct request * next;
};
我们看硬盘驱动的初始化代码。
void hd_init(void)
{
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
set_intr_gate(0x2E,&hd_interrupt);
outb_p(inb_p(0x21)&0xfb,0x21);
outb(inb_p(0xA1)&0xbf,0xA1);
}
#define DEVICE_REQUEST do_hd_reques
do_hd_reques函数就是摘取待处理的请求队列中摘下一个节点然后进行处理。我们这里是读取的操作,所以只看相关代码。把命令和参数写入硬盘控制器。
hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr);
=>
static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect,
unsigned int head,unsigned int cyl,unsigned int cmd,
void (*intr_addr)(void))
{
register int port asm("dx");
if (drive>1 || head>15)
panic("Trying to write bad sector");
if (!controller_ready())
panic("HD controller not ready");
// 数据准备好触发中断时执行的回调,在blk.h定义,每个驱动都维护了自己的do_hd
do_hd = intr_addr;
outb_p(hd_info[drive].ctl,HD_CMD);
port=HD_DATA;
outb_p(hd_info[drive].wpcom>>2,++port);
outb_p(nsect,++port);
outb_p(sect,++port);
outb_p(cyl,++port);
outb_p(cyl>>8,++port);
outb_p(0xA0|(drive<<4)|head,++port);
outb(cmd,++port);
}
至此,驱动到硬盘控制器的处理完成。我们回到ll_rw_block函数处理,继续往下看,发现执行了
wait_on_buffer(bh);
我们看wait_on_buffer的代码
// 加锁,互斥访问
static inline void wait_on_buffer(struct buffer_head * bh)
{
cli();
while (bh->b_lock)
sleep_on(&bh->b_wait);
sti();
}
// 当前进程挂载到睡眠队列p中,p指向队列头指针的地址
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");
/*
*p为第一个睡眠节点的地址,即tmp指向第一个睡眠节点
头指针指向当前进程,这个版本的实现没有采用真正链表的形式,
他通过每个进程在栈中的临时变量形成一个链表,每个睡眠的进程,
在栈里有一个变量指向后面一个睡眠节点,然后把链表的头指针指向当前进程,
然后切换到其他进程执行,当被wake_up唤醒的时候,wake_up会唤醒链表的第一个
睡眠节点,因为第一个节点里保存了后面一个节点的地址,所以他唤醒后面一个节点,
后面一个节点以此类推,从而把整个链表的节点唤醒,这里的实现类似nginx的filter,
即每个模块保存后面一个节点的地址,然后把全局指针指向自己。
*/
tmp = *p;
*p = current;
// 不可中断睡眠只能通过wake_up唤醒,即使有信号也无法唤醒
current->state = TASK_UNINTERRUPTIBLE;
schedule();
// 唤醒后面一个节点
if (tmp)
tmp->state=0;
}
因为bh在ll_rw_block中被加锁了,所以进程被阻塞在这。系统调度其他进程执行。
时间过了很久…
硬盘读好了数据,给系统发了中断。从硬盘驱动的初始化函数中(参考上面的hd_init)我们发现。硬盘中断的处理函数是hd_interrupt。该函数是用汇编定义的。
_hd_interrupt:
pushl %eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax
mov %ax,%fs
movb $0x20,%al
outb %al,$0xA0 # EOI to interrupt controller #1
jmp 1f # give port chance to breathe
1: jmp 1f
1: xorl %edx,%edx
// 把do_hd的内容和edx的交换
xchgl _do_hd,%edx
// 判断do_hd是否有效
testl %edx,%edx
jne 1f
movl $_unexpected_hd_interrupt,%edx
1: outb %al,$0x20
// 执行注册的回调
call *%edx # "interesting" way of handling intr.
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
该函数执行了do_hd执行的函数。该函数就是在执行do_hd_request时注册的read_intr。
阻塞
static void read_intr(void)
{
if (win_result()) {
bad_rw_intr();
do_hd_request();
return;
}
// 从硬盘控制器的缓存读取数据
port_read(HD_DATA,CURRENT->buffer,256);
CURRENT->errors = 0;
CURRENT->buffer += 512;
CURRENT->sector++;
// 还有数据要读,继续注册该函数,等待中断回调
if (--CURRENT->nr_sectors) {
do_hd = &read_intr;
return;
}
// 结束该request,通知上层进程
end_req
uest(1);
// 处理下一个request
do_hd_request();
}
// 数据读写完后执行该函数
extern inline void end_request(int uptodate)
{
DEVICE_OFF(CURRENT->dev);
// 读写数据成功,数据有效位置1
if (CURRENT->bh) {
CURRENT->bh->b_uptodate = uptodate;
// 唤醒进程
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);
}
// 唤醒等待该request的请求,貌似暂时没有使用这个字段
wake_up(&CURRENT->waiting);
// 有request可用了
wake_up(&wait_for_request);
CURRENT->dev = -1;
// 更新请求队列,移除当前处理完的节点
CURRENT = CURRENT->next;
}
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);
}
至此,数据读取的过程差不多就结束了,等系统调度时选择该进程执行,然后进程从buffer里就获取了需要的数据,再返回到应用层。