1. 什么是字符设备驱动
Linux将设备分为三大类:字符设备、块设备和网络设备。字符设备是其中较为基础的一类,它的读写操作需要一个字节一个字节地进行,无法随机读取设备中的某一字节的数据。比较常见的字符设备有鼠标、键盘、串口等。
2. char device driver internal
2.1 数据结构
Linux 使用 cdev 结构体来表示一个字符设备
 | struct cdev { 	struct kobject kobj; 	struct module *owner; 	const struct file_operations *ops; 	struct list_head list; 	dev_t dev; 	unsigned int count; } __randomize_layout;
 
  | 
 
kojb 不必多说,嵌入在 cdev 中,从而构成设备间的层次关系。
其中,dev_t 表示设备号,一共有 32 bit,其中 24 bit 为主设备号,8 bit 为次设备号,可以通过下面的宏快速操作
1 2 3
   | #define MAJOR(dev)	((dev)>>8) #define MINOR(dev)	((dev) & 0xff) #define MKDEV(ma,mi)	((ma)<<8 | (mi))
 
  | 
 
另一个关键成员 ops 则定义了字符设备驱动提供给虚拟文件系统的接口函数。
2.2 设备初始化
每种设备都需要分配空间+初始化之后才能使用,字符设备也不例外,分别对应 cdev_alloc 和cdev_device_add。
1 2 3 4 5 6 7 8 9
   | struct cdev *cdev_alloc(void) { 	struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); 	if (p) { 		INIT_LIST_HEAD(&p->list); 		kobject_init(&p->kobj, &ktype_cdev_dynamic); 	} 	return p; }
 
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | int cdev_device_add(struct cdev *cdev, struct device *dev) { 	int rc = 0;
  	if (dev->devt) { 		cdev_set_parent(cdev, &dev->kobj);
  		rc = cdev_add(cdev, dev->devt, 1); 		if (rc) 			return rc; 	}
  	rc = device_add(dev); 	if (rc) 		cdev_del(cdev);
  	return rc; }
 
  | 
 
同时需要注意,调用 cdev_device_add 添加设备之前,还需要通过 register_chrdev_region 申请设备号,从而赋予该设备唯一的标识
2.3 文件系统 API
file_operations 中的回调函数是字符设备驱动的关键,这些函数会在虚拟文件系统的 open,read,write,close 等操作时被内核回调。
下面通过一些具体的例子来说明字符设备驱动应该如何设置 file_operations
2.3.1 tty
1 2 3 4 5 6 7 8 9 10 11 12
   | static const struct file_operations tty_fops = { 	.llseek		= no_llseek, 	.read		= tty_read, 	.write		= tty_write, 	.poll		= tty_poll, 	.unlocked_ioctl	= tty_ioctl, 	.compat_ioctl	= tty_compat_ioctl, 	.open		= tty_open, 	.release	= tty_release, 	.fasync		= tty_fasync, 	.show_fdinfo	= tty_show_fdinfo, };
 
  |