前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux 内核之字符设备驱动

Linux 内核之字符设备驱动

作者头像
一只小虾米
发布2023-02-10 15:40:19
4.8K0
发布2023-02-10 15:40:19
举报
文章被收录于专栏:Android点滴分享Android点滴分享

本篇介绍

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

支持阻塞IO的驱动demo

Linux 上的设备类型可以大概分为以下几种:

  • 字符设备:以字节为单位传输,传输率低,不支持随机访问,常见的设备有鼠标,键盘,触摸屏等
  • 块设备: 以块位单位传输,常见的就是磁盘
  • 网络设备:涉及网络协议的设备 本篇先看字符设备的内容。 先看下字符设备的结构
代码语言:javascript
复制
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;

设备号相关的操作方法如下:

代码语言:javascript
复制
#define MAJOR(dev)  ((dev)>>8)
#define MINOR(dev)  ((dev) & 0xff)
#define MKDEV(ma,mi)    ((ma)<<8 | (mi))

cdev的操作方法如下:

代码语言:javascript
复制
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 数据结构

内核也提供了动态分配设备号的方法:

代码语言:javascript
复制
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的结构:

代码语言:javascript
复制
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 内核将一些不符合预先确定的字符设备划分为杂项设备,使用的数据结构如下;

代码语言:javascript
复制
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;
};

主要的操作函数如下:

代码语言:javascript
复制
extern int misc_register(struct miscdevice *misc);
extern void misc_deregister(struct miscdevice *misc);

接下来我们看一个misc设备的例子。

代码语言:javascript
复制
#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,如下:

代码语言:javascript
复制
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下出现节点:

代码语言:javascript
复制
crw-rw-rw- 1 root root 10, 122  2月  5 19:59 /dev/my_misc_dev

再执行如下命令读写下该节点:

代码语言:javascript
复制
sudo cat /dev/my_misc_dev &
echo "test" > /dev/my_misc_dev 

看下系统日志:

代码语言:javascript
复制
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 即可卸载该驱动。 代码中涉及到的阻塞结构如下: 等待队列头,可以存放处于等待状态的任务。

代码语言:javascript
复制
struct wait_queue_head {
    spinlock_t      lock;
    struct list_head    head;
};
typedef struct wait_queue_head wait_queue_head_t;

加入等待的方法如下:

代码语言:javascript
复制
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)

唤醒的方法如下:

代码语言:javascript
复制
#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机制,在内核中对应的文件方法就是:

代码语言:javascript
复制
__poll_t (*poll) (struct file *, struct poll_table_struct *);

用户态对设备执行poll或select,设备驱动的poll方法就会被调用,poll会执行以下步骤:

  1. 在一个或多个等待队列中调用poll_wait, 该函数会把当前进程加到执行的等待列表中(poll_table),当请求的数据准备好后就会唤醒睡眠的进程。
  2. 返回监听事件,就是POLLIN或POLLOUT等掩码。

基于上述demo,如果要支持 IO多路复用,poll的实现如下:

代码语言:javascript
复制
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;
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-02-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本篇介绍
  • 支持阻塞IO的驱动demo
  • 支持IO多路复用的demo
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档