📄 drivers.html
字号:
<PRE> 正如前面说到的,打开设备文件并返回文件描述符的操作与对一个普通文本 的操作没有任何区别。O_WRONLY标记指明文件以只读方式打开,因为MN固件 尚未实现从设备读的处理。</PRE><PRE> 紧跟着的一个switch是对第二个参数的处理,至于其中的0x04/0x08/0x0C, 可以参考固件和MN的连线。</PRE><PRE> 对第一个参数的处理包含着重要的语句:ioctl,正是这些语句让MN散发出 迷人的光芒。核外空间ioctl的原型长得比较奇怪: int ioctl (int fd, int cmd, ...); 省略号表示可变参数,根据cmd的不同这个可变参数的意义/存在与否/参数 类型也不同(不要怀疑你对C语言的掌握,这个参数其实总是存在的,并且有 一个唯一的类型:char *argp)。参数cmd会原装传递给内核,附加的参数会 被作为unsigned long传递。不过要十分注意的是当附加参数是指针时,需 特别处理,原因是内核空间的核外空间使用不同的映射方法;并且也有可能 带来安全问题。MN的这个参数类型是u16,所以暂时不用担心这个问题。</PRE><PRE> 回到终端用户身份,再执行一次turnLED,不过这次同时使用一个特殊的程 序: [root@Home show]# strace ./turnLED 1 A execve("./turnLED", ["./turnLED", "1", "A"], [/* 53 vars */]) = 0 uname({sys="Linux", node="Home", ...}) = 0 brk(0) = 0x8049854 old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40013000 open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=63441, ...}) = 0 old_mmap(NULL, 63441, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40014000 close(3) = 0 open("/lib/i686/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260X\1"..., 1024) = 1024 fstat64(3, {st_mode=S_IFREG|0755, st_size=1246468, ...}) = 0 old_mmap(NULL, 1256516, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x40024000 mprotect(0x40151000, 23620, PROT_NONE) = 0 old_mmap(0x40151000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x12c000) = 0x40151000 old_mmap(0x40155000, 7236, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40155000 close(3) = 0 munmap(0x40014000, 63441) = 0 open("/dev/MiniNurse", O_WRONLY) = 3 ioctl(3, 0x80022500, 0xc) = 0 close(3) = 0 exit_group(0) = ? strace能够显示一个进程使用到的系统调用及参数和返回值,这是一个有力 的调试工具。通过它可以更清楚的了解究竟应用程序做了什么。</PRE><PRE> 最后说明一点,MN的驱动对内核非强占式的Linux2.4而言基本上是可重入的, 也就是说可以用两个进程(只要你喜欢,三个也行)同时对MN进行操作。要取 得好的效果,IPC当然是必不可少的,不过它不在本文讨论范围之内。</PRE><PRE><b><a name="— MN的驱动程序 —"><font size="3" color="#000080">— MN的驱动程序 —</font></a></b></PRE><PRE> 本段想按如下顺序进行: * 更多Linux模块、驱动细节介绍 * 作为USB设备的MN的驱动程序 * BUGs 和 TODO</PRE><PRE> 前面说到过一个模块的入口和出口,现在看看关于这两个函数的一些操作, usb-mininurse.h的L83-86。</PRE><PRE> static int usb_mn_init(void); static void usb_mn_exit(void); module_init (usb_mn_init); module_exit (usb_mn_exit);</PRE><PRE> moudle_init和module_exit都是位于<kernels>/include/linux/init.h中的 宏,这两个宏的作用是重命名出、入口函数。我猜这大概是为了响应命名规 则的号召。执行了这两个宏后,模块被加入时执行usb_mn_init(void);被移 除时执行usb_mn_exit。</PRE><PRE> 仅有模块出、入口还不能完成驱动能力。前面说过驱动程序完成的事情:解 释、处理资源(如中断);实现系统调用;实现机制。对资源的解释处理融于 驱动的实现细节之内,而关于机制的讨论可能会放在后面。现在看看驱动程 序是如何实现系统调用从而让应用程序发挥功用。首先介绍两个重要的数据 结构,均定义于<kernels>/include/linux/fs.h</PRE><PRE> struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); };</PRE><PRE> 在这个数据结构中,多数成员名是似曾相识的,并且它们的类型都是函数指 针。你应该很容易联想到开头说到的两个面向对象术语:“重载”和“方 法”。没错,操作系统正是用这一技术对外提供统一的文件“对象”名,对 内(驱动程序)允许重载每个系统调用的实现。并不是每个域都是为设备驱动 程序准备的,并且即便如此,也不是每个驱动程序都必须实现所有的域。随 着时间的推移,结构成员也会有所变化。一会儿会结合MN驱动做分析。</PRE><PRE> struct file { struct list_head f_list; struct dentry *f_dentry; struct vfsmount *f_vfsmnt; struct file_operations *f_op; atomic_t f_count; unsigned int f_flags; mode_t f_mode; loff_t f_pos; unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; struct fown_struct f_owner; unsigned int f_uid, f_gid; int f_error;</PRE><PRE> unsigned long f_version;</PRE><PRE> /* needed for tty driver, and maybe others */ void *private_data;</PRE><PRE> /* preallocated helper kiobuf to speedup O_DIRECT */ struct kiobuf *f_iobuf; long f_iobuf_lock; };</PRE> <PRE> file结构代表一个广义文件,在*nix里一切都是文件(包括MN),而不仅仅是 通常认为的磁盘或其它介质上文件。当执行系统调用open时,内核创建该结 构的一个实例并自动维护直到对open时返回的文件描述符做close。成员 *private_date,可以由驱动开发人员自由使用,通常用来保存设备的相关 信息,因为并不是所有的系统调用内核都向其传递设备信息。这个结构里也 有一个file_operations实例:*f_op,不同文件有不同的f_op,也就有了不 同的read/write等实现。主设备号通常可以对应一个f_op,但也是可以重载 的。注意不是ANSI C中的FILE,核外的东西不可能出现在核内。 </PRE><PRE> 文件/dev/MiniNurse的file_operations定义在usb-mininurse.h的L94-100。</PRE><PRE> static struct file_operations mn_fops = { owner: THIS_MODULE,</PRE><PRE> ioctl: mn_ioctl, open: mn_open, release: mn_release, };</PRE><PRE> 这不是ISO-C89语法,这是一个GNU-C扩展。比起直接用指针传递,这种方法 看上去更加清爽,并且使用这种扩展可以更容易移植。通常编译器是不会调 整结构成员顺序的,但是当编译这个语法成分时,编译器则能将频繁访问的 成员放在相同的硬件缓冲行上取得性能的提高。在ISO-C99规范中也开始引 进类似的语法,Linux2.4可以同时使用这两个,Linux2.6强制使用ISO-C99 的语法。</PRE><PRE> owner字段被初始化为THIS_MODULE,这个宏帮助Linux2.4初始化模块,并自 动完成模块引用记数的工作。 MN实现了三个系统调用:ioctl/open/release,并分别命名为: mn_ioctl/mn_open/mn_release。其它的都被做为NULL处理。</PRE><PRE> MN应该算作字符设备(至少不会是块或网络设备),但是同时它也是USB设备, 对MN的操作强烈依赖USB-CORE,因此钩上USB子系统比象征意义地钩上字符 设备列表重要得多。 作为USB设备,其驱动软件的最高抽象是结构usb_driver,定义于 <kernels>/include/linux/usb.h: </PRE><PRE> struct usb_driver { struct module *owner;</PRE><PRE> const char *name;</PRE><PRE> void *(*probe)( struct usb_device *dev, /* the device */ unsigned intf, /* what interface */ const struct usb_device_id *id /* from id_table */ ); void (*disconnect)(struct usb_device *, void *);</PRE><PRE> struct list_head driver_list;</PRE><PRE> struct file_operations *fops; int minor;</PRE><PRE> struct semaphore serialize;</PRE><PRE> int (*ioctl)(struct usb_device *dev, unsigned int code, void *buf);</PRE><PRE> const struct usb_device_id *id_table; };</PRE><PRE> 就其要者: const char *name,这个名字在USB系统中应该是唯一的,按照建议它和模块 的名字应该是一样的。</PRE><PRE> 函数指针void *(*probe) {struct usb_device *dev, unsigned intf, const struct usb_device_id *id},当USB CORE发现有新设备查在总线上 时,这个函数会被调用,如果返回一个指针,则说明这个驱动认领了一个新
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -