如何往内核里添加一个字符驱动程序
-
分配设备号
- 前置:
- 设备号分为主设备号和次设备号.
- 主设备号是分配给设备驱动程序的唯一标识符,用于标识设备所属的驱动程序。它告诉内核在访问设备时应该调用哪个驱动程序来处理请求.
- 次设备号是与主设备号配合使用的较小标识符,用于区分同一主设备号下的不同设备实例
- 内核中设备号是一个
dev_t
类型的变量, 通过MAJOR
和MINOR
宏可以获取主设备号和次设备号.MKDEV
宏可以将主设备号和次设备号合并成一个dev_t
类型的变量.
申请设备号的方法有两种:
- 静态申请, 自己指定设备号
int register_chrdev_region(dev_t first, unsigned int count, char *name);
- 动态申请, 调用该函数后内核会分配一个未被使用的设备号
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
无论使用哪种方法都在不需要的时候释放设备号
void unregister_chrdev_region(dev_t first, unsigned int count);
-
实现设备操作函数
在linux中一切皆文件, linux使用统一的接口来访问设备, 实现这些接口就是实现设备的驱动程序, 不支持的接口可以设置为NULL
.
接口的定义是struct file_operations
:
struct module *owner // 指向拥有这个结构的模块的指针, 避免它还在被使用时模块被卸载. 通常使用被初始为 THIS_MODULEloff_t (*llseek) (struct file *, loff_t, int); //改变文件中的当前读/写位置ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //从设备中获取数据ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t); //异步读ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //发送数据给设备ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *); //异步写int (*open) (struct inode *, struct file *); //对设备文件的第一个操作int (*release) (struct inode *, struct file *); //在文件结构被释放时引用这个操作,文件关闭时long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long parm); //原生的ioctl,设备特定的控制命令long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //为了兼容32为的ioctl
...
-
内核中使用
cdev
管理驱动程序.- 使用
cdev_alloc
函数分配一个cdev
结构体 - 使用
cdev_init(struct cdev *cdev, struct file_operations *fops)
函数初始化cdev
结构体, 把设备操作函数和cdev绑定. - 使用
cdev_add(struct cdev *cdev, dev_t dev, unsigned int count)
函数将cdev
添加到内核中, 和设备号绑定在一起, 一个cdev可以和多个设备号绑定在一起,这个函数会把dev - (dev+count-1)的设备号都绑定到这个cdev.
- 在不需要的时候,使用
cdev_del(struct cdev *cdev)
函数将cdev
从内核中删除, 使其失效.
- 使用
-
使用模块技术加载进入内核.
创建设备节点(文件)
设备节点通常是在/dev
目录下, 通过mknod
命令创建, 也可以通过udev
自动创建.
mknod 命令格式: c 表示字符设备, b 表示块设备
mknod /dev/设备名 c 主设备号 次设备号
创建设备节点的时候会根据设备号把对应的cdev放在文件的inode中, 这样就能通过这个设备节点文件访问到对应的设备驱动程序.
使用动态分配设备号的一个问题就是不能提前知道设备号, 不能提前创建设备节点. 只能在分配通过/proc/devices
查看设备号, 然后创建设备节点.
下面是一个创建设备节点的脚本:
#!/bin/sh
module="scull"
device="scull"
mode="664"
# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
/sbin/insmod ./$module.ko $* || exit 1 # $*表示所以传入当前脚本的参数, || exit 1表示如果insmod执行失败,则exit 1 错误退出
# remove stale nodes
rm -f /dev/${device}[0-3]
major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices)
mknod -m ${mode} /dev/${device}0 c $major 0
mknod -m ${mode} /dev/${device}1 c $major 1
mknod -m ${mode} /dev/${device}2 c $major 2
mknod -m ${mode} /dev/${device}3 c $major 3
# give appropriate group/permissions, and change the group.
# Not all distributions have staff, some have "wheel" instead.
# group="staff"
# grep -q '^staff:' /etc/group || group="wheel"
# chgrp $group /dev/${device}[0-3]
# chmod $mode /dev/${device}[0-3]
快速参考
<linux/types.h> |
---|
dev_t 内核设备号类型 |
<linux/kdev_t.h> |
---|
MAJOR (dev_t dev); |
MINOR (dev_t dev); |
MKDEV (int major, int minor); |
<linux/fs.h> |
---|
file_operations |
alloc_chrdev_region (dev_t *dev, unsigned int firstminor, unsigned int count, char *name) |
register_chrdev_region (dev_t first, unsigned int count, char *name) |
unregister_chrdev_region (dev_t first, unsigned int count); |
iminor (inode) 通过inode获取次设备号 |
imajor (inode) |
<linux/cdev.h> |
---|
struct cdev *cdev_alloc (void); |
void cdev_init (struct cdev *dev, struct file_operations *fops); |
int cdev_add (struct cdev *dev, dev_t num, unsigned int count); |
void cdev_del (struct cdev *dev); |
<linux/kernel.h> |
---|
container_of (pointer, container_type, container_field); 通过filed找到container的地址 |
<linux/uaccess.h> |
---|
copy_to_user (void __user *to, const void *from, unsigned long n) |
copy_from_user (void *to, const void __user *from, unsigned long n) |