概述:基于kernle 4.0 的qemu 对字符设备驱动学习进行记录。
说明: 字符设备驱动相对来说是比较成熟的一个驱动模型,尽管是在linux 4.0 的版本上进行学习和记录,在之前或者之后的版本中,操作方法应该都是一样的。
1 字符设备的数据结构
文件路径:include\linux\cdev.h
字符设备通过一个接着一个的字符已流的方式向用户程序传送数据的设备或者虚拟设备。内核中的数据类型如下:
- struct cdev {
- struct kobject kobj; /*字符设备嵌入的kobject 对象*/
- struct module *owner; /*驱动的拥有者*/
- const struct file_operations *ops; /*驱动提供的文件操作方法(函数)接口*/
- struct list_head list; /*字符设备链表*/
- dev_t dev; /*字符设备设备号*/
- unsigned int count; /*字符设备子设备个数*/
- };
2 字符设备的设备号
每一个字符设备都是有设备号来唯一标识,设备号又分主设备号和次设备号,分别表示这类设备或者这个设备的,对应关系如下:

用命令“ls -l /dev ” 可以查看到如下

红框中245、5、253、4等分别是teepriv0、tty、ttyFIQ0和ttyS0的主设备号。其中 ‘c’ 开头的就是字符设备的缩写,还有
b-----表示块设备
p-----表示命名管道
d-----表示文件夹
l------表示链接文件
s-----套接字
观察上面的截图可以发现哈,主设备号,都是用来代表一类设备的,tty 和 ttyS0 不是一类设备,所以他们的主设备号就不一样。那么假如同一类设备,比如都是 ttyS*,设备号是什么样的呢?还是查看 /dev 目录下,可以看到,关于camera有如下设备:

哈, 主设备号全部一致,都是81,但是后面的次设备号不一样;设备号从两个维度唯一确定了一个设备。
dev_t 是一个tyedef 自定义的类型,其实就是无符号整形,占用4个字节,32 bits。

kernel 提供了一些接口对设备号进行操作
- #define MINORBITS 20
- #define MINORMASK ((1U << MINORBITS) - 1)
-
- #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) /*从设备号中获取主设备号*/
- #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) /*从设备号获取次设备号*/
- #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) /*将主、次设备号合成设备号*/
通过如上这些宏,可以看出,dev_t 分开的主次设备号是由前 12 个bit 和后 20 个bit 分别代表主设备号和次设备号组成 。
3. 字符设备的操作方法集合:struct file_operations *ops
路径:include\linux\fs.h
其定义如下:
- struct file_operations {
- struct module *owner;
- loff_t (*llseek) (struct file *, loff_t, int);
- ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
- ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
- ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
- ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
- ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
- ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
- int (*iterate) (struct file *, struct dir_context *);
- unsigned int (*poll) (struct file *, struct poll_table_struct *);
- long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
- long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
- int (*mmap) (struct file *, struct vm_area_struct *);
- int (*mremap)(struct file *, struct vm_area_struct *);
- int (*open) (struct inode *, struct file *);
- int (*flush) (struct file *, fl_owner_t id);
- int (*release) (struct inode *, struct file *);
- int (*fsync) (struct file *, loff_t, loff_t, int datasync);
- int (*aio_fsync) (struct kiocb *, int datasync);
- int (*fasync) (int, struct file *, int);
- int (*lock) (struct file *, int, struct file_lock *);
- 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);
- int (*check_flags)(int);
- int (*flock) (struct file *, int, struct file_lock *);
- ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
- ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
- int (*setlease)(struct file *, long, struct file_lock **, void **);
- long (*fallocate)(struct file *file, int mode, loff_t offset,
- loff_t len);
- void (*show_fdinfo)(struct seq_file *m, struct file *f);
- #ifndef CONFIG_MMU
- unsigned (*mmap_capabilities)(struct file *);
- #endif
- };
额~~~ 比较多,常用的也就 open、close、read、write和ioctl。后面实现一个字符驱动的时候会涉及到。