LDD3笔记:第三章 字符设备驱动

来源:互联网 发布:淘宝网男士长袖衬衣 编辑:程序博客网 时间:2024/05/16 05:45

平凡的我们不能预见虚无且略带迷茫的明天,唯有着眼当下做好我们手边的事,这才是王道。

  初来咋到不敢造次,追寻伟人,以期借肩膀一用,弱弱的陈述,积极的整理。举网络资源之海量,去迷茫众生之浮躁。新人笔记恳请批评指正。在驱动界,梵高说过:得LDD精髓者得天下。恩...那就开始吧...

Chapter03:Char Drivers

  我们的最终目的是编写一个模块化的字符驱动, 但是我们不会在本章讨论模块化的事情.

本章展示从一个真实设备驱动提取的代码片段: scull( Simple Character Utility for Loading Localities). scull 是一个字符驱动, 操作一块内存区域好像它是一个设备.

  编写驱动的第一步是定义驱动将要提供给用户程序的能力(机制).整本书包括内核源码一直都在强调 机制 (设备能干什么)与 策略 (如何使用这些功能)的分离,具体如何实现真的很难窥测到,这需要经验的积累。

一.主设备号和次设备号 

  对字符设备的访问是通过文件系统内的设备名(被称为特殊文件、设备文件或文件系统的节点)来进行的。Major num 标识与(一类)设备对应的驱动程序(一般是一对一,也有例外),而Minor num则被内核用来标识驱动程序所实现的设备,也即一个驱动可被多个设备共同享有,其实在内存中这一类设备公用一段程序代码,只是私有一份自己的数据而已,呐,这其实是通过虚拟内存管理实现的,普通的x86上有段管理和页管理,有资料说Linux只用页管理且是3级管理(也有说是四级),这也会涉及到TLB(Table Lookaside Buffer)她存放最近访问过的页表(虚拟地址到物理地址的转换表)。什么是页呢?在Linux内核当中,物理内存被描述成页,用结构体struct page()。

  麻烦的是要分清几个概念:程序使用的逻辑地址,进程使用的线性地址,处理器使用的物理地址,当然还有内核虚拟地址,内核逻辑地址。

逻辑地址 :你在写程序时要在程序体当中会用到内存地址吧,程序当中都是相对某起始点的。咋说呢,好像是你写汇编的时候,会定义一个_start吧?然后以她做偏移的不是么?

线性地址 :与特定的处理器有关系,x86是32位地址线可寻址4G空间,IA64是64位的可以寻址...反正老大一块了。注意哦每个进程都有这么大的一个独立的空间!那为什么能同时运行那般多的进程呢?打个比方:一个教室可以容纳10个人,有十个班级(每班2人)要上课,怎么办呢?这时你可以理解成要上的是选修课,而同时呢就只有那么几个人喜欢听这课,在大学不总流行:选修课必逃,必修课选逃么!这样就可以容纳十个班级了,甚至还没坐满呢!哎,不对啊,那有一天老师突然要点名咋整啊?这样怎么让老师知道十个班级20个人都来上课了呢,位置不够啊?!呵呵,结合经验你就该知道了:路人甲被点完名就溜出去了,这时呢路人乙只要在同学的通知下说今天要点名并能及时赶来,趁着路人甲出去,混进来了,并能及时的喊“到”不就完事了么,假象是他们都来听老师的课了!

Linux内核当中也是这样,用到的时候我在把你叫到内存当中,来替换那些已经被读过或写过的(标记为脏的)。

物理地址 :就是真实的32位或者64位地址了

内核逻辑地址 :内核将4G的地址空间分成了1G+3G两部分,自己使用1G也叫内核地址空间(低端内存(16M以上896M以下)),也叫内核逻辑地址,用户空间3G(也叫高端内存)。所以内核无法直接操作(安全需要)没有映射进内核地址空间的内存,其他3G空间是不可能被内核直接访问到的,故而内核要访问超出这1G的内存只能通过映射将超出这1G的地址的内存采用映射的机制把他们一点一点的映射在1G的范围内!通常逻辑地址与物理地址存在一个固定的偏移量。

内核虚拟地址: 内核虚拟地址类似于逻辑地址, 它们都是从内核空间地址到物理地址的映射. 内核虚拟地址不必有逻辑地址空间具备的线性的, 一对一到物理地址的映射, 但是. 所有的逻辑地址是内核虚拟地址, 但是许多内核虚拟地址不是逻辑地址. 例如, vmalloc 分配的内存有虚拟地址(但没有直接物理映射). kmap 函数(本章稍后描述)也返回虚拟地址. 虚拟地址常常存储为指针变量.

当需要访问一页数据时时,处理器呢先去TLB转悠转悠如果运气好能遇到知音呢就直接把她揽到怀里,有点霸道了,就直接把表中的页拿出来用了,可是呢如果没遇到咋办呢,就会产生一个缺页异常,从磁盘上把页读进来。其实整个系统中最慢的部件就属磁盘了,其寻道是靠机械手臂!还好现在有固态磁盘了,速度相当惊人!扯远了,还是回到主题说字符吧

设备编号的内部表达:

  在内核中,dev_t类型(在<linux/types.h>中定义)用来保存设备编号——包括主设备号和次设备号。devt_t是一个32位数,其中12位表示Major num,20位表示Minor num。我们不能对设备编号的组织方式做任何的假设!而必须用相应的宏去获取。<linux/kdev.h>中有关宏:

    MAJOR(dev_t);

    MINOR(dev_t);同时可以用MKDEV(int major,int minor);将主次设备号转换成dev_t类型。

分配和释放设备编号:

  ...

分配和释放设备号:

在建立一个字符设备之前,驱动需要首先获得一个或多个设备编号。

静态方式:

  int register_chrdev_region(dev_t first, unsigned int count, char *name);

         first:要分配的起始设备编号,经常为0

         count:请求连续设备编号的个数

         name:则被sysfs文件系统使用

其返回0,表示分配成功

动态方式:

         intalloc_chrdev_region(dev_t *dev, unsigned int firstminor,

unsigned int count, char *name);

         dev:用于保存已分配范围的第一个编号

         firstminor:请求的第一个次设备号,通常是0

         count:请求连续设备编号的个数

         name:则被sysfs文件系统使用

需要记住的是:内核中显式创建的不用的资源就必须显式的把它释放掉!

释放设备号:

         void unregister_chrdev_region(dev_tfrom, unsigned count);/*一般会在模块清除函数中调用*/

         驱动程序将设备号和内部函数相连以实现设备的操作。

动态分配主设备号:

         分配主设备号的最佳方式:默认采用动态方式,同时保留在加载甚至是编译时指定主设备号的余地。

1.      使用一个全局变量scull_major来保存所选择的设备号,scull_minor来放次设备号

2.      if (scull_major) {

dev = MKDEV(scull_major, scull_minor);

result = register_chrdev_region(dev,scull_nr_devs, "scull");

} else {

result = alloc_chrdev_region(&dev,scull_minor, scull_nr_devs,

"scull");

scull_major = MAJOR(dev);

}

if (result < 0) {

printk(KERN_WARNING "scull: can't get major %d\n",scull_major);

return result;

}

原创粉丝点击