如何利用Cdev在Linux系统中实现设备驱动开发?

Linux内核中的cdev结构体用于描述字符设备,包括设备号和操作函数等关键信息。

Linux内核字符设备驱动详解

cdev linux

在Linux操作系统的世界中,一切皆文件,无论是硬件设备还是普通文件,都可以通过统一的接口进行访问,本文将详细探讨Linux内核中的字符设备驱动程序,特别是cdev结构体的使用,通过本文,您将了解字符设备的基础知识、cdev结构体的定义和操作,以及如何在内核中实现一个简单的字符设备驱动。

一、字符设备简介

字符设备是Linux系统中的三大类设备之一(另外两类是块设备和网络设备),它们面向流数据,以字节为单位进行顺序读取或写入操作,常见的字符设备包括鼠标、键盘、串口等,与块设备不同,字符设备通常不能随机访问数据,而是按照顺序读取或写入。

二、cdev结构体解析

在Linux内核中,struct cdev结构体用于描述一个字符设备,该结构体定义在<linux/cdev.h>头文件中,其定义如下:

struct cdev {
    struct kobject kobj;         // 内嵌的内核对象
    struct module *owner;        // 拥有该字符设备的模块指针
    const struct file_operations *ops; // 默认文件操作接口
    struct list_head list;      // 用于链表管理
    dev_t dev;                  // 设备号
    unsigned int count;         // 次设备号的数量
};

1.kobject kobj

kobject是一个内核对象,用于表示驱动程序中的设备,它包含一些基本的成员函数指针和引用计数等。

2.module *owner

指向拥有该字符设备结构体的模块的指针,这是为了确保设备在模块卸载时能够被正确释放。

cdev linux

3.file_operations *ops

这是一个函数指针数组,包含了字符设备驱动提供给虚拟文件系统的接口函数,如openreadwrite等。

4.list_head list

用于将所有注册的字符设备连接成一个链表,方便管理和遍历。

5.dev_t dev

字符设备的设备号,由主设备号和次设备号组成,主设备号标识设备类型,而次设备号区分同一类型的多个设备。

6.unsigned int count

cdev linux

隶属于同一主设备号的次设备号的数量。

三、cdev结构体的操作函数

1.cdev_init()

该函数用于初始化一个cdev结构体,并建立cdev与file_operations之间的连接。

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
    memset(cdev, 0, sizeof(*cdev));
    INIT_LIST_HEAD(&cdev->list);
    kobject_init(&cdev->kobj, &ktype_cdev_default);
    cdev->ops = fops;
}

2.cdev_alloc()

动态申请一个cdev结构体内存,并进行初步的初始化。

struct cdev *cdev_alloc(void)
{
    struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
    if (p) {
        INIT_LIST_HEAD(&p->list);
        kobject_init(&p->kobj, &ktype_cdev_dynamic);
    }
    return p;
}

3.cdev_add()

向系统添加一个cdev结构体,完成字符设备的注册。

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
    int ret;
    p->dev = dev;
    p->count = count;
    ret = kobject_add(&p->kobj, &char_ktype, NULL, "%s", devname(dev));
    if (ret < 0)
        return ret;
    return 0;
}

4.cdev_del()

从系统中删除一个cdev结构体,注销字符设备。

void cdev_del(struct cdev *p)
{
    kobject_put(&p->kobj);
}

四、字符设备驱动模型

字符设备驱动模型主要包括以下几个部分:

设备号分配:静态或动态分配设备号。

cdev结构体初始化:使用cdev_init()函数初始化cdev结构体。

file_operations实现:编写具体的设备操作函数,并将函数指针赋值给file_operations结构体。

设备注册与注销:使用cdev_add()cdev_del()函数向系统注册和注销字符设备。

用户空间交互:用户程序通过设备文件访问字符设备,触发内核中的设备操作函数。

五、示例代码

以下是一个简单的字符设备驱动示例,展示了如何使用cdev结构体实现一个基本的字符设备驱动。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define MYDEVICE_NAME "mychardevice"
#define CLASS_NAME "mycharclass"
static int majorNumber;
static struct class *charClass;
static struct cdev myCdev;
static char msg[80] = "Hello, Linux kernel!";
static int deviceOpened = 0;
static int messageLen = 0;
// 定义file_operations结构体
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = deviceOpen,
    .release = deviceRelease,
    .read = deviceRead,
    .write = deviceWrite,
};
// 打开设备函数
int deviceOpen(struct inode *inode, struct file *file) {
    if (deviceOpened)
        return -EBUSY;
    deviceOpened++;
    try_module_get(THIS_MODULE);
    printk(KERN_INFO "Device opened
");
    return 0;
}
// 释放设备函数
int deviceRelease(struct inode *inode, struct file *file) {
    deviceOpened--;
    module_put(THIS_MODULE);
    printk(KERN_INFO "Device released
");
    return 0;
}
// 读取设备函数
ssize_t deviceRead(struct file *filp, char __user *buffer, size_t len, loff_t *offset) {
    int error_count = 0;
    printk(KERN_INFO "Device read request
");
    if (*offset == 0) {
        error_count = copy_to_user(buffer, msg, strlen(msg));
        *offset = strlen(msg);
    } else {
        error_count = -EFAULT; // Offset not supported yet.
    }
    return error_count ? error_count : strlen(msg);
}
// 写入设备函数
ssize_t deviceWrite(struct file *filp, const char __user *buff, size_t len, loff_t *offset) {
    int error_count = 0;
    printk(KERN_INFO "Device write request
");
    if (len > sizeof(msg))
        len = sizeof(msg);
    if (copy_from_user(msg, buff, len)) {
        printk(KERN_ALERT "Failed to copy from user space
");
        error_count = -EFAULT; // Failed to copy from user space.
    } else {
        msg[len] = '