版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
操作系统OperatingSystem第9章设备驱动Chapter11:DeviceDriver回顾一下图灵机控制器纸带0111010对应CPU管理(进程)!对应内存管理!显示器键盘磁盘网卡等是操作系统的核心内容,让轮子转起来!I/O管理是操作系统的重要内容,它让轮子转的更好!认识计算机外设与计算机!PCI总线图形控制器IDE控制器总线控制器CPU-内存总线图灵机!扩展总线接口扩展总线并行口越来越多的任务超越了这个部分(如Web)!想一想外设怎么工作?PCI总线总线控制器CPU-内存总线IDE控制器发出一个读命令读完后向CPU发出中断将数据送往内存读磁盘CPU向控制器中的寄存器读写数据控制器完成真正的工作,并向CPU发中断信号想让外设工作并不复杂!I/O系统想给用户提供一个什么样的视图?从用户开始讲操作系统的故事总是合适的!看一段操纵外设的程序
intfd=open(“/dev/something”);
for(inti=0;i<10;i++){
fprintf(fd,”Count%d\n”,i);
}
close(fd);(1)不论什么设备都是open,read,write,close操作系统为用户提供统一的接口!(2)不同的设备对应不同的文件(设备文件)设备文件中存放了设备的属性!显然操作系统将完成…键盘命令系统调用接口open(),read(),write(),close()磁盘命令设备驱动设备属性数据进行解释键盘控制器磁盘控制器中断处理中断处理键盘磁盘称为I/O系统I/O系统如何向设备发命令?系统接口设备命令设备控制器系统接口中断处理设备中断I/O系统向设备控制器发命令设备控制器的结构readwritecontrolstatus显存寄存器组硬件控制器总线接口I/O系统向哪里发送设备发命令?读写设备控制器的寄存器!怎么读写?readwritecontrolstatus显存寄存器组硬件控制器总线接口mov[100],ax关键是地址设备寄存器的编址独立编址:
需要独立的指令(in,out),如out0x21,AL内存映像编址:
是内存物理地址空间的一部分,使用mov命令,如mov[0x8000f000],AL查查硬件手册就知道了!I/O系统发完命令后做什么?方案1:原地踏步等待!发送Read命令ReadI/O状态
检查I/O状态没有就绪从I/O读取数据就绪将数据写内存原地踏步出错处理inAL,0x??while(AL!=reday){inAL,0x??}读数据...轮询!轮询浪费CPU资源(CPU比外设快太多了)!I/O系统发完命令后做什么?方案2:设备就绪了告诉CPU一声!set_trap_gate(??,do?())do?(){inAL,0x??if(AL!=reday){error();}
读数据...}中断发送Read命令ReadI/O状态
检查I/O状态从I/O读取数据就绪将数据写内存CPU做其它工作I/O中断CPU和I/O并行出错处理中断是大部分I/O的处理方式!?中断在某些场合还不够!如果用中断来读磁盘…磁盘缓存磁盘控制器总线内存CPU中断将数据读入内存每个字节从缓存移动内存都由CPU负责完成可以设计有一定处理能力的外围设备,将一些简单任务交给它!I/O系统发完命令后做什么?方案3:简单任务自己做,完成了告诉CPU一声!引入DMA(直接内存存取)总线磁盘缓存磁盘控制器内存CPUDMA地址计数中断发送命令计数地址命令将数据读入内存幸运的是:
该方式的细节由DMA设计者考虑,对于操作系统而言,考虑的仍然只是中断处理总结一些I/O系统要完成的工作!read(buf,10);OS需要提供系统调用接口DMA.addr=buf;DMA.count=10;……sleep_on(Disk);do_read_end()//中断处理
{wakeup(Disk);}查一下手册就可以找到该写什么命令?该向哪里写?让出CPU?需要写中断处理程序!总的感觉:
很简单处理流程是很简单,复杂的是一些细节问题,如滚屏PC终端和Linux终端管理!为什么选择终端设备?前面的内容已经可以让(多个)程序运行起来?现在我们很想看到屏幕显示,很想输入参数…完成这些任务的I/O设备通常称为终端设备(控制台)voidmain(void){if(!fork()){init();}从哪里开始这个故事呢?Shell要打出提示符,等待输入命令Shell启动前必定会初始化终端设备。就从这里开始……voidinit(void){open(“dev/tty0”,O_RDWR,0);dup(0);dup(0);}open展开成系统调用!在linux/lib/open.c中intopen(constchar*filename,intflag,…){__asm__(“int0x80”:”=a”(_res):””(__NR_open),”b”(filename),”c”(flag));return_res;}0x80的中断处理程序会找到__NR_open(编号,在ax中)对应的函数sys_open!在linux/fs/open.c中intsys_open(constchar*filename,intflag){i=open_namei(filename,flag,&inode);if(S_ISCHR(inode->i_mode))//字符设备
{if(MAJOR(inode->i_zone[0])==4)current->tty=MINOR(inode->i_zone[0]);}}解析目录,读入设备信息!和块设备(磁盘)对应。终端设备是典型的字符设备!我们来向屏幕输出点东西!voidinit(void){open(“/dev/tty0”...);
printf(“Thisisinitprocess”);}write中的printbuf显然是要输出的字符串,那么1呢?在linux/fs/read_write.c中intsys_write(unsignedintfd,char*buf,intcount){structfile*file;
file=current->filp[fd];inode=file->f_inode;...}staticintprintf(constchar*fmt,...){...
write(1,printbuf,sizeof(??));}write也要展开成系统调用!fd成为了找到file的索引!文件描述符表和打开文件表回头再看一下sys_open在linux/fs/open.c中intsys_open(constchar*filename,intflag){structfile*f;f=0+file_table;
cuurent->filp[fd]=f;//第一个空闲的fdf->f_mode=inode->i_mode;f->f_inode=inode;f->f_count=1;returnfd;}全局打开文件表在linux/fs/file_table.c中structfilefile_table[NR_FILE];PCBfilpfile_tableinodeinode开始真正向屏幕输出!继续sys_write!在linux/fs/read_write.c中intsys_write(unsignedintfd,char*buf,intcnt){inode=file->f_inode;if(S_ISCHR(inode->i_mode))returnrw_char(WRITE,inode->i_zone[0],buf,cnt);...转到rw_char!在linux/fs/char_dev.c中intrw_char(intrw,intdev,char*buf,intcnt){crw_ptrcall_addr=crw_table[MAJOR(dev)];call_addr(rw,dev,buf,cnt);...}一个函数数组,处理各种字符设备!看看crw_table!staticcrw_ptrcrw_table[]={...,rw_ttyx,};typedef(*crw_ptr)(intrw,unsignedminor,char*buf,intcount)第4个!再转到tty_write!//实现输出的核心函数staticintrw_ttyx(intrw,unsignedminor,char*buf,intcount){return((rw==READ)?tty_read(minor,buf):
tty_write(minor,buf));}在linux/kernel/tty_io.c中inttty_write(unsignedchannel,char*buf,intnr){structtty_struct*tty;tty=channel+tty_table;
sleep_if_full(&tty->write_q);...}可以猜测:输出就是放入队列!继续tty_write这一核心函数在linux/kernel/tty_io.c中inttty_write(unsignedchannel,char*buf,intnr){...charc,*b=buf;while(nr>0&&!FULL(tty->write_q)){c=get_fs_byte(b);if(c==‘\r’){PUTCH(13,tty->write_q);continue;}if(O_LCUC(tty))c=toupper(c);b++;nr--;PUTCH(c,tty->write_q);}//输出完事或写队列满!
tty->write(tty);if(nr>0)schedule();}fs:数据段选择符!tty->write应该是真的开始输出屏幕了!看看tty->write在include/linux/tty.h中structtty_struct{void(*write)(structtty_struct*tty);structtty_queueread_q,write_q;}先应该看看tty_struct这个结构!看看tty_struct结构的初始化!tty=channel+tty_table在linux/kernel/chr_drv/tty_io.c中structtty_structtty_table[]={{con_write,{0,0,0,0,””},{0,0,0,0,””}},{},…};structtty_queue{unsignedlongdata;unsignedlonghead;unsignedlongtail;structtask_struct*proc_list;charbuf[TTY_BUF_SIZE];};走了很久的路,终于到了con_write在linux/kernel/chr_drv/console.c中voidcon_write(structtty_struct*tty){intnr=CHARS(tty->write_q);charc;while(nr--){GETCH(tty->write_q,c);switch(state){//不同状态按不同方式处理(字符或命令)case0:if(c>31&&c<127){__asm__(“movb_attr,%%ah\n\t”“movw%%ax,%1\n\t”::”a”(c),”m”(*(short*)pos):”ax”);pos+=2;x++;}...con_write用来真正的向显示器写东西!32-126是可视字符!其它是控制字符。32是空格,126是~。将c写到pos处,pos指向显存。看看pospos指向显存,先来了解一下显存…PC/AT机内存区域图0x000000xA0000显存0xC0000……ROMBIOSVGAROMBIOS0x1000000xffffffff回忆:分页时内存640K-1M不和逻辑页映射!pos指向显存:pos=0xA0000别忘了,段选择符改为内核段,基址为0所以完成显示中最核心的秘密就是控制pos跟踪pos...pos的修改:pos+=2跟踪pos:pos初值+pos修改为什么加2?屏幕上的一个字符在显存中除了字符本身还应该有字符的属性(如颜色等)这就需要说一说适配器标准了…1个byte字符,1个byte属性!CGA彩色图形适配器0xb8000~0xbc000适配器标准内存地址字符属性字节BLD7RD6GD5BD4ID3RD2GD1BD0闪烁高亮度背景色前景色字符的显示属性EGA/VGA的显存起始地址:0xa0000现代计算机大都是EGA/VGAEGA/VGA兼容CGA和MDAStaticunsignedcharattr=0x07;__asm__(“movb_attr,%%ah\n\t”“movw%%ax,%1\n\t”::”a”(c),”m”(*(short*)pos):”ax”);黑底白字!跟踪pos之pos初值!pos初值的设定一定在终端控制台的初始化con_initmain(){tty_init();}tty_init(){con_init();rs_init()}con_init()在linux/kernel/chr_drv/console.c中voidcon_init(void){if(ORIG_VIDEO_MODE==7)//是MDAelse{//彩色显示卡CGA/EGA/VGAvideo_mem_start=0xb80000;//显存起始地址
video_mem_end=0xbc000;//显存末端地址
}origin=video_mem_start;
gotoxy(ORIG_X,ORIG_Y);staticunsignedlongvideo_mem_start;staticunsignedlongvideo_mem_end;staticunsignedlongorigin;Staticunsignedlongpos;一些控制显示的全局变量!#defineORIG_VIDEO_MODE((*(unsignedshort*)0x90006)&0xff)//显示模式一些物理参数取出来放在0x90000处!gotoxy(ORIG_X,ORIG_Y)在linux/kernel/chr_drv/console.c中staticinlinevoidgotoxy(unsignedintnew_x,unsignedintnew_y){if(new_x>video_num_columns||new_y>=video_num_lines)return;//不合法的x、yx=new_x;y=new_y;
pos=origin+y*video_size_row+(x<<1);}
#defineORIG_X(*(unsignedchar*)0x90000)//初始光标列号#defineORIG_Y(*(unsignedchar*)0x90001)//初始光标行号在con_init()中video_num_columns=ORIG_VIDEO_COLS;video_size_row=video_num_columns*2;video_num_lines=ORIG_VIDEO_LINES;设为250x90006的高字节屏幕有限而显示内容无限
翻页case0:if(c>31&&c<127){if(x>=video_num_columns){x-=video_num_columns;
pos-=video_size_row;
lf();//换行}__asm__(“movb...在movw(ax,pos)之前,还有一小段程序…x的作用!staticvoidlf(void){if(y+1<bottom){y++;pos+=video_size_row;return;}
scrup();//翻页
}staticunsignedlongx,y;staticunsignedlongtop,bottom;staticunsignedlongscr_end;x,y定位光标行列坐标!显然,翻页时的pos另外处理?用来控制翻页的…翻页scrup()根据显存想一想如何翻页?staticvoidscrup(void)if(video_type==VIDEO_TYPE_EGAC||video_type==VIDEO_TYPE_EGAM){if(!top&&bottom==video_num_lines){orgin+=video_size_row;pos+=video_size_row;scr_end+=video_size_row;if(scr_end>video_mem_end){(移动整屏数据)}else{(新行上填入空格字符)}set_origin();//”移动”显示器
}else{(非整屏向上翻)}}
显存?A?B?C?D?E?F?G?H......?W?X?Y?Z显示器向下翻页整屏向上翻!超过显存末端!在显存中移动数据、填充空格新行填充空格字符
__asm__(“cld\n\t”“rep\n\t”“movsl\n\t”movl_video_num_columns,%1\n\t”“rep\n\t”“stosw”::”a”(video_erase_char),“c”((video_num_lines-1)*video_num_columns>>1),”D”(video_mem_start),”S”(origin):”cx”,”di”,”si”);scr_end-=origin-video_mem_start;pos-=origin-video_mem_start;origin=video_mem_start;
移动整屏数据(向下翻动一行)初始化为0x0720
__asm__(“cld\n\t”“rep\n\t”“stosw”::”a”(video_erase_char),“c”(video_num_columns),”D”(scr_end-video_size_row):”cx”,”di”);留1行!set_origin()//真正的翻屏各参数初始化(con_init)staticinlinevoidset_origin(void){
cli();//下面的动作是原子的
outb_p(12,video_port_reg);//选择“索引”寄存器端口
outb_p(0xff&((origin-video_mem_start)>>9),video_port_val);outb_p(13,video_port_reg);
outb_p(0xff&((origin-video_mem_start)>>1),video_port_val);sti();}移动整屏数据(向下翻动一行)top=0;bottom=video_num_lines;src_end=video_mem_start+video_num_lines*video_size_row;video_port_reg=0x3d4;video_port_val=0x3d5;r12:卷屏起始地址高字节梳理printf的整个过程!
库函数(printf)read_write.cchar_dev.ctty设备写(tty_write)字符设备接口(crw_table[])系统调用(write)write_q队列显示器写(con_write)tty_io.c显存写pos位置终端控制台设备=显示器+键盘关于键盘的故事从哪里开始?对于使用者(人):敲键盘、看结果voidcon_init(void)//应为键盘也是console的一部分{set_trap_gate(0x21,&keyboard_interrupt);outb_p(inb_p(0x21)&0xfd,0x21);//操作中断控制器
a=inb_p(0x61);//操作键盘控制器
outb_p(a|0x80,0x61);outb_p(a,0x61);}对于操作系统:“等着”你敲键盘,敲了就中断故事应该从键盘中断开始,从中断初始化开始…了解一点键盘硬件…0x21是什么?两个0x21一样吗?set_trap_gate(0x21,&keyboard_interrupt);outb_p(inb_p(0x21)&0xfd,0x21);CPUINTR8259A主片IR0时钟IR1键盘INT8259A从片IR0IR1INTIR2端口0x20~端口0xA0~inb_p(0x21)//取中断屏蔽字&0xfd//11111101IR1置0outb_p(?,0x21)//允许键盘中断set_trap_gate(0x21)//中断号在setup.s中初始化moval,#0x20out#0x21,almoval,#0x28out#0xA1,al8259初始化后才使用主片中断号从0x20开始,从片从0x28开始了解一点键盘硬件…0x61是什么?a=inb_p(0x61);outb_p(a|0x80,0x61);outb_p(a,0x61);键盘控制器804X端口0x61端口可读可写:其中位7=1表示禁止键盘;位7=0表示允许键盘outb_p(a|0x80,0x61);//禁止键盘outb_p(a,0x61);//又允许键盘禁止了一下,又允许了一下。挺好玩!con_init:设置键盘中断处理;允许中断;复位键盘控制器,让键盘开始正常工作…键盘中断处理keyboard_interrupt在kernel/chr_drv/keyboard.S中.globl_keyboard_interrupt_keyboard_interrupt:pushl%eaxpushl%ebxpushl%ecxpushl%edxpush%dspush%esmov$0x10,%eaxmov%ax,%dsmov%ax,%es//内核段
xorl%al,%alinb$0x60,%al//从端口0x60读扫描吗
callkey_table(,%eax,4)//调用key_table+eax*4inb$0x61,%aljmp1f1:jmp1f//延迟一会儿
1:orb$0x80,%aljmp1f1:jmp1f1:outb%al,0x61jmp1f1:jmp1f1:andb$0x7f,%aloutb%al,$0x61movb$0x20,%aloutb%al,$0x20//向8259发送EOIpush$0//传递参数0call_do_tty_interruptaddl$4,%esppop%es......iret处理扫描码!处理扫描码key_table+eax*4在kernel/chr_drv/keyboard.S中key_table:.longnone,do_self,do_self,do_self//扫描码00-03.longdo_self,...,func,scroll,cursor等等key_table是一个函数数组扫描码02对应按键1;01对应ESC;12对应E等等显示字符通常都用此函数处理!mode:.byte0do_self:leaalt_map,%ebxtestb$0x20,mode//alt键是否同时按下jne1fleashift_map,%ebxtestb$0x03,modejne1fleakey_map,%ebx1:
找到映射表,如a的key_map映射为a,而shift_map映射为A从key_map中取出ASCII码各种map(alt_map,shift_map,key_map)放在哪里?#ifdefined(KBD_US)key_map:.byte0,27.ascii“1234567890-=“...shift_map:.byte0,27.ascii“!@#$%^&*()_+”...#elifdefined(KBD_GR)...继续do_self,从1f开始,ebx放的是map起始地址1:movb(%ebx,%eax),%al//扫描码索引,ASCII码
alorb%al,%aljenone//没有对应的ASCII码
testb$0x4c,mode//看caps是否亮
je2fcmpb$’a,%aljb2fcmpb$’},%alja2fsubb$32,%al//变大写2:testb$??,mode//处理其他模式,如ctrl同时按下3:andl$0xff,%eaxxorl%ebx,%ebxcallput_queuenone:retput_queue将ASCII码放到?put_queue//可见,取出的ASCII码放入队列就完事put_queue:pushl%ecxpushl%edxmovl_table_list,%edxmovlhead(%edx),%ecx1:movb%al,buf(%edx,%ecx)incl%ecxandl$size-1,%ecxcmpltail(%edx),%ecx//头指针==尾指针(缓冲区满)je3fshrdl$8,%ebx,%eaxje2fshrl$8,%ebxjmp1b2:movl%ecx,head(%edx)movlproc_list(%edx),%ecxtestl%ecx,%ecxje3f
movl$0,(%ecx)//task_struct的第一个域就是状态3:popl%edxpopl%ecxret在kernel/chr_drv/tty_io.c中structtty_queue*table_list[]={&tty_table[0].read_q,&tty_table[0].write_q;...};edx指向tty_table[0](console)head指向队列头部把ebx:eax中最多8个字节放到队列con.read_qdo_tty_interrupt要做什么?
到目前为止还差什么?在kernel/chr_drv/tty_io.c中voiddo_tty_interrupt(inttty)//上面传来的是0{copy_to_cooked(tty_table+tty);}对了…“回显”push$0在kernel/chr_drv/tty_io.c中voidcopy_to_cooked(structtty_struct*tty){signedcharc;while(!EMPTY(tty->read_q)&&!FULL(tty->secondry)){GETCH(tty->read_q,c);
if(I_UCLC(tty))c=tolower(c);//大写变小写
...copy_to_cooked//一定是拷贝到另一个队列中do_tty_interrupt要做什么?
一定还有许多if…在kernel/chr_drv/tty_io.c中voidcopy_to_cooked(structtty_struct*tty){if(I_UCLC(tty))c=tolower(c);//大写变小写
if(L_ISIG(tty)){
if(c==INTR_CHAR(tty)){//^Ctty_intr(tty,INTMASK);//向进程发送中断信号
continue;}}
if(L_ECHO(tty))
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- AI清风行动方案
- 《生态恢复生态工程》课件
- 高中美术鉴赏-青铜器
- 全外汇投资经验教你入门
- 拒绝异议处理-保险公司早会分享培训模板课件演示文档幻灯片资料
- 《大学物理上》课件
- 2026年生产安全事故报告调查考核题库及答案
- 2026年绿色供应链管理员企业环保技能题库及答案
- 建筑设计防火规范讲座:防火防爆
- 2026年餐饮后厨卫生专项检查考核押题卷及答案
- 2026年辽宁高级档案职称考试(档案管理概论)模拟试题及答案
- 2026年中级注册安全工程师安全生产法律法规模拟题库及答案
- 【新教材】统编版(2026)九年级上册道德与法治全册教案
- 2026年秋新教材统编版初中语文八年级第一学期教学计划及进度表
- 2026年湖南岳阳现代物流集团有限公司招聘11人笔试参考题库及答案详解
- GB 1207-2006电磁式电压互感器
- 洁净煤技术完整版ppt课件全册电子教案
- 加氢工艺安全知识培训内容课件
- 当代教育心理学(范围)课件
- 预应力锚索施工作业指导书
- 二次函数与韦达定理综合题
评论
0/150
提交评论