本篇介绍
本篇介绍下如何写字符设备的驱动程序。
支持阻塞IO的驱动demo
Linux 上的设备类型可以大概分为以下几种:
- 字符设备:以字节为单位传输,传输率低,不支持随机访问,常见的设备有鼠标,键盘,触摸屏等
- 块设备: 以块位单位传输,常见的就是磁盘
- 网络设备:涉及网络协议的设备
本篇先看字符设备的内容。
先看下字符设备的结构
struct cdev {
struct kobject kobj; // 用于linux设备驱动模型
struct module *owner; // 字符设备驱动所在的内核模块对象指针
const struct file_operations *ops; // 字符设备驱动中最关键的一个操作函数,在和应用程序交互的过程中起枢纽作用
struct list_head list; // 用来将字符设备串成一个链表
dev_t dev; // 字符设备的设备号,由主设备号和次设备号组成
unsigned int count; // 同属某个主设备号的次设备号个数
} __randomize_layout;
设备号相关的操作方法如下:
#define MAJOR(dev) ((dev)>>8)
#define MINOR(dev) ((dev) & 0xff)
#define MKDEV(ma,mi) ((ma)<<8 | (mi))
cdev的操作方法如下:
struct cdev *cdev_alloc(void); // 创建cdev
void cdev_init(struct cdev *, const struct file_operations *); // 初始化
int cdev_add(struct cdev *, dev_t, unsigned); // 添加字符设备
void cdev_del(struct cdev *); // 从系统中删除cdev 数据结构
内核也提供了动态分配设备号的方法:
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *); // 系统自动分配一个主设备号
extern int register_chrdev_region(dev_t, unsigned, const char *); //指定主设备号进行分配子设备号
extern void unregister_chrdev_region(dev_t, unsigned); // 释放设备号
再看下file_operations的结构:
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 (*iopoll)(struct kiocb *kiocb, struct io_comp_batch *,
unsigned int flags);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*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 *);
unsigned long mmap_supported_flags;
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 (*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
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
int (*uring_cmd)(struct io_uring_cmd *ioucmd, unsigned int issue_flags);
int (*uring_cmd_iopoll)(struct io_uring_cmd *, struct io_comp_batch *,
unsigned int poll_flags);
} __randomize_layout;
这些函数的名字基本都可以自解释。
再介绍下misc 设备,linux 内核将一些不符合预先确定的字符设备划分为杂项设备,使用的数据结构如下;
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
主要的操作函数如下:
extern int misc_register(struct miscdevice *misc);
extern void misc_deregister(struct miscdevice *misc);
接下来我们看一个misc设备的例子。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>
#include <linux/sched.h>
#include <linux/slab.h>
#define DEMO_NAME "my_misc_dev"
DEFINE_KFIFO(mydemo_fifo, char, 64);
struct mydemo_device {
const char* name;
struct device *dev;
struct miscdevice *miscdev;
wait_queue_head_t read_queue;
wait_queue_head_t write_queue;
};
struct mydemo_private_data {
struct mydemo_device *device;
};
static struct mydemo_device *demo_device;
static int demodrv_open(struct inode *inode, struct file *file)
{
struct mydemo_private_data *data;
struct mydemo_device *device = demo_device;
int major = MAJOR(inode->i_rdev);
int minor = MINOR(inode->i_rdev);
printk("%s: major = %d, minor = %d\n", __func__, major, minor);
data = kmalloc(sizeof(struct mydemo_private_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->device = device;
file->private_data = data;
return 0;
}
static int demodrv_release(struct inode *inode, struct file *file)
{
struct mydemo_private_data *data = file->private_data;
kfree(data);
return 0;
}
static ssize_t demodev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
printk("%s enter\n", __func__);
struct mydemo_private_data *data = file->private_data;
struct mydemo_device *device = data->device;
int actual_readed;
int ret;
if (kfifo_is_empty(&mydemo_fifo)) {
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
printk("%s: pid = %d, going to sleep\n", __func__, current->pid);
ret = wait_event_interruptible(device->read_queue,
!kfifo_is_empty(&mydemo_fifo));
if (ret)
return ret;
}
ret = kfifo_to_user(&mydemo_fifo, buf, count, &actual_readed);
if (ret)
return -EIO;
if (!kfifo_is_full(&mydemo_fifo))
wake_up_interruptible(&device->write_queue);
printk("%s, pid=%d, actual_readed=%d, pos=%lld\n",__func__,
current->pid, actual_readed, *ppos);
return actual_readed;
}
static ssize_t demodrv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
printk("%s enter\n", __func__);
struct mydemo_private_data *data = file->private_data;
struct mydemo_device *device = data->device;
unsigned int actual_write;
int ret;
if (kfifo_is_full(&mydemo_fifo)){
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
printk("%s: pid=%d, going to sleep\n", __func__, current->pid);
ret = wait_event_interruptible(device->write_queue,
!kfifo_is_full(&mydemo_fifo));
if (ret)
return ret;
}
ret = kfifo_from_user(&mydemo_fifo, buf, count, &actual_write);
if (ret)
return -EIO;
if (!kfifo_is_empty(&mydemo_fifo))
wake_up_interruptible(&device->read_queue);
printk("%s: pid=%d, actual_write =%d, ppos=%lld, ret=%d\n", __func__,
current->pid, actual_write, *ppos, ret);
return actual_write;
}
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.release = demodrv_release,
.read = demodev_read,
.write = demodrv_write
};
static struct miscdevice mydemodrv_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEMO_NAME,
.fops = &demodrv_fops,
};
static int __init simple_char_init(void)
{
int ret;
struct mydemo_device *device = kmalloc(sizeof(struct mydemo_device), GFP_KERNEL);
if (!device)
return -ENOMEM;
ret = misc_register(&mydemodrv_misc_device);
if (ret) {
printk("failed register misc device\n");
goto free_device;
}
device->dev = mydemodrv_misc_device.this_device;
device->miscdev = &mydemodrv_misc_device;
init_waitqueue_head(&device->read_queue);
init_waitqueue_head(&device->write_queue);
demo_device = device;
printk("succeeded register char device:%s\n", DEMO_NAME);
return 0;
free_device:
kfree(device);
return ret;
}
static void __exit simple_char_exit(void)
{
struct mydemo_device *dev = demo_device;
printk("removing device\n");
misc_deregister(dev->miscdev);
kfree(dev);
}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("shanks");
MODULE_DESCRIPTION("my test kernel module");
MODULE_ALIAS("simple char");
写一个makefile,如下:
BASEINCLUDE ?= /lib/modules/`uname -r`/build
misc_test-objs := my_test.o
obj-m := misc_test.o
all:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) modules;
clean:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) clean;
rm -f *.ko
执行make,这样就会生成misc_test.ko, 通过sudo insmod misc_test.ko, 这样就会加载到内核中,并在/dev下出现节点:
crw-rw-rw- 1 root root 10, 122 2月 5 19:59 /dev/my_misc_dev
再执行如下命令读写下该节点:
sudo cat /dev/my_misc_dev &
echo "test" > /dev/my_misc_dev
看下系统日志:
cat /dev/kmsg
4,1487,142245381721,-;succeeded register char device:my_misc_dev
4,1488,142296177620,-;demodrv_open: major = 10, minor = 122
4,1489,142296177640,-;demodev_read enter
4,1490,142296177643,-;demodev_read: pid = 100185, going to sleep
4,1491,142312460300,-;demodrv_open: major = 10, minor = 122
4,1492,142312460318,-;demodev_read enter
4,1493,142312460320,-;demodev_read: pid = 100191, going to sleep
4,1494,142428831428,-;demodrv_open: major = 10, minor = 122
4,1495,142428831457,-;demodrv_write enter
4,1496,142428831462,-;demodrv_write: pid=72399, actual_write =5, ppos=0, ret=0
4,1497,142428831486,-;demodev_read, pid=100191, actual_readed=5, pos=0
4,1498,142428831514,-;demodev_read enter
4,1499,142428831517,-;demodev_read: pid = 100191, going to sleep
执行 rmmod misc_test 即可卸载该驱动。
代码中涉及到的阻塞结构如下:
等待队列头,可以存放处于等待状态的任务。
struct wait_queue_head {
spinlock_t lock;
struct list_head head;
};
typedef struct wait_queue_head wait_queue_head_t;
加入等待的方法如下:
wait_event(wq_head, condition)
wait_event_freezable(wq_head, condition)
wait_event_timeout(wq_head, condition, timeout)
wait_event_interruptible(wq_head, condition)
wait_event_interruptible_timeout(wq_head, condition, timeout)
唤醒的方法如下:
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL, 1)
#define wake_up_all_locked(x) __wake_up_locked((x), TASK_NORMAL, 0)
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE)
支持IO多路复用的demo
提到多路复用,就是linux中著名的poll,epoll,select机制,在内核中对应的文件方法就是:
__poll_t (*poll) (struct file *, struct poll_table_struct *);
用户态对设备执行poll或select,设备驱动的poll方法就会被调用,poll会执行以下步骤:
- 在一个或多个等待队列中调用poll_wait, 该函数会把当前进程加到执行的等待列表中(poll_table),当请求的数据准备好后就会唤醒睡眠的进程。
- 返回监听事件,就是POLLIN或POLLOUT等掩码。
基于上述demo,如果要支持 IO多路复用,poll的实现如下:
static unsigned int demodrv_poll(struct file *file, poll_table *wait)
{
int mask = 0;
struct mydemo_private_data *data = file->private_data;
struct mydemo_device *device = data->device;
poll_wait(file, &device->read_queue, wait);
poll_wait(file, &device->write_queue, wait);
if (!kfifo_is_empty(&device->mydemo_fifo))
mask |= POLLIN | POLLRDNORM;
if (!kfifo_is_full(&device->mydemo_fifo))
mask |= POLLOUT | POLLWRNORM;
return mask;
}
网友评论