Q:内核中的如何描述设备号?
A:Struct kdev_t
Q:如何从kdev_t结构中分解出主设备号?
A:MAJOR(kdev_t dev)
Q:如何从kdev_t结构中分解出次设备号?
A:MINOR(kdev_t dev)
-
创建设备文件:
-
使用mknod命令手工创建。
-
使用devis提供的函数在驱动程序中自动创建。
-
2.Mknod用法:
Mknod filename type major minor
Filename:设备文件名
Type:设备文件类型
Major:主设备号
Minor:次设备号
例子:mknod serial0 c 100 0(创建的设备名字为serial0,c代表的是字符设备,主设备号为100,次设备号为0)。
3.重要结构:
在linux字符设备驱动程序设计中,有三种非常重要的数据结构。
Struct file
Struct inode
Struct file_operations
Struct file:代表一个打开的文件,系统中每个打开的文件在内核中都有一个关联的struct file. 它由内核在打开文件时创建,在文件关闭后,内核释放这个数据结构。
重要成员:
Loff_t f_pos /*文件读写位置*/
Struct file_operations *f_op
Struct inode:用来表示物理上存在的文件。因此,它和代表打开文件描述符的file是不同德。一个物理上存在的文件可以有多个打开描述符file,但只有一个inode结构。
重要成员:
Kdev_t i_dev:设备号
Struct file_operations:一个函数指针的集合,定义能在设备上进行的操作。结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留为null。
例子:men_fops
Struct file_operations mem_fops={
.owner= THIS_MODULE,
.llseek=mem_seek,
.read = mem_read,
.write =mem_write,
.ioctl = mem_ioctl,
.open = mem_open,
.release = mem_release,
};
设备驱动安装:
安装一个设备驱动的方法:
Int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
这里,major是主设备号,name是驱动的名字(出现在/proc/devices),fops是file_operations结构。
卸载一个字符设备驱动的方法:
Int unregister_chrdev(unsigned int major, const char *name);
Major和name必须和传递给register_chrdev的相同,否则调用会失败。
设备操作:
Int (*pen)(struct inode *, struct file *)
这是操作在设备文件上的第一个操作,然而并不要求驱动程序一定要声明这个方法。如果该项为NULL,设备的打开操作永远成功。
Void (*release)(struct inode *, struct file *)
当设备文件被关闭时调用这个操作。与open相仿,release也可以没有。
Int (*read)(struct inode *, struct file *, char *, int);
用来从设备中读取数据。当其为null指针时将引起read系统调用返回-EINVAL。函数返回一个非负数表示成功的读取了一个字节。
Int (*write)(struct inode *, struct file *, const char *, int);
向设备发送数据。如果没有这个函数,write系统调用向调用程序返回一个-EINVAL。
Int (*select)(struct inode *, struct file *, int , select_table *);
Select一般用于程序询问设备是否可读和可写。
Int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);
系统调用ioctl提供调用设备相关命令的方法,对于任何内核没有定义的请求,ioctl系统调用将返回-EINVAL.当调用成功时,返回给程序一个非负返回值。
Int (*mmap)(struct inode *, struct file *, struct vm_area_struct *);
Mmap用来将设备内存映射到进程内存中。
Int (*lseek)(struct inode *, struct file *, off_t, int);
用来修改一个文件的当前读写位置,并将新位置作为返回值。
Open方法:
Open方法是驱动程序用来为以后的操作完成初始化准备工作的。此外,open还会增加设备计数,以便于防止文件在关闭前模块被卸载出内核。在大部分驱动程序中,open完成如下工作:
初始化设备。表明次设备号。增加使用计数。
Release方法:
Release方法的作用正好与open相反。这个设备方法又是也称为close。它应该:使用计数减1.关闭设备。
读和写:
读和写方法都是进行类似的任务,从和到应用程序代码拷贝数据。因此,他们的原型相当类似:
Ssize_t xxx(dev)_read(struct file *filp, char *buff, size_t count, loff_t *offp);
Ssize_t xxx(dev)_write(struct file *filp, const char *buff, size_t count, loff_t *offp);
对于这两个方法,flip是文件指针,count是请求的传输数据大小。Buff参数指向数据缓存。最后,offp他指向用户正在存取的文件位置。
Read和write方法的buff参数是用户空间指针。因此,他不能被内核代码直接引用,理由如下:
用户空间指针在内核空间时可能根本是无效的----没有那个地址的映射。
驱动必须能够存取用户空间缓存以完成它的工作。但是,为了安全起见这个存取必须使用特殊的,内核提供的函数,例如:
Unsigned long copy_to_user(void __user *to, const void *from, unsigned long count).
Unsigned long copy_from_user(void *to, const void __user *from, unsigned long count).
上面的例子中app=mem.c是测试的程序,memdev.c和memdev.h是驱动程序。该驱动程序实现的功能,它不驱动具体的设备,只是访问某个内存。