字符设备驱动API

文章详细介绍了在Linux内核中如何注册和管理字符设备驱动,包括使用`register_chrdev`进行一次性批量注册,以及使用`cdev`结构体进行分步注册的方法。此外,还讨论了动态申请设备号、创建设备节点以及自动创建设备节点的过程,提供了读写操作的示例代码,并展示了编译和测试驱动的流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字符设备驱动框架

注册字符设备

批量注册设备

int register_chrdev(unsigned int major, const char *name,
      const struct file_operations *fops)
功能:注册字符设备驱动
参数:
    @major:主设备号 (一次申请了256个设备,次设备号的范围是0-255)
        major > 0 :静态指定设备号
        major = 0 :让系统自动申请
    @name:驱动的名字
        cat /proc/devices 
        Character devices:
          1  mem
          4  /dev/vc/0
          4  tty
          4  ttyS
          5  /dev/tty
          5  /dev/console
          |         |
         主设备号 名字
   ·@fops:操作方法结构体
        struct file_operations {
            int (*open) (struct inode *, struct file *);
            ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
            ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
            int (*release) (struct inode *, struct file *);
        }
返回值:
  major > 0 成功返回0,失败返回错误码
  major = 0 成功返回主设备号,失败返回错误码
    
void unregister_chrdev(unsigned int major, const char *name)
功能:注销字符设备
参数:
    @major:主设备号
	@name:名字
返回值:

register_chardev 一次申请256个次设备号,由于设备号的数量有限,所以在很多需要大量设备区驱动的时候就不能使用这个函数。

分步注册设备

更多的时候是用多少申请多少,避免无设备号可用

#include <linux/cdev.h>
#include <linux/slab.h>
1.分配对象
    struct cdev {
        struct module *owner;             //THIS_MODULE
        const struct file_operations *ops; //操作方法结构体
        struct list_head list;            //构成链表
        dev_t dev;                        //设备号
        unsigned int count;               //设备的个数
 };
 
 方法1struct cdev cdev; //定义结构体变量
 方法2struct cdev *cdev: //定义结构体指针
2.对象初始的函数
 struct cdev *cdev_alloc(void)
    功能:为cdev的结构体指针分配内存
    参数:
        @无
 返回值:成功返回结构体指针,失败返回NULL
    
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    功能:初始化cdev
    参数:
        @cdev:cdev的结构体指针
        @fops:操作方法结构体
 返回值:无
 
 int register_chrdev_region(dev_t from, unsigned count, const char *name)
    功能:静态指定设备号
 	参数:
      @from:设备号的起始的值
      @count:设备的个数
      @name:设备的名字,可以通过cat /proc/devices
 返回值:成功返回0,失败返回错误码
 
 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
   const char *name)
 功能:动态申请设备号
 参数:
    @dev:申请到的设备号
    @baseminor:次设备号的起始值
    @count:设备的个数
    @name:设备的名字,可以通过cat /proc/devices
 返回值:成功返回0,失败返回错误码
    
3.注册
 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
 功能:字符设备驱动的注册
 参数:
    @p:cdev的结构体指针
    @dev:设备号
    @count:设备的个数
 返回值:成功返回0,失败返回错误码
------------------------------------------------------------------------------------
4.注销
    void cdev_del(struct cdev *p)
 功能:注销字符设备驱动
 参数:
    @p:cdev的结构体指针
 返回值:无
 
 void unregister_chrdev_region(dev_t from, unsigned count)
 功能:归还设备号
 参数:
    @from:设备号起始的值
    @count:个数
 返回值:无

 void kfree(void *p)
 功能:释放cdev_alloc申请的内存
 参数:
    @p:cdev的结构体指针
 返回值:无 

创建设备节点

使用shell命令创建设备节点

sudo mknod  /dev/mycdev c 240 0
    
mknod      :创建设备节点的命令
/dev/mycdev:创建的设备节点的路径及名字(路径是任意的,习惯上放在/dev/)
c/b        : c 字符设备   b 块设备
240        : 主设备号 (驱动分配的主设备号)
0          : 次设备号 {0-255}

这么做每次都要手动创建设备节点,比较麻烦

自动创建设备节点

#include <linux/device.h>

struct class * class_create(owner, name);
功能:向上提交目录名
参数:
    @owner:THIS_MODULE //(编译器使用的一个宏)
    @name:目录名
返回值:成功返回结构体指针,失败返回错误码指针
    
struct device *device_create(struct class *class, struct device *parent,
        dev_t devt, void *drvdata, const char *fmt, ...)
功能:向上提交节点信息
参数:
	@class :class的结构体指针
    @parent:一般写为NULL
    @devt  :设备号
          MKDEV(major,minor); //根据主次设备号合成设备号
          MAJOR(dev)          //根据设备号获取到主设备号
          MINOR(dev)          //根据设备号得到次设备号
    @drvdata:NULL
    @fmt,...:设备节点的名字 "myled%d",i
返回值:成功返回结构体指针,失败返回错误码指针

------------------------------------------------------------------------------------
void device_destroy(struct class *class, dev_t devt)
功能:销毁设备节点信息
参数:
 	@class :class的结构体指针
    @devt  :设备号
返回值:无
        
void class_destroy(struct class *cls)
功能:销毁向上提交的目录信息 
参数:
 	@class :class的结构体指针
返回值:无

例子

使用分步注册设备驱动,自动创建设备节点,的读写实例

驱动 rw.c

#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>

#define CNAME "rw"
dev_t major;
dev_t minor;
struct  cdev *cdev;
struct device *dev;
struct class *cls;
int count = 3;
char kbuf[128];


ssize_t myread (struct file * file, char __user * ubuf, size_t size, loff_t * offs)
{
	printk("%s:%s:%d\r\n", __FILE__, __func__, __LINE__);
	if(size > 127)
		size = 127;
	copy_to_user(ubuf, kbuf, size);
	return 0;
}

ssize_t mywrite (struct file * file, const char __user * ubuf, size_t size, loff_t * offs)
{
	printk("%s:%s:%d\r\n", __FILE__, __func__, __LINE__);
	if(size > 127)
		size = 127;
	copy_from_user(kbuf, ubuf, size);
	return 0;
}

int myclose (struct inode * ino, struct file * file)
{
	printk("%s:%s:%d\r\n", __FILE__, __func__, __LINE__);
	return 0;
}

int myopen (struct inode * ino, struct file * file)
{
	printk("%s:%s:%d\r\n", __FILE__, __func__, __LINE__);
	return 0;
}

const struct file_operations fops  = {
	.open = myopen,
	.release = myclose,
	.read = myread,
	.write = mywrite,
};

static int __init rw_init(void)
{
	dev_t devno;
	int ret = 0, i;
	//1. 分配对象
	cdev = cdev_alloc();
	if(cdev == NULL)
	{
		printk("alloc cdev error\r\n");
		ret = -ENOMEM;
		goto ERR1;
	}
	//2. 对象初始化
	cdev_init(cdev, &fops);

	//3. 申请设备号,使用动态申请
	ret = alloc_chrdev_region(&devno, minor, count, CNAME);
	if(ret)
	{
		printk("request devno error\r\n");
		goto ERR2;
	}
	major = MAJOR(devno);
	minor = MINOR(devno);
	
	//4. 注册
	ret = cdev_add(cdev, MKDEV(major, minor), count);
	if(ret)
	{
		printk("register chardev error\r\n");
		goto ERR3;
	}

	//5. 自动创建节点
	cls = class_create(THIS_MODULE, CNAME);
	if(IS_ERR(cls))
	{
		printk("Create class error\r\n");
		ret = PTR_ERR(cls);
		goto ERR4;
	}

	for(i = 0; i < count; i++)
	{
		dev = device_create(cls, NULL, MKDEV(major, i), NULL, "rw%d", i);
		if(IS_ERR(dev))
		{
			printk("device create error \r\n");
			ret = PTR_ERR(dev);
			goto ERR5;
		}
	}
	return 0;

ERR5:
	for(--i; i >= 0; i--)
	{
		device_destroy(cls, MKDEV(major, i));
	}
	class_destroy(cls);

ERR4:
	cdev_del(cdev);

ERR3:
	unregister_chrdev_region(MKDEV(major, minor), count);

ERR2:
	kfree(cdev);

ERR1:
	return ret;
}

static void __exit rw_exit(void)
{
	int i;
    for (i = 0; i < count; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);

    cdev_del(cdev);

    unregister_chrdev_region(MKDEV(major, minor), count);

    kfree(cdev);
}

module_init(rw_init);
module_exit(rw_exit);
MODULE_LICENSE("GPL");

Makefile

使用的是柚木派-v851s

# 设置模块名
modname?=demo

# 设置交叉编译器
CROSS_COMPILE:=/home/linux/tina-v853-open/tina-v853-open/prebuilt/rootfsbuilt/arm/toolchain-sunxi-musl-gcc-830/toolchain/bin/arm-openwrt-linux-

# 设置内核源码路径
KERNELDIR:=/home/linux/tina-v853-open/tina-v853-open/kernel/linux-4.9

# 模块需要的源文件列表
obj-m := $(modname).o

build: kernel_modules

kernel_modules:
	make ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modules

# 清理编译结果
clean:
	make -C $(KERNELDIR) M=$(PWD) clean

编译命令为:

make modname=rw

测试代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>


int main(int argc, const char* argv[])
{
    int fd;
    char buf[128] = { 0 };
    if ((fd = open("/dev/rw1", O_RDWR)) == -1) //使用了rw节点,驱动创建了rw0-2三个节点
        printf("open error");

    printf("input > \r\n");
    fgets(buf, sizeof(buf), stdin);
    buf[strlen(buf) - 1] = '\0';

    write(fd, buf, sizeof(buf));

    memset(buf,0,sizeof(buf));
    read(fd, buf, sizeof(buf));
    printf("buf = %s\n",buf);

    close(fd);
    return 0;
}

运行截图

ar buf[128] = { 0 };
if ((fd = open(“/dev/rw1”, O_RDWR)) == -1) //使用了rw节点,驱动创建了rw0-2三个节点
printf(“open error”);

printf("input > \r\n");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';

write(fd, buf, sizeof(buf));

memset(buf,0,sizeof(buf));
read(fd, buf, sizeof(buf));
printf("buf = %s\n",buf);

close(fd);
return 0;

}


### 运行截图

![image-20230630101607871](https://img-blog.csdnimg.cn/img_convert/619466f1aca44ef69f8b1e9b3a9b7d5d.png)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值