美文网首页
Linux 内核之字符设备驱动

Linux 内核之字符设备驱动

作者: android小奉先 | 来源:发表于2023-02-04 20:43 被阅读0次

本篇介绍

本篇介绍下如何写字符设备的驱动程序。

支持阻塞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会执行以下步骤:

  1. 在一个或多个等待队列中调用poll_wait, 该函数会把当前进程加到执行的等待列表中(poll_table),当请求的数据准备好后就会唤醒睡眠的进程。
  2. 返回监听事件,就是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;
}

相关文章

网友评论

      本文标题:Linux 内核之字符设备驱动

      本文链接:https://www.haomeiwen.com/subject/cnyehdtx.html