文章目录
1 设备驱动
- 还是从那台计算机开始
1.1 让外设工作起来
- CPU向控制器中的寄存器,读写数据
- 控制器完成真正的工作,并向CPU发中断信号
- 总的来说就是给控制器当中的存储器/寄存器写东西,发一个指令,然后控制器就会处理
- 如何让外设执行起来?向控制器发指令,而向控制器发指令,就是查查控制器对应哪个端口或者对应什么东西,所以实际上那么多代码的核心就是
out xx, al
这样的指令,写这一句的原因就是让外设工作的更简单,当外设完事后就做一下中断的处理,这就是外设工作的全部秘密 - 就是CPU向外设寄存器发出指令然其工作,完了之后外设再向CPU发出终端请求
- 向设备控制器的寄存器写不就可以了吗?
- 需要查寄存器地址、内容的格式和语义操作系统要给用户提供一个简单
视图,即文件视图,这样方便,因为无论使用什么样的显卡,到操作系统上都是printf("xxx")
,所以最终就是形成一个统一的接口
1.2 操纵外设的程序
int
fd open (/dev/xxx");
for (int i = 0; i < 10; i++){
write(fd, i, sizeof(int));
}
close(fd);
- 不论什么设备都是open, read, write, close
- 操作系统为用户提供统一的接口
- 不同的设备对应不同的设备文件(/dev/xxx)
- 根据设备文件找到控制器的地址、内容格式等等
1.3 文件视图
- orw之类的命令,根据文件名来找到对应的硬件再进行处理
- 做完了就用中断处理,再回到文件视图里
- 中断处理:键盘获得了输入字符,放到缓冲区,通知cpu取数据
- 响应设备中断的 ,比如按下键盘k,就把键盘寄存器里存的按下去的键的映射给操作系统进入内存
1.4 printf
- 无论访问的是什么设备,都是从文件接口开始,也就是系统调用那部分说过
write(1, buf)
就是落实下来就是out
指令- 设备虽然不同,但是接口都是
read
,write
write
通过系统调用,通过int 0x80
到了内核里面,执行的就是sys_write
- 关键在于
write(1, )
这个1很重要,1决定了是往显示器上写 - 1对应的形参是
int fd
,fd是文件描述符,0:标准输入,1:标准输出,2:标准错误 file = current->filp[fd];
inode = file->f_inode;
- current就是当前进程,这个数组的第一项就会赋值给文件file
- 1这个地方对应了一个文件,实际上是1这个地方打开了文件
- 然后inode就被打开了,inode里面就会取出到底往哪个地方输出
- 既然是1,就要知道1对应的这个文件是什么,从而才能知道file这个文件存放的信息是什么
- 1是从PCB来的,那么PCB是怎么来的?
- PCB是fork()创建的时候,拷贝来的
- 即
copy_process()
- 都是从父进程那拷贝来的
- 因为父子进程文件标识fd是数据,不写更改时是不需要复制的。这个的++操作的对象是被操作的文件,目的是文件信息更新(使用者加一)
- filp[i]是打开文件的指针
- 所有进程打开的指针都是父进程那里拷贝来的
- 果然打开了这个文件,并且拷贝了两份,dup(0),duplicate复制
- tty0是中断设备
1.5 open系统调用
- 根据传入文件的名字,会把文件读入进来,最终实际上读入文件的inode
- inode就是这个文件存放在磁盘上的一些信息
- 对于设备文件就有对应的是哪个设备,哪个类型这些信息
- 操作系统得到了这些信息,就会知道往哪里走,但是最终的都是显卡的out
- open就是形成如下这个链
- 1实际上就是找到这个dev/tty0对应的inode
- 这个inode里面肯定有信息,把信息读进来
- 根据这些信息选择走哪条路,磁盘,显示器等等
- 已经读进来的,看看信息是不是char设备
if (I_ISCHR())
就是判断是否是字符设备(计算机通常分为字符设备以及块设备)rw_char
就是读写,还有既然是char设备也得告诉你是哪一个char设备,inode里面肯定有一个字段存放这个设备到底是第几个设备- 如下的
i_zone[0]
就是给出的char 设备号 - 所以创建文件就得写进去
int rw char(int rw, int dev, char *buf, int cnt)
crw_ptr call_addr = crw_table [MAJOR(dev)];
call_addr(rw, dev, buf, cnt); ...}
- 这个表里面存放的是函数指针
- 根据这个函数指针,根据你是第几个字符设备就可以找到对应的处理函数
1.6 crw_table
- 接下来跳到rw_ttyx
- 终端设备就是键盘和显示器,显示器是写的,键盘是读的
1.7 tty->write
- *buf工作在用户态内存,从用户态内存取一个字符,放到队列调用函数进行输出
- 文件视图 -> 字符设备 -> tty字符设备 -> 写到缓冲区 -> 调用函数 -> 提取缓冲区 -> 往各个地方进行写入 -> 接下来就是out
- fd–>找inode(文件类型)–>确定了是tty(显示器)–>然后转交给驱动程序(由驱动程序写寄存器)
if(c>31&&c<127){
_asm_("mov_b attr, %%ah\n\t"
"movw %%ax, %1\n\t"::"a"(c),
"m"(*(short*)pos):"ax"); pos+=2;}
- 从这里可以看出,就是al当中是数据,然后ah当中是属性
- 最后就是
mov ax, pos
,没有用out,这个pos就是显卡的控制器(或者是外设的) - 有的外设控制器可以统一和内存编址,这时候寻址用mov,然后独立编址就用out
- 现在就能打印到屏幕上了
- 这些代码形成就是设备驱动
1.8 pos的修改
-
mov pos
就是往显存里面写 -
pos从哪里来?每次写完都要+2,所以要知道初始的pos,在init里面
-
初始的pos看con_init,里面有两个参数,然后通过这两个参数可以把pos计算出来
-
然后写出这两个宏,就能知道这两个是什么
-
90000和90001,这就是第一节课当中的bootsect.s把自己和setup.s移动到内存0x90000处
-
这就是光标的位置