用大白话说操作系统(七)

OK,好的,上次我们说到了内存和中断初始化对吧,好的,现在我们继续!

读硬盘前的准备工作

从开机启动开始,操作系统调用BIOS从硬盘读取数据到内存,那时候是用汇编语言写的,具体BIOS是怎么写的有机会再学,那么现在操作系统用C语言是怎么读取硬盘的呢,用户调用读写硬盘的系统调用是怎么实现的呢???

  • 读取硬盘需要块设备驱动程序
  • 涉及内存缓冲区的管理

块设备请求项的初始化

blk_dev_init();

main函数中有上面一行代码

void blk_dev_init(void) {
    int i;
    for (i=0; i<32; i++) {
        request[i].dev = -1;
        request[i].next = NULL;
    }
}

我们可以看到代码挺简单的,但是这个request数组是啥呀,这个结构体长什么样呢?毕竟搞清楚含义才是硬道理。继续往下挖!

/*
 * Ok, this is an expanded form so that we can use the same
 * request for paging requests when that is implemented. In
 * paging, 'bh' is NULL, and 'waiting' is used to wait for
 * read/write completion.
 */
struct request {
    int dev;        /* -1 if no request */  //设备号,-1表示空闲
    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;//指向下一个请求项
};

现在试着理解一下结构体里面为什么要设置这些数据,当有一个cmd过来时,告诉我们去sector读数据,读nr_sectors的数据,读完之后放到位置在bh的buffer当中,操作过程中记录错误,大概就是这样,不知道链接下一个请求干嘛。。。接着往下看看。

读操作的系统调用函数是sys_read,粗略的展示一下。

int sys_read(unsigned int fd,char * buf,int count) {
    struct file * file = current->filp[fd];
    struct m_inode * inode = file->f_inode;
    // 校验 buf 区域的内存限制
    verify_area(buf,count);
    // 仅关注目录文件或普通文件
    return file_read(inode,file,buf,count);
}

参数1: fd,文件描述符,可以找到文件的inode,进而找到文件在硬盘中的位置。
参数2: buf,复制到内存中的位置
参数3: count,要复制的字节数
下面继续往里面挖掘file_read()这个函数做了什么。

int file_read(struct m_inode * inode, struct file * filp, char * buf, int count) {
    int left,chars,nr;
    struct buffer_head * bh;
    left = count;
    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;
}

通过一个循环每次读入一个块的数据,知道读完指定大小的数据。
继续深入bread()函数

struct buffer_head * bread(int dev,int block) {
    struct buffer_head * bh = getblk(dev,block);//申请内存中的一个缓冲块
    if (bh->b_uptodate)
        return bh;
    ll_rw_block(READ,bh);//把数据读入缓冲块
    wait_on_buffer(bh);
    if (bh->b_uptodate)
        return bh;
    brelse(bh);
    return NULL;
}

继续深入ll_rw_block()看看怎么将数据读入缓冲块

void ll_rw_block(int rw, struct buffer_head * bh) {
    ...
    make_request(major,rw,bh);
}

static void make_request(int major,int rw, struct buffer_head * bh) {
    ...
if (rw == READ)
        req = request+NR_REQUEST;
    else
        req = request+((NR_REQUEST*2)/3);
/* find an empty request */
    while (--req >= request)
        if (req->dev<0)
            break;
    ...
/* 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;
    req->nr_sectors = 2;
    req->buffer = bh->b_data;
    req->waiting = NULL;
    req->bh = bh;
    req->next = NULL;
    add_request(major+blk_dev,req);
}

这里也就是说往我们最开始的请求项链表中添加一个请求项,只要链表中还有未处理的请求都会陆续被处理,直到链表为空。

tty_init()

键盘->显示器 这个功能就是在这里实现的。哇哦哦哦哦开始兴奋了~~~~

void tty_init(void)
{
    rs_init();
    con_init();
}

可以看到,需要两个函数来共同完成
第一个,rs_init()

void rs_init(void)
{
    set_intr_gate(0x24,rs1_interrupt);
    set_intr_gate(0x23,rs2_interrupt);
    init(tty_table[1].read_q.data);
    init(tty_table[2].read_q.data);
    outb(inb_p(0x21)&0xE7,0x21);
}

这个说实话emmmmm…看不太懂,就是串口中断的开启,设置对应的中断处理程序,听说这个可以先忽略,那我就先不管了
第二个,con_init()

void con_init(void) {
    ...
    if (ORIG_VIDEO_MODE == 7) {
        ...
        if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {...}
        else {...}
    } else {
        ...
        if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {...}
        else {...}
    }
    ...
}

这里有很多的分支判断,是为了对应不同的显示模式。

显示模式

一个字符是如何显示器上显示的呢?
内存中有一个图形视频缓冲区是对应显存的,【你往这块内存中写数据就相当于写在显存中;往显存中写数据,就相当于在屏幕上输出文本】
以文本显示模式为例

#define ORIG_X          (*(unsigned char *)0x90000)
#define ORIG_Y          (*(unsigned char *)0x90001)
void con_init(void) {
    register unsigned char a;
    // 第一部分 获取显示模式相关信息
    video_num_columns = (((*(unsigned short *)0x90006) & 0xff00) >> 8);
    video_size_row = video_num_columns * 2;
    video_num_lines = 25;
    video_page = (*(unsigned short *)0x90004);
    video_erase_char = 0x0720;
    // 第二部分 显存映射的内存区域 
    video_mem_start = 0xb8000;
    video_port_reg  = 0x3d4;
    video_port_val  = 0x3d5;
    video_mem_end = 0xba000;
    // 第三部分 滚动屏幕操作时的信息
    origin  = video_mem_start;
    scr_end = video_mem_start + video_num_lines * video_size_row;
    top = 0;
    bottom  = video_num_lines;
    // 第四部分 定位光标并开启键盘中断
    gotoxy(ORIG_X, ORIG_Y);
    set_trap_gate(0x21,&keyboard_interrupt);
    outb_p(inb_p(0x21)&0xfd,0x21);
    a=inb_p(0x61);
    outb_p(a|0x80,0x61);
    outb(a,0x61);
}
  1. 往屏幕任意位置写字符,指定颜色
  2. 接受键盘中断
    接下来,继续看第四部分的gotoxy()这个函数
static inline void gotoxy(unsigned int new_x,unsigned int new_y) {
   ...
   x = new_x;//光标在哪一列
   y = new_y;//光标在哪一行
   pos = origin + y*video_size_row + (x<<1);//根据行号和列号计算出来的内存指针
}

这里给x,y,pos赋值
下面看看按下键盘,触发键盘中断,之后的程序调用是怎样的

_keyboard_interrupt:
    ...
    call _do_tty_interrupt
    ...
    
void do_tty_interrupt(int tty) {
   copy_to_cooked(tty_table+tty);
}

void copy_to_cooked(struct tty_struct * tty) {
    ...
    tty->write(tty);
    ...
}

// 控制台时 tty 的 write 为 con_write 函数
void con_write(struct tty_struct * tty) {
    ...
    __asm__("movb _attr,%%ah\n\t"
      "movw %%ax,%1\n\t"
      ::"a" (c),"m" (*(short *)pos)
      :"ax");
     pos += 2;
     x++;
    ...
}

__asm__内联汇编,是不是很兴奋,这块我会啊!!!把字符c写入pos指向的内存,这样就相当于网屏幕写了。
之后pos+=2,x++,就依次往后写呗。还需要一些逻辑进行换行、滚屏、回车等操作。
好的,OK,今天就写到这吧,有问题欢迎评论区留言讨论!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神仙诙谐代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值