📄 driver.txt
字号:
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><font size="3" color="#0000FF"><b><a href="#MN的驱动程序">— MN的驱动程序 —</a></b></font></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,但也是可以重载
的。
</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发现有新设备查在总线上
时,这个函数会被调用,如果返回一个指针,则说明这个驱动认领了一个新
的设备接口,如果返回NULL,则这个驱动并不适用新的设备。</PRE>
<PRE> 调用参数结构usb_device代表一个USB硬件,同样定义于usb.h,在此不列出。
这个结构体保存了一些USB规范上的信息,和一些链表用于把usb_device链
成树型,其中非叶子节点是一种特殊的USB设备:HUB。因为USB是可以热拔
插的,所以软件也必须做足够的健壮性处理。
</PRE>
<PRE> 结构体usb_device_id为热拔插策略的部署提供信息,定义于usb.h。第一个
成员为匹配规则,后面的成员为idVendor/idProduct等。通常一个驱动可以
带一堆同类的设备,所以通常实现出这个结构体的数组,并且数组的最后一
个元素要为零,作为停止匹配的标记。usb.h中同时定义了用于构筑id条目
的宏,使用很方便。</PRE>
<PRE> 函数指针void (*disconnect)(struct usb_device *, void *)实现和probe
正好相反的功能,在设备被拔走时调用。</PRE>
<PRE> MN的usb_driver定义在usb-mininurse.h的L102-109。USB CORE提供了注册
和注销函数:
extern int usb_register(struct usb_driver *);
extern void usb_deregister(struct usb_driver *);
usb-mininurse.c在usb_mn_init和usb_mn_exit中分别调用这两个函数,也
就是说每当模块被加载时就向USB CORE注册MN驱动,模块被移除时向USB
CORE注销MN驱动。</PRE>
<PRE> 至此已经为全面分析MN驱动做了足够准备。下面来看看MN是如何实现那三个
系统调用以及两个为USB新增的函数。</PRE>
<PRE> 通常probe总是第一个被调用,所以也先讨论,它位于usb-mininurse.c的
L134-201。它的参数刚才介绍过了。</PRE>
<PRE> L142-145
if ((udev->descriptor.idVendor != USB_MN_VENDOR_ID) ||
(udev->descriptor.idProduct != USB_MN_PRODUCT_ID)) {
return NULL;
}</PRE>
<PRE> 这段代码用于检测新设备的各种ID值是否适用于这个驱动。
USB_MNVENDOR_ID和USB_MN_PRODUCT_ID是在usb_mininurse.h定义的宏,其
值分别为0x0471(这是Philips在USB-IF申请的厂商代码)和0x0666,不过似
乎到2003年1月为止,Philips并没有启用0x0666这个产品代码。</PRE>
<PRE> L147-156,这段代码为新插入的MN选择一个合适的次设备号。数组
minor_table则保存了所有在线的MN。不过,自从驱动版本到0.1.1后,这段
代码和相关的变量不仅显得多余而且迫害了整个代码的清晰度。把它保留在
这里的唯一理由是只要做少量修改,代码就可以就更好的兼容性。以后会说
到版本0.1.1到底增加了神奇的东西。</PRE>
<PRE> L158-163,这段代码做常规的内存分配。其中的kmalloc函数和malloc函数
在使用上基本相同,但是它的第二个参数使得kamlloc有更多行为方式。在
这里使用的是GFP_KERNEL,通常在系统调用级的函数中会用这个标记分配内
存,它要求调用函数必须可重入。此外kmalloc获得内存后不会清空原有内容,
通常为了安全用memeset将其置空。</PRE>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -