📄 (ldd) ch08-硬件管理(转载).htm
字号:
color=#ffffff size=3>
<P>的是上面给出的循环,/dev/short0p使用outb_p和inb_p来替代前者使用的“较快的”函<BR>数,而/dev/short0s使用了串指令。共有四个这样的设备,从short0到short3,每个都<BR>读写一个端口。这四个端口是连续的。<BR> <BR> <BR> <BR> 在Alpha上编译时,由于不提供insb和outb,short0s设备就和short0一样了。<BR> <BR> <BR> <BR> 虽然short不能进行“真正的”硬件控制,但它可以作为一个对不同的指令计时<BR>的有趣的测试平台,并且可以作为一个学习如何进行“真正的”硬件控制的开始。任何<BR>对写驱动程序有兴趣的人肯定拥有更多更有趣的设备,但老的傻瓜式的并口还是能作些<BR>有用的工作的-我自己就是在早上打开收音机后用它来为我准备咖啡的。<BR> <BR>访问设备卡上的内存<BR> 上一章介绍了分配RAM内存的所有各种方法;现在我们要介绍计算机能提供的另<BR>一种内存:扩展卡上的内存。外设也有内存。显卡有帧缓冲区,视频捕捉卡用来存放捕<BR>捉到的数据,而网卡将接受到的数据包存放在内存区域中;此外,大多数设备卡上还有<BR>卡上ROM,存放着系统启动时处理器要执行的代码。所有这些都是“内存”,因为处理器<BR>通过访存指令来对它们进行读写。这里我只讨论ISA和PCI设备,因为现在它们最常用。<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> 在标准的x86机器上共有三种通用的设备内存:640KB到1MB地址范围内的ISA内存<BR>,14MB到16MB地址范围内的ISA内存,和在物理地址空间之上的PCI内存。上面这些用到<BR>的地址都是物理地址,是在计算机的地址总线上的跑的数值,与程序代码中所使用的虚<BR>拟地址没有任何关系(参见第7章“获取内存”的“vmalloc和相关函数”一节)。I/O内存<BR>使用这些物理位置主要是出于历史的原因,下面介绍这三种内存区域时会作出解释。<BR> <BR> <BR> <BR> 不幸的是(或者,幸运的是,如果你更倾向于好的体系结构设计而不是容易移植<BR>的话)。不是所有的Linux平台都支持ISA和PCI;本节只限于讨论支持ISA和PCI的Linux平<BR>台。<BR> <BR>1M地址空间之下的ISA内存<BR> 在第2章“编写和运行模块”的“ISA内存”一节中我已经介绍过一种“容易的”<BR>(但有些毛病的)访问这种内存的方法,在那里我使用了存放物理地址的指针来正确地指<BR>向所申请的I/O内存。尽管这种技术在x86平台可行,但却不能移植到其它的Linux平台上<BR>。使用指针的方法对小的生命期较短的项目是较好的选择,但对作为产品的驱动程序并<BR>不推荐。<BR> <BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> 推荐的I/O内存接口是在linux1.3版的开发树中被引入内核的,更早的版本并不<BR>提供。与short范例模块一起发布的sysdep.h头文件为1.2版以来的各个内核版本实现了<BR>这种新的语义。<BR> <BR> <BR> <BR> 新的接口包括一系列宏和函数调用,取代了使用指针来访问I/O内存的方法。这<BR>些宏和函数是可移植的;这意味着相同的源码可以在不同的体系结构上编译和运行,只<BR>要它们拥有类型相同的设备总线。<BR> <BR> <BR> <BR> 当你在基于Intel处理器的平台上编译代码时,这些宏现在大部分会扩展对指针<BR>的操作,但它们的内部实现会在将来被适当地修改。例如,在Linux最初从2.0版转到2.1<BR>版时,这样的修改就发生过,Linus决定改变虚存的布局。在新的布局下,就不能再以第<BR>2章中介绍的旧的方式访问ISA内存了。<BR> <BR> <BR> <BR> 新的I/O内存接口由下面这些函数组成:<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR>unsigned readb(address);<BR> <BR>unsigned readw(address);<BR> <BR>unsigned readl(address);<BR> <BR>这些宏用于从I/O内存中取得8位,16位和32位的数据值。1.2版的Linux不提供。使用宏<BR>的优点是对参数的类型不作要求;参数address在使用前会被强制类型转换,因为这个值<BR>“不清楚是整数还是指针,但我们两者都能接受”(见 asm-alpha/io.h)。读和写函数都<BR>不会检查address参数的合法性,因为使用它就是为了能和使用指针一样快(我们已经知<BR>道有时它们实际上就是被扩展成指针操作)。<BR> <BR> <BR> <BR>unsigned writeb(unsigned value, address);<BR> <BR>unsigned writew(unsigned value, address);<BR> <BR>unsigned writel(unsigned value, address);<BR> <BR> 和前面的函数类似的,这些函数(宏)用于写8位,16位和32位的数据项。<BR></P></FONT><FONT
color=#ffffff size=3>
<P> 和前面的函数类似的,这些函数(宏)用于写8位,16位和32位的数据项。<BR> <BR> <BR> <BR>memset_io(address,value,count);<BR> <BR>当你要对I/O调用memset进行操作时,这个函数可以满足你的需要,并且也保留了memset<BR>原来的语义。<BR> <BR> <BR> <BR>memcpy_fromio(dest, source, nbytes);<BR> <BR>memcpy_toio(dest, source, nbytes);<BR> <BR>这些函数用于成块传输I/O内存的数据,和memcpy_tofs的功能有些相似。它们是和上面<BR>这些函数一起引入Linux中的,1.2版的Linux不提供。与示例代码一起发布的sysdep.h头<BR>文件修正了函数的版本相关性问题,为1.2版以后的所有内核提供了这些函数的定义。<BR> <BR> <BR> <BR> 和I/O端口函数一样的,这些函数在能支持的体系结构间的移植性现在也很有限<BR>。一些平台根本不提供这些函数;一些平台上它们是被扩展为指针操作的宏,而在另一<BR></P></FONT><FONT
color=#ffffff size=3>
<P>。一些平台根本不提供这些函数;一些平台上它们是被扩展为指针操作的宏,而在另一<BR>些平台上它们则是真正的函数。<BR> <BR> <BR> <BR> 象我这种习惯于旧PC机的平的(flat)内存模式的人,可能会不愿意为了只是读写<BR>“物理地址区域”而麻烦地使用新的一套接口。实际上,要习惯使用某套接口只需要练<BR>习练习使用这些函数。当然,没有比看看一个访问I/O内存的傻瓜式(silly)模块更好的<BR>获得自信的方法了。我下面要给你展示的模块就叫作silly,是“Simple Tool for<BR>Unloading and Printing ISA Data,卸载和打印ISA数据的简单工具”的简称。<BR> <BR> <BR> <BR>该模块包括四个用了不同的数据传输函数来完成相同任务的设备节点。silly设备是作为<BR>I/O内存之上的一个窗口,与/dev/mem有些类似。对该设备,你可以读写数据,lseek到<BR>一个任意的I/O内存地址,也可以将I/O内存区域mmap到你的进程中来(参见第13章“Mmap<BR>和DMA”的“mmap设备操作”一节)。<BR> <BR> <BR> <BR>/dev/sillyb,次设备号为0,调用readb和writeb函数来读写内存空间。下面的代码给出<BR>了read的实现,它将0xA0000到0xFFFFF的地址范围重映射到设备文件的偏移量从0到0x5F<BR>FFF的位置。read函数将不同的访问模式组织进一个switch语句中;下面是sillyb设备的<BR></P></FONT><FONT
color=#ffffff size=3>
<P>FFF的位置。read函数将不同的访问模式组织进一个switch语句中;下面是sillyb设备的<BR>case分支:<BR> <BR> <BR> <BR>case M_8:<BR> <BR> while (count){<BR> <BR> *ptr=readb(add);<BR> <BR> add++;count--;ptr++;<BR> <BR> }<BR> <BR> break;<BR> <BR> <BR> <BR>接下来的两种设备是/dev/sillyw(次设备号为1)和/dev/sillyl(次设备号为2)。它们和/<BR>dev/sillyb设备差不多,只是分别使用了16位和32位的函数。下面是sillyl设备的write<BR>函数的实现,也是switch语句的一部分。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> <BR>case M_32:<BR> <BR> while (count>=4 ){<BR> <BR> writel(*(u32*)ptr,add);<BR> <BR> add+=4;count-=4;ptr+=4;<BR> <BR> }<BR> <BR> break;<BR> <BR> <BR> <BR> 最后一种设备是/dev/sillycp(次设备号为3),该设备用memcpy_*io函数来执行<BR>相同的任务。下面是它的read函数实现的核心:<BR> <BR> <BR> <BR> case M_memcpy:<BR></P></FONT><FONT
color=#ffffff size=3>
<P> case M_memcpy:<BR> <BR> memcpy_fromio(ptr, add, count);<BR> <BR> break;<BR> <BR>1M地址空间之上的ISA内存<BR> 有些ISA设备卡带有的卡上内存会映射到物理地址空间的14MB和16MB范围内。这<BR>些设备正在逐渐消失,但仍值得介绍一下如何访问它们的内存区域。但本讨论仅适用于x<BR>86体系结构;我没有这种ISA卡在Alpha或其它体系结构上相应行为方面的资料。<BR> <BR> <BR> <BR> 在使用80286处理器的旧时代,物理地址空间的宽度是20个位(16MB),所有的地<BR>址线都在ISA总线上,几乎没有计算机带的RAM会超过1兆或2兆。那为什么扩展卡不能“<BR>偷”点高端的内存地址用做它的缓冲区呢?这种想法并不新鲜;相同的概念已经出现在<BR>对1M地址空间之下的ISA内存的使用上了,后面又会再次用来实现对高端PCI内存的使用<BR>上。选作ISA设备卡内存的地址范围是最顶端的2M,尽管大多数卡只使用了最顶端的1M。<BR> <BR> <BR> <BR> <BR> 只到今天,在一些主板仍能使用旧式的设备卡,即使物理内存超过了14M。要正<BR></P></FONT><FONT
color=#ffffff size=3>
<P> 只到今天,在一些主板仍能使用旧式的设备卡,即使物理内存超过了14M。要正<BR>确地处理这段内存区域要求你对这段地址范围作些处理,避免将内存地址和总线地址重<BR>叠起来。<BR> <BR> <BR> <BR> 如果你有一块带有高端内存的ISA设备,又不幸地RAM小于16MB,那管理内存就很<BR>容易。你的软件在处理时就好象拥有着高端PCI缓冲区(参见下一节),只是更慢些,因为<BR>ISA内存比较慢。<BR> <BR> <BR> <BR> 而如果你拥有的ISA设备带了高端内存而你的RAM要多于16MB,那么就有麻烦了。<BR> <BR> <BR> <BR> <BR> 一种可能就是你的主板不能正确地支持“ISA空洞”。在这种情况下,除非你拔<BR>掉一些内存,否则无法访问卡上内存。另一种可能是,主板能处理ISA空洞,你仍要告诉<BR>Linux内核这种内存的存在,作一些处理以便能访问RAM的其它部分(超过16M的地址范围)<BR>。<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> 你需要作些“黑客”的工作来正确保留高端的ISA内存,同时又仍能对其余的RAM<BR>进行正常访问,要修改的部分是对计算机的物理内存进行映射的部分。这个映射部分是<BR>在源文件arch/i386/mm/init.c的函数mem_init中实现的。数组mem_map中存放着与内存<BR>页有关的信息;如果某页的PG_reserved位被设置了,内核就不会对该页进行正常的页面<BR>处理(也即,该页被“保留”了,不要碰它)。但驱动程序仍可以使用保留页;640KB到1M<BR>B间的地址范围被标记为“保留的”,但仍可以被用作为设备内存。<BR> <BR> <BR> <BR> 下面的代码,插在mem_init函数中,正确地保留了15MB和16MB间的地址空间:<BR> <BR> <BR> <BR>while(start_mem<high_memory){<BR> <BR> if (start_mem>=0xf00000 && start_mem<0x1000000){<BR> <BR> /* keep it reserved, and prevent couting as data */<BR> <BR> reservedpages++; datapages--;<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> }<BR> <BR> else<BR> <BR> clear_bit(PG_reserved,<BR>&mem_map[MAP_NR(start_mem)].falgs);<BR> <BR> start_mem += PAGE_SIZE;<BR> <BR> }<BR> <BR> <BR> <BR> 最初所有的内存区域都被标记为“保留的”,上面给出的代码行保证了不会将对<BR>高端的I/O内存去除“保留”标记;原来的代码只有上面给出的循环的else分支。因为在<BR>内核代码之后的每个保留页都算作内核数据,要修改两个计数器reservedpages和datapa<BR>ges以避免启动时给出不匹配的信息。我的机器,有32MB内存,用前面的代码来访问ISA<BR>空洞,启动是的报告如下:<BR> <BR> <BR> <BR> Memory: 30120/32768k available (512k kernel code, 1408k reserved,<BR></P></FONT><FONT color=#ffffff size=3>
<P> Memory: 30120/32768k available (512k kernel code, 1408k reserved,<BR>728k data)<BR> <BR> <BR> <BR> 我是在自己的安装了2.0.29版内核的Intel机器(主板支持ISA空洞)上测试这段代<BR>码的。如果你运行的内核版本并不相同,你可能需要修改一下代码-与内存管理有关的<BR>内部数据结构在2.1版的内核中有一点改动,而在1.2版的内核中则很不一样。要支持旧<BR>式的(有时是不良设计的)硬件设备不可避免地必须“黑客”内核代码。<BR> <BR>高端PCI内存<BR> 访问高端PCI内存比访问高端ISA内存要更容易。PCI卡上的高端内存真的很高-<BR>高于任何合理的物理RAM地址(至少对未来若干年而言)。<BR> <BR> <BR> <BR> 正如第7章的“vmalloc和相关函数”一节讨论到的,访问该内存只要调用vremap<BR>(2.1版的内核是ioremap)。但是如果你希望代码能在不同平台上移植的话,你应该只通<BR>过readb和其它类似函数来访问重映射的内存区域。由于不是所有平台都能将PCI缓冲区<BR>直接映射到处理器的地址空间,所以必须加上这个限制。<BR> <BR>访问字符模式的视频缓冲区<BR> silly模块解释了如何访问内存640KB到1MB地址范围内的视频缓冲区,而下面要<BR></P></FONT><FONT
color=#ffffff size=3>
<P> silly模块解释了如何访问内存640KB到1MB地址范围内的视频缓冲区,而下面要<BR>介绍的更“直观的”演示程序则能帮助你熟悉readb和writeb函数。silly模块拥有两个<BR>额外的设备节点:/dev/sillytxt(次设备号为4)和/dev/sillitest(次设备号为5)。<BR> <BR> <BR> <BR>——————————————————————————————————————<BR>—<BR> <BR>警告 这样的设备只能在运行在字符模式下的VGA兼容的显示卡上使用;在没有VGA显<BR>示卡的系统上使用这样的设备,和任何对硬件资源的不受控制的读写一样,具有潜在的<BR>破坏性。<BR> <BR>——————————————————————————————————————<BR>—<BR> <BR> <BR> <BR> 第一个设备,sillytxt,只是VGA字符缓冲区上的一个窗口。与其它的silly节点<BR>不同的是,它可以作为输出重定向的目标和用于覆盖控制台上的显示。这让我们想起/de<BR>v/vcs,但silly的实现是不可移植的而且也没有象vcs那样集成进内核。<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> 最后一个设备只是和你开个玩笑:它将字母从字符屏幕上移走。每向该设备写进<BR>一个字符都会导致屏幕上的一个字符落到屏幕的底部。提供这个设备只是为了演示在I/O<BR>内存如何进行更复杂的操作-可以用同样的代码来操作VGA缓冲区或其它内存,比如网络<BR>数据包或者帧捕捉卡上的视频数据。<BR> <BR> <BR> <BR> 要注意对字符屏幕的任何修改都是易丧失的,并且会干扰内核自己的字符处理。<BR>如果你真的需要在应用程序中访问字符缓冲区,那么有更好的完成这个任务的方法:通<BR>过ncurses库或者通过/dev/vcs设备。vcs设备是“Virtual Console Screen,虚拟控制<BR>台屏幕”,可以用它来获得每个虚拟控制台的字符缓冲区当前的快照或者进行修改。vcs<BR>设备的文档就在它自己的源码中:内核源码树中的drivers/char/vc_screen.c文件。或<BR>者,你可以在最新的man-pages帮助页发布中找到关于该设备的描述。<BR> <BR>快速参考<BR> 本章引入下面一些与操纵硬件有关的符号:<BR> <BR> <BR> <BR>#include <asm/io.h><BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>unsigned inb(unsigned port);<BR> <BR>void outb(unsigned char byte, unsigned port);<BR> <BR>unsigned inw(unsigned port);<BR> <BR>void outw(unsigned short word, unsigned port);<BR> <BR>unsigned inl(unsigned port);<BR> <BR>void outl(unsigned doubleword, unsigned port);<BR> <BR>这些函数用于读写I/O端口。如果能正确获取访问端口的权限,它们也可以被用户空间的<BR>程序调用。不是所有平台都支持所有这些函数,它们与底层的硬件设计有关。<BR> <BR> <BR> <BR>SLOW_DOWN_IO;<BR> <BR>unsigned inb_p(unsigned port);<BR> <BR>…<BR></P></FONT><FONT
color=#ffffff size=3>
<P>…<BR> <BR>有时需要用SLOW_DOWN_IO语句来处理x86平台上低速的ISA卡。如果在每个I/O操作之后需<BR>要一小段延迟,你可以用与上面引入的6个函数相应的暂停式版本,它们得在相应的函数<BR>名后要加个_p。<BR> <BR> <BR> <BR>void insb(unsigned port, void *addr, unsigned long count);<BR> <BR>void outsb(unsigned port, void *addr, unsigned long count);<BR> <BR>void insw(unsigned port, void *addr, unsigned long count);<BR> <BR>void outsw(unsigned port, void *addr, unsigned long count);<BR> <BR>void insl(unsigned port, void *addr, unsigned long count);<BR> <BR>void outsl(unsigned port, void *addr, unsigned long count);<BR> <BR>“串操作”用于优化从输入端口到内存区域(或者反过来)的数据传输。这种传输是通过<BR>对同一个端口读写count次完成的。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> <BR>unsigned readb(address);<BR> <BR>unsigned readw(address);<BR> <BR>void writeb(unsigned value, address);<BR> <BR>void writew(unsigned value, address);<BR> <BR>void writel(unsigned value, address);<BR> <BR>memset_io(address, value, count);<BR> <BR>memcpy_fromio(dest, source, nbytes);<BR> <BR>memcpy(dest, source, nbytes);<BR> <BR>所有这些函数都用于访问I/O内存区域-低端的ISA内存或者高端的PCI缓冲区(在调用vre<BR>map后)。<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P>所有这些函数都用于访问I/O内存区域-低端的ISA内存或者高端的PCI缓冲区(在调用vre<BR>map后)。<BR> <BR> <BR> <BR> <BR> <BR>-----------------------------------------------------------------------------<BR> <BR>* 实际上,有时I/O端口是和内存一样对待的,(例如)你可以将2个8位的操作合并成一个<BR>16位的操作。例如,PC的显示卡就可以,但一般来说不能认为一定具有这种特性。<BR>--<BR><FONT
color=#00ff00>※ 来源:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 202.38.196.234]</FONT><BR>--<BR><FONT
color=#00ffff>※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR><FONT
color=#0000ff>※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR><FONT
color=#ffff00>※ 转载:.南京大学小百合站 bbs.nju.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR><FONT
color=#ff0000>※ 转载:·饮水思源 bbs.sjtu.edu.cn·[FROM: 211.80.41.106]</FONT><BR></P></FONT>
<P align=center><A href="http://joyfire.net/lsdp/index.htm"><FONT
color=#ffffff size=2>目录页</FONT></A> | <A
href="http://joyfire.net/lsdp/9.htm"><FONT color=#ffffff
size=2>上一页</FONT></A> | <A href="http://joyfire.net/lsdp/11.htm"><FONT
color=#ffffff size=2>下一页</FONT></A></P></SPAN></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>
<TBODY>
<TR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -