0%

实现最简单的字符设备驱动

实现最简单的字符设备驱动

1 驱动程序的入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int hello_init(void) {
devNum = MKDEV(reg_major, reg_minor);
if(OK == register_chrdev_region(devNum, subDevNum, "helloworld")) {
printk(KERN_INFO "register_chrdev_region ok \n");
} else {
printk(KERN_INFO "register_chrdev_region error n");
return ERROR;
}
printk(KERN_INFO " hello driver init \n");
gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
gFile->open = hello_open;
gFile->read = hello_read;
gFile->write = hello_write;
gFile->owner = THIS_MODULE;
cdev_init(gDev, gFile);
cdev_add(gDev, devNum, 1);
return 0;
}
  • MKDEV 根据主、次设备号生成设备编号,通常主设备号表示某一类设备,次设备号表示这类设备里不同的设备。MKDEV 的实现,
1
2
#define MINORBITS   20
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

将主设备号左移20位,然后或上从设备号

  • 注册字符设备,
1
2
/*指定设备编号来静态注册一个字符设备*/
int register_chrdev_region(dev_t from, unsigned count, const char *name); 
参数 说明
from 设备号
count 需要连续注册的次设备编号个数
name 字符设备名称

当返回值小于0时,表示注册失败

  • 为字符设备分配内存,
1
gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);

kzalloc等价于先调用kmalloc分配一块内存空间,然后初始化为0

1
2
3
4
5
6
7
8
9
/**
* kzalloc - allocate memory. The memory is set to zero.
* @size: how many bytes of memory are required.
* @flags: the type of memory to allocate (see kmalloc).
*/
static inline void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO);
}
参数 说明
size 要分配内存空间的字节数
flags 分配方式

对于 gfp_t (gfp 是 get free page的缩写)类型,有以下几个常量,

gfp_t 常量 说明
GFP_ATOMIC 内存分配过程是原子的,不会被(高优先级进程或中断)打断
GFP_KERNEL 正常分配内存
GFP_DMA 给 DMA 控制器分配内存

分配内存后如果不释放会造成内存泄漏,在内存中可能导致系统崩溃,

1
void kfree(const void *objp);

可以调用 kfree 函数释放动态分配的内存

  • file_operations 结构体中声明了一组对文件的操作,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 (*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 (*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、read、write、flush 等。用户进程在对设备文件(linux 中一切皆为文件)进行诸如 read/write 操作的时候,系统调用通过设备文件的主设备号找到相应的设备驱动程序,读取 file_operations 结构体中对应的函数指针,接着把控制权交给该函数,这是 linux 的设备驱动程序工作的基本原理

  • 以回调函数的形式指定驱动程序对文件的各种操作,
1
2
3
4
gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
gFile->open = hello_open;
gFile->read = hello_read;
gFile->write = hello_write;

实践中,file_operations 结构体的 owner 字段通常被初始化为宏 THIS_MODULE,

1
gFile->owner = THIS_MODULE;
  • 建立字符设备和 file_operations 对象的联系,
1
cdev_init(gDev, gFile);

cdev_init 函数声明,

1
void cdev_init(struct cdev *, const struct file_operations *);

传入字符设备、file_operations指针,建立二者之间的联系

  • 建立字符设备和设备号的联系,
1
cdev_add(gDev, devNum, 1);

cdev_add 函数声明,

1
int cdev_add(struct cdev *, dev_t, unsigned);

传入字符设备指针、设备号、添加设备的个数,建立字符设备和设备号之间的联系

2 驱动程序的出口

1
2
3
4
5
6
7
8
void __exit hello_exit(void) {
printk(KERN_INFO " hello driver exit \n");
cdev_del(gDev);
kfree(gFile);
kfree(gDev);
unregister_chrdev_region(devNum, subDevNum);
return;
}
  • 移除字符设备,
1
void cdev_del(struct cdev *);
  • 释放动态分配的内存空间,
1
void kfree(const void *);
  • 注销设备号,
1
extern void unregister_chrdev_region(dev_t, unsigned);

3 驱动程序的文件操作逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int hello_open(struct inode *p, struct file *f) {
printk(KERN_INFO "hello_open\r\n");
return 0;
}


ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l) {
printk(KERN_INFO "hello_write\r\n");
return 0;
}


ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l) {
printk(KERN_INFO "hello_read\r\n");
return 0;
}

作为实例程序,只输出操作对应的提示信息

4 注册驱动程序的入口、出口函数

1
2
3
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
  • 分别调用 module_init 和 module_exit 函数,注册驱动程序的入口、出口函数
  • 在 linux 系统,执行 insmod 命令会调用驱动程序入口函数,执行 rmmod 命令会调用驱动程序出口函数