Linux设备驱动开发之0416

3.2 Linux 2.6 内核的特点


4、虚拟内存的变化
从虚拟内存的角度来看,新内核融合了r-map(反向映射)技术,显著改善虚拟
内存在一定程度负载下的性能。


新的Linux音频体系结构ALSA(Advanced Linux Sound Architecture)


支持ACPI(高级电源配置管理界面,Advanced Configuration and Power Interface)
CPU不同的负载下工作不同的时钟频率。


Linux内核的组成部分
保持一颗谦虚的心
Linux内核主要由进程调度(SCHED)、内存管理(MM)、虚拟文件系统(VFS)、
网络接口(NET)和进程间通信(IPC)等5个子系统组成。


启动内核线程的函数为:
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags);


,一般而言,Linux 的每个进程享有4GB的内存空间,0~3GB属于
用户空间,3~4GB属于内核空间,内核空间对常规内存、I/O 设备内存以及高端内存
存在不同的处理方式。


,X86 处理器包含4 个不同的特权级,称为Ring0~Ring3。Ring0 下,可以
执行特权级指令,对任何I/O 设备都有访问权等,而Ring3 则有很多操作限制。


Linux 系统充分利用CPU的这一硬件特性,但它只使用了两级。在Linux 系统中,
内核可进行任何操作,而应用程序则被禁止对硬件的直接访问和对内存的未授权访
问。例如,若使用X86处理器,则用户代码运行在特权级3,而系统内核代码则运行
在特权级0。


前期千万不要问
3.4 Linux内核的编译及加载


3.4.1 Linux内核的编译
使用make config、make menuconfig等命令后,会生成一个.config配置文件(隐
含在顶层Makefile中),记录哪些部分被编译入内核、哪些部分被编译为内核模块。




3.4.2 Kconfig和Makefile


在Linux 内核中增加程序需要完成以下3项工作。
将编写的源代码复制到Linux 内核源代码的相应目录。
在目录的Kconfig文件中增加新源代码对应项目的编译配置选项。
在目录的 Makefile文件中增加对新源代码的编译条目。


kbuild Makefile的语法包括如下几个方面。
(1)目标定义
目标定义用来定义哪些内容要作为模块编译,哪些要编译并连接进内核。
obj-y += foo.o
表示要由foo.c或foo.s文件编译得到foo.o并连接进内核,而obj-m则表示该
文件要作为模块编译。除了y、m以外的obj-x形式的目标都不会被编译。


obj-$(CONFIG_ISDN) += isdn.o
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
上面是根据.config文件的CONFIG_变量来决定文件的编译方式。


(2)多文件模块的定义


obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o bitmap.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o


模块的名字为ext2,由balloc.o和bitmap.o两个目标文件最终连接生成ext2.o
直至ext2.ko文件,是否包含xattr.o取决于内核配置文件的配置情况。


(3)目录层次的迭代。
obj-$(CONFIG_EXT2_FS) += ext2/


3.Kconfig
(1)菜单入口。
大多数的内核配置选项都对应Kconfig中的一个菜单入口


配置选项的属性包括类型、数据范围、输入提示、依赖关系(及反向依赖关系)、帮助信
息和默认值等。


prompt <prompt> [if <expr>]
if用来表示该提示的依赖关系。


depends on BAR
bool "foo"
default y
depends能限定一个symbol的上限。


<expr> ::= <symbol>
<symbol> '=' <symbol>
<symbol> '!=' <symbol>
'(' <expr> ')'
'!' <expr>
<expr> '&&' <expr>
<expr> '||' <expr>


menuconfig关键字的作用与config类似,但它在config的基础上要求所有的子选
项作为独立的行显示。


(2)菜单结构。
第一种方式如下所示:
menu "Network device support"
depends on NET
config NETDEVICES

endmenu
所有处于“menu”和“endmenu”之间的菜单入口都会成为“Network device support”
的子菜单。


config MODULES
bool "Enable loadable module support"
config MODVERSIONS
bool "Set version information on all module symbols"
depends on MODULES
comment "module support disabled"
depends on !MODULES
MODVERSIONS直接依赖MODULES,如果MODULES不为“N”,该选项才可
见。


test driver 的树型目录:
|--test
    |-- cpu
        | -- cpu.c
    |-- test.c
    |-- test_client.c
    |-- test_ioctl.c
    |-- test_proc.c
    |-- test_queue.c
在新增的test目录下,应该包含如下Kconfig文件:


#
# TEST driver configuration
#
menu "TEST Driver "
comment " TEST Driver"
config CONFIG_TEST
bool "TEST support " //显示"TEST support " ,等待用户选择
config CONFIG_TEST_USER
tristate "TEST user-space interface"
depends on CONFIG_TEST
endmenu
由于TEST driver 对于内核来说是新的功能,所以首先需要创建一个菜单TEST
Driver
由于用户接口功能可以被编译成内核模块,所以这里的询问语句使用了tristate。


source "drivers/test/Kconfig"
脚本中的source意味着引用新的Kconfig文件。


3.4.3 Linux内核的引导


不过在X86 PC 上有一个从BIOS(基本输入/输出系统)转移到Bootloader 的过
程,如图3.10 所示,而嵌入式系统往往复位后就直接运行Bootloader。


(1)当系统上电或复位时,CPU会将PC指针赋值为一个特定的地址0xFFFF0,并
执行该地址处的指令。在PC 中,该地址位于BIOS 中,它保存在主板上的ROM 或
Flash中。


U-Boot 的定位为“Universal Bootloader”,其功能比较强大,涵盖了包括PowerPC、
ARM、MIPS 和X86 在内的绝大部分处理器构架,提供网卡、串口、Flash 等外设驱
动,提供必要的网络协议(BOOTP、DHCP、TFTP),能识别多种文件系统(cramfs、
fat、jffs2 和registerfs 等),并附带了调试、脚本、引导等工具,应用十分广泛。




3.5 Linux下的C编程
代码风格:
#define PI 3.1415926
int min_value, max_value;
void send_data(void);


Linux系统上可用的C编译器是GNU C编译器,它建立在自由软件基金会的编程
许可证的基础上,因此可以自由发布。


struct var_data
{
    int len;
    char data[0];
};


它并没有为data[]数组分配内存,因此sizeof(struct
var_data)=sizeof(int)。


switch (ch)
{
    case '0'... '9': c -= '0';
    break;
    case 'a'... 'f': c -= 'a' - 10;
    break;
    case 'A'... 'F': c -= 'A' - 10;
    break;
}
代码中的case '0'... '9'等价于标准C中的如下代码:
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':


3.语句表达式
4.typeof 关键字
5.可变参数的宏


标准 C只支持可变参数的函数,意味着函数的参数是不固定的,例如printf()函数
的原型为:
int printf( const char *format [, argument]... );
而在GNU C中,宏也可以接受可变数目的参数,例如:
#define pr_debug(fmt,arg...) \
printk(fmt,##arg)
这里arg 表示其余的参数可以是零个或多个,这些参数以及参数之间的逗号构成
arg 的值,在宏扩展时替换arg,例如下列代码:
pr_debug("%s:%d",filename,line)
会被扩展为:
printk("%s:%d", filename, line)


6.标号元素


7.当前函数名


8.特殊属性声明


noreturn 属性作用于函数,表示该函数从不返回。


format属性也用于函数,表示该函数使用printf、scanf 或strftime 风格的参数,
指定format属性可以让编译器根据格式串检查参数类型。


unused属性作用于函数和变量,表示该函数或变量可能不会被用到,这个属性可
以避免编译器产生警告信息。


aligned属性用于变量、结构体或联合体,指定变量、结构体或联合体的对界方式,
以字节为单位。


struct example_struct
{
char a;
int b;
long c;
} _ _attribute_ _((aligned(4)));
表示该结构类型的变量以4 字节对界。


packed属性作用于变量和类型,用于变量或结构体成员时表示使用最小可能的对
界,用于枚举、结构体或联合体类型时表示该类型使用最小的内存。


struct example_struct
{
    char a;
    int b;
    long c _ _attribute_ _((packed));
};


9.内建函数
不属于库函数的其他内建函数的命名通常以_ _builtin 开始,


3.5.3 do { } while(0)
其实do{}while(0)主要用于宏定义中。


#define SAFE_FREE(p) do{ free(p); p = NULL;} while(0)
#define SAFE_FREE(p) free(p); p = NULL;


if(NULL != p)
SAFE_DELETE(p)
else
...//do something


if(NULL != p)
free(p); p = NULL;
else
...//do something
展开的代码中存在两个问题:
(1)if分支后有两个语句,导致else分支没有对应的if,编译失败;
(2)假设没有else分支,则SAFE_FREE 中的第二个语句无论if测试是否通过都
会执行。


不会再出现编译问题。do{}while(0)的使用完全是为了保证宏定义的使用者能无编
译错误地使用宏,它不对其使用者做任何假设。


3.5.4 goto
用不用goto一直是一个著名的争议话题,Linux 内核源代码中对goto的应用非常
广泛,但是一般只限于错误处理中,其结构如下:




第 4 章Linux内核模块


一种方法是把所有需要的功能都编译到Linux 内核。这会导致两个问题:
一是生成的内核会很大,
二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。


Linux 提供了这样的一种机制,这种机制被称为模块(Module),可以实现以上效
果。模块具有以下特点。


模块本身不被编译入内核映像,从而控制了内核的大小。
模块一旦被加载,它就和内核中的其他部分完全一样。




1 #include <linux/init.h>
2 #include <linux/module.h>
3 MODULE_LICENSE("Dual BSD/GPL");
4 static int hello_init(void)
5 {
6 printk(KERN_ALERT " Hello World enter\n");
7 return 0;
8 }
9 static void hello_exit(void)
10 {
11 printk(KERN_ALERT " Hello World exit\n ");
12 }
13 module_init(hello_init);  //模块的加载
14 module_exit(hello_exit);  //模块的卸载
15
16 MODULE_AUTHOR("Song Baohua");
17 MODULE_DESCRIPTION("A simple Hello World Module");
18 MODULE_ALIAS("a simplest module");




编译它会产生hello.ko 目标文件,通过“insmod ./hello.ko”命令可以加载它,通过“rmmod hello”命令可以卸载它,加载时
输出“Hello World enter”,卸载时输出“Hello World exit”


在 Linux 系统中,使用lsmod命令可以获得系统中加载了的所有模块以及模块间
的依赖关系,


modprobe命令比insmod命令要强大,它在加载某模块时会同时加载该模块所依赖的
其他模块。使用modprobe命令加载的模块若以“modprobe -r filename”的方式卸载将同
时卸载其依赖的模块。


使用 modinfo <模块名>命令可以获得模块的信息,包括模块的作者、模块的说明、
模块所支持的参数以及vermagic,


4.2 Linux内核模块的程序结构


一个Linux 内核模块主要由以下几个部分组成。
模块加载函数(必须)。
当通过 insmod或modprobe命令加载内核模块时。
模块卸载函数(必须)。
通过 rmmod命令卸载某模块。
模块许可证声明(必须)。
模块许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE,
模块被加载时,将收到内核被污染(kernel tainted)的警告。
模块参数(可选)。
模块导出符号(可选)。
模块作者等信息声明(可选)。


4.3 模块加载函数


Linux内核模块加载函数一般以_ _init标识声明。
它返回整型值,若初始
化成功,应返回0。而在初始化失败时,应该返回错误编码。在Linux内核里,错误编码
是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM 之类的符号值。


在 Linux 内核中,所有标识为_ _init的函数在连接的时候都放在.init.text这个区段
内,此外,所有的_ _init函数在区段.initcall.init中还保存了一份函数指针,在初始化
时内核会通过这些函数指针调用这些_ _init函数,并在初始化完成后释放init区段(包
括.init.text,.initcall.init等)。


4.4 模块卸载函数


Linux内核模块卸载函数一般以_ _exit标识声明。


4.5 模块参数


static char *book_name = "深入浅出Linux设备驱动";
static int num = 4000;
module_param(num, int, S_IRUGO);
module_param(book_name, charp, S_IRUGO);


1 #include <linux/init.h>
2 #include <linux/module.h>
3 MODULE_LICENSE("Dual BSD/GPL");
4
5 static char *book_name = "dissecting Linux Device Driver";
6 static int num = 4000;
7
8 static int book_init(void)
9 {
10 printk(KERN_INFO " book name:%s\n",book_name);
11 printk(KERN_INFO " book num:%d\n",num);
12 return 0;
13 }
14 static void book_exit(void)
15 {
16 printk(KERN_ALERT " Book module exit\n ");
17 }
18 module_init(book_init);
19 module_exit(book_exit);
20 module_param(num, int, S_IRUGO);
21 module_param(book_name, charp, S_IRUGO);
22
23 MODULE_AUTHOR("Song Baohua, [email protected]");
24 MODULE_DESCRIPTION("A simple Module for testing module params");
25 MODULE_VERSION("V1.0");




4.6 导出符号


Linux 2.6的“/proc/kallsyms”文件对应着内核符号表,它记录了符号以及符号所在
的内存地址。


模块可以使用如下宏导出符号到内核符号表:
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名);


1 #include <linux/init.h>
2 #include <linux/module.h>
3 MODULE_LICENSE("Dual BSD/GPL");
4
5 int add_integar(int a,int b)
6 {
7 return a+b;
8 }
9
10 int sub_integar(int a,int b)
11 {
12 return a-b;
13 }
14
15 EXPORT_SYMBOL(add_integar);
16 EXPORT_SYMBOL(sub_integar);




4.7 模块声明与描述


MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);


4.8 模块的使用计数
Linux 2.6 内核提供了模块计数管理接口try_module_get(&module)和module_put
(&module),从而取代Linux 2.4 内核中的模块使用计数管理宏。


4.9 模块的编译


obj-m := hello.o
make -C /usr/src/linux-2.6.15.5/ M=/driver_study/ modules
make –C /usr/src/linux-2.6.15.5 M=$(pwd) modules
其中-C后指定的是Linux内核源代码的目录,而M=后指定的是hello.c和Makefile


编译过程中经历了这样的步骤:先进入Linux 内核所在的目录,
并编译出hello.o 文件,运行MODPOST 会生成临时的hello.mod.c 文件,而后根据此
文件编译出 hello.mod.o,之后连接hello.o 和hello.mod.o 文件得到模块目标文件
hello.ko,最后离开Linux内核所在的目录。


hello.mod.o产生了ELF(Linux 所采用的可执行/可连接的文件格式)的两个节,
即modinfo和.gun.linkonce.this_module。


4.10 模块与GPL


但是一般而言,产品在启动过程中应该加载模块,在嵌入式Linux 的启动过
程中,加载企业自己的模块的最简单的方法是修改启动过程的rc脚本,增加
insmod /.../xxx.ko 这样的命令。如某设备正在使用的Linux 系统中包含如下
rc脚本:




第 5 章Linux文件系统与设备文件系统


由于字符设备和块设备都很好地体现了“一切都是文件”的设计思想,掌握Linux
文件系统、设备文件系统的知识非常重要。


Linux 2.6 内核的基于sysfs 的udev文件系统。


5.1 Linux文件操作


5.1.1 文件操作的相关系统调用
Linux 的文件操作系统调用(在Windows 编程领域,习惯称操作系统提供的接口
为API)涉及创建、打开、读写和关闭文件。


1、创建
int creat(const char *filename, mode_t mode);
int umask(int newmask);
该调用将umask设置为newmask,然后返回旧的umask,它只影响读、写和执行
权限。


2、打开
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);


open("test", O_CREAT, 10705);
上述语句等价于:
open("test", O_CREAT, S_IRWXU | S_IROTH | S_IXOTH | S_ISUID );


3.读写
在文件打开以后,我们才可对文件进行读写,Linux 系统中提供文件读写的系统
调用是 read、write函数,如下所示:
int read(int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);


4.定位
对于随机文件,我们可以随机地指定位置读写,使用如下函数进行定位:
int lseek(int fd, offset_t offset, int whence);
lseek()将文件读写指针相对whence移动offset个字节。
参数whence可以使用如下值。
SEEK_SET:相对文件开头。
SEEK_CUR:相对文件读写指针的当前位置。
SEEK_END:相对文件末尾。


offset可取负值,例如下述调用可将文件指针相对当前位置向前移动5 个字节。
lseek(fd, -5, SEEK_CUR);
由于lseek 函数的返回值为文件指针相对于文件头的位置,因此下列调用的返回值就
是文件的长度:
lseek(fd, 0, SEEK_END);


5、 关闭


int close(int fd);


1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <fcntl.h>
4 #include <stdio.h>
5 #define LENGTH 100
6 main()
7 {
8 int fd, len;
9 char str[LENGTH];
10
11 fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); /*
12 创建并打开文件*/
13 if (fd)
14 {
15 write(fd, "Hello World", strlen("Hello World")); /*写入字符串*/
16
17 close(fd);
18 }
19
20 fd = open("hello.txt", O_RDWR);
21 len = read(fd, str, LENGTH); /* 读取文件内容*/
22 str[len] = '\0';
23 printf("%s\n", str);
24 close(fd);
25 }


5.1.2 C库函数的文件操作


C 库函数的文件操作实际上是独立于具体的操作系统平台的,不管是在DOS、
Windows、Linux 还是在VxWorks中都是这些函数。


1.创建和打开


2.读写


3.关闭


1 #include <stdio.h>
2 #define LENGTH 100
3 main()
4 {
5 FILE *fd;
6 char str[LENGTH];
7
8 fd = fopen("hello.txt", "w+"); /* 创建并打开文件*/
9 if (fd)
10 {
11 fputs("Hello World", fd); /* 写入字符串*/
12 fclose(fd);
13 }
14
15 fd = fopen("hello.txt", "r");
16 fgets(str, LENGTH, fd); /* 读取文件内容*/
17 printf("%s\n", str);
18 fclose(fd);
19 }


5.2 Linux 文件系统


5.2.1 Linux文件系统目录结构


1./bin
包含基本命令,如ls、cp、mkdir 等,这个目录中的文件都是可执行的。


2./boot
Linux 系统的内核及引导系统程序所需要的文件,如vmlinuz、initrd.img 文件都位
于这个目录中。


3./dev
设备文件存储目录,应用程序通过对这些文件的读写和控制就可以访问实际的设
备。


4./etc
系统配置文件的所在地,一些服务器的配置文件也在这里,如用户账号及密码配
置文件。


5./home
普通用户的家目录。


6./lib
库文件存放目录。


7./lost+found
在Ext2 或Ext3 文件系统中,当系统意外崩溃或机器意外关机时会产生一些文
件碎片放在这里。


8./mnt
/mnt 这个目录一般是用于存放挂载储存设备的挂载目录的,比如有cdrom 等目
录,可以参看/etc/fstab的定义。有时我们可以把让系统开机自动挂载文件系统,把挂
载点放在这里也是可以的。


9./opt
opt是“可选”的意思,有些软件包会被安装在这里,比如在Fedora Core 5.0 中
的OpenOffice就是安装在这里,用户自己编译的软件包也可以安装在这个目录中。


10./proc
操作系统运行时,进程及内核信息(比如CPU、硬盘分区、内存信息等)存放在
这里。/proc 目录为伪文件系统proc 的挂载目录,proc 并不是真正的文件系统,它存
在于内存之中。


11./root
Linux 超级权限用户root 的家目录。


12./sbin
存放可执行文件,大多是涉及系统管理的命令,是超级权限用户root 的可执行命
令存放地,普通用户无权限执行这个目录下的命令,这个目录和
/usr/sbin;/usr/X11R6/sbin或/usr/local/sbin目录是相似的。


13./tmp
有时用户运行程序的时候会产生临时文件,/tmp用来存放临时文件。


14./usr
这个是系统存放程序的目录,比如命令、帮助文件等,它包含很多文件和目录,
Linux 发行版提供的软件包大多被安装在这里。


15./var
var 表示的是变化的意思,这个目录的内容经常变动,如/var 的/var/log 目录被用
来存放系统日志。


16./sys
Linux 2.6 内核所支持的sysfs文件系统被映射在此目录。Linux设备驱动模型中的
总线、驱动和设备都可以在sysfs 文件系统中找到对应的节点。当内核检测到在系统
中出现了新设备后,内核会在sysfs文件系统中为该新设备生成一项新的记录。


17./initrd
若在启动过程中使用了initrd 映像作为临时根文件系统,则在执行完其上的
/linuxrc挂接真正的根文件系统后,原来的初始RAM文件系统被映射到/initrd目录。




5.2.2 Linux文件系统与设备驱动


应用程序和VFS 之间的接口是系统调用,而VFS 与磁盘文件系统以及普通设备之间
的接口是file_operations结构体成员函数,这个结构体包含对文件进行打开、关闭、读写、
控制的一系列成员函数。


由于字符设备的上层没有磁盘文件系统,所以字符设备的file_operations 成员函
数就直接由设备驱动提供了,file_operations 正是字符设备驱动的核心。


在设备驱动程序的设计中,一般而言,会关心结构体file和inode这两个结构体。


1.file结构体


文件结构体
1 struct file
2 {
3 union
4 {
5 struct list_head fu_list;
6 struct rcu_head fu_rcuhead;
7 } f_u;
8 struct dentry *f_dentry; /*与文件关联的目录入口(dentry)结构*/
9 struct vfsmount *f_vfsmnt;
10 struct file_operations *f_op; /* 和文件关联的操作*/
11 atomic_t f_count;
12 unsigned int f_flags; /*文件标志,如O_RDONLY、O_NONBLOCK、O_SYNC*/
13 mode_t f_mode; /*文件读/写模式,FMODE_READ和FMODE_WRITE*/
14 loff_t f_pos; /* 当前读写位置*/
15 struct fown_struct f_owner;
16 unsigned int f_uid, f_gid;
17 struct file_ra_state f_ra;
18
19 unsigned long f_version;
20 void *f_security;
21
22 /* tty驱动需要,其他的驱动可能需要*/
23 void *private_data; /*文件私有数据*/
24
25 #ifdef CONFIG_EPOLL
26 /* 被fs/eventpoll.c使用以便连接所有这个文件的钩子(hooks) */
27 struct list_head f_ep_links;
28 spinlock_t f_ep_lock;
29 #endif /* #ifdef CONFIG_EPOLL */
30 struct address_space *f_mapping;
31 };


驱动程序中经常会使用如下类似的代码来检测用户打开文件的读写方式。
if (file->f_mode & FMODE_WRITE) //用户要求可写
{


}
if (file->f_mode & FMODE_READ) //用户要求可读
{


}


下面的代码可用于判断以阻塞还是非阻塞方式打开设备文件。
if (file->f_flags & O_NONBLOCK) //非阻塞
    pr_debug("open: non-blocking\n");
else //阻塞
    pr_debug("open: blocking\n");


2.inode结构体


VFS inode 包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修
改时间等信息。
它是Linux 管理文件系统的最基本单位,也是文件系统连接任何子目
录、文件的桥梁。


inode结构体
1 struct inode
2 {
3 ...
4 umode_t i_mode; /* inode的权限*/
5 uid_t i_uid; /* inode拥有者的id */
6 gid_t i_gid; /* inode所属的群组id */
7 dev_t i_rdev; /* 若是设备文件,此字段将记录设备的设备号*/
8 loff_t i_size; /* inode所代表的文件大小*/
9
10 struct timespec i_atime; /* inode最近一次的存取时间*/
11 struct timespec i_mtime; /* inode最近一次的修改时间*/
12 struct timespec i_ctime; /* inode的产生时间*/
13
14 unsigned long i_blksize; /* inode在做I/O时的区块大小*/
15 unsigned long i_blocks; /* inode 所使用的block 数,一个block 为512
byte*/
16
17 struct block_device *i_bdev;
18 /*若是块设备,为其对应的block_device结构体指针*/
19 struct cdev *i_cdev; /*若是字符设备,为其对应的cdev结构体指针*/
20 ...
21 };


对于表示设备文件的inode结构,i_rdev字段包含设备编号。Linux 2.6 设备编号
分为主设备编号和次设备编号,前者为dev_t 的高12 位,后者为dev_t 的低20 位。
下列操作用于从一个inode中获得主设备号和次设备号:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);


主设备号是与驱动对应的概念,同一类设备一般使用相同的主设备号,不同类的
设备一般使用不同的主设备号。
因为同一驱动可支持多个同类设备,因此用次设备号来描述使用该驱动的设备
的序号,序号一般从0 开始。


内核 Documents 目录下的devices.txt 文件描述了Linux 设备号的分配情况,它由
LANANA ( The Linux Assigned Names And Numbers Authority , 网址:
http://www.lanana.org/)组织维护,Torben Mathiasen 是其中的主要维护者


5.3 devfs设备文件系统


它的出现使得设备驱动程序能自主地管理它自己的设备文件。
具体来说,devfs 具有如下优点。
(1)可以通过程序在设备初始化时在/dev目录下创建设备文件,卸载设备时将它
删除。
(2) 设备驱动程序可以指定设备名、所有者和权限位,用户空间程序仍可以修改
所有者和权限位。
(3) 不再需要为设备驱动程序分配主设备号以及处理次设备号,在程序中可以直
接给register_chrdev()传递0 主设备号以动态获得可用的主设备号,并在
devfs_register()中指定次设备号。


/*创建设备目录*/
devfs_handle_t devfs_mk_dir(devfs_handle_t dir, const char *name, void
*info);


/*创建设备文件*/
devfs_handle_t devfs_register(devfs_handle_t dir, const char *name,
unsigned nt flags, unsigned int major, unsigned int minor, umode_t mode, void
*ops, void *info);


/*撤销设备文件*/
void devfs_unregister(devfs_handle_t de);


1 static devfs_handle_t devfs_handle;
2 static int _ _init xxx_init(void)
3 {
4 int ret;
5 int i;
6 /*在内核中注册设备*/
7 ret = register_chrdev(XXX_MAJOR, DEVICE_NAME, &xxx_fops);
8 if (ret < 0)
9 {
10 printk(DEVICE_NAME " can't register major number\n");
11 return ret;
12 }
13 /*创建设备文件*/
14 devfs_handle =devfs_register(NULL, DEVICE_NAME,
DEVFS_FL_DEFAULT,
15 XXX_MAJOR, 0, S_IFCHR | S_IRUSR | S_IWUSR, &xxx_fops, NULL);
16 ...
17 printk(DEVICE_NAME " initialized\n");
18 return 0;
19 }
20
21 static void _ _exit xxx_exit(void)
22 {
23 devfs_unregister(devfs_handle); /*撤销设备文件*/
24 unregister_chrdev(XXX_MAJOR, DEVICE_NAME); /*注销设备*/
25 }
26
27 module_init(xxx_init);
28 module_exit(xxx_exit);




5.4 udev设备文件系统


Linux VFS 内核维护者Al Viro指出了udev
取代devfs 的几点原因:
devfs 所做的工作被确信可以在用户态来完成。
一些bug相当长的时间内未被修复。
devfs 的维护者和作者停止了对代码的维护工作。


cd:变换目录
pwd:显示当前目录
mkdir:建立一个新的目录
rmdir:删除一个空的目录


2.kobject内核对象


kobject是Linux 2.6 引入的设备管理机制,在内核中由kobject结构体表示,这个
数据结构使所有设备在底层都具有统一的接口。


第6章  字符设备驱动


6.1 Linux字符设备驱动结构


6.1.1 cdev结构体


在 Linux 2.6 内核中使用cdev结构体描述字符设备,
1 struct cdev
2 {
3     struct kobject kobj; /* 内嵌的kobject对象*/
4     struct module *owner; /*所属模块*/
5     struct file_operations *ops; /*文件操作结构体*/
6     struct list_head list;
7     dev_t dev; /*设备号*/
8     unsigned int count;
9 };


cdev结构体的dev_t 成员定义了设备号,为32 位,其中高12 位为主设备号,低
20 位为次设备号。使用下列宏可以从dev_t获得主设备号和次设备号。
MAJOR(dev_t dev)
MINOR(dev_t dev)


而使用下列宏则可以通过主设备号和设备号生成dev_t。
MKDEV(int major, int minor)


void cdev_init(struct cdev *, struct file_operations *);
初始化cdev 的成员,并建立cdev 和file_operations 之间的连接。


struct cdev *cdev_alloc(void);
cdev_alloc()函数用于动态申请一个cdev内存。


cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备
的注册和注销。


6.1.2 分配和释放设备号


在 调用cdev_add() 函数向系统注册字符设备之前, 应首先调用
register_chrdev_region()或alloc_chrdev_region()函数向系统申请设备号,这两个函数
的原型如下:



猜你喜欢

转载自blog.csdn.net/baiyibin0530/article/details/79965489