前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >linux系统调用之read源码解析(基于linux0.11)

linux系统调用之read源码解析(基于linux0.11)

作者头像
theanarkh
发布2019-05-14 13:30:57
2.6K0
发布2019-05-14 13:30:57
举报
文章被收录于专栏:原创分享原创分享

进程通过系统调用,从而进入中断处理,中断处理从系统调用表里找到sys_read函数执行。

代码语言:javascript
复制
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函数。

代码语言:javascript
复制
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函数。

代码语言:javascript
复制
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及相关函数。

代码语言:javascript
复制
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();
}

我们看到,这里是给一个队列插入了一个请求节点。那么这个队列是啥呢?继续看驱动程序的代码。系统有一张表,保存了驱动程序需要处理的请求和处理函数。

代码语言:javascript
复制
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;
};

我们看硬盘驱动的初始化代码。

代码语言:javascript
复制
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函数就是摘取待处理的请求队列中摘下一个节点然后进行处理。我们这里是读取的操作,所以只看相关代码。把命令和参数写入硬盘控制器。

代码语言:javascript
复制
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的代码

代码语言:javascript
复制
// 加锁,互斥访问
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。该函数是用汇编定义的。

代码语言:javascript
复制
_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。 阻塞

代码语言:javascript
复制
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里就获取了需要的数据,再返回到应用层。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档