Linux 字符设备驱动程序

本文介绍了如何在Linux 5.x内核中编写字符设备驱动程序,包括字符设备驱动的基本概念、数据结构、操作函数及设备号管理。通过示例代码展示了从分配设备号、初始化字符设备、添加到内核到创建设备节点的完整流程,以及设备的删除过程。
摘要由CSDN通过智能技术生成

前段时间遇到一个功能需求,需要将内核空间的一些数据结构和接口导出给用户空间,当然这可以通过自定义文件系统、netlink等机制实现,但为了实现更一般的接口,决定通过字符设备来实现,编写字符设备驱动程序,向用户空间导出设备文件,这样用户空间程序可以使用简单的读写文件操作设备,由于Linux内核当前更新到5.x版本了,字符设备的实现较之前版本(很早了,我以前研究的2.6.x)还是有些区别,所以赶紧研究了5.x版本内核的驱动管理实现,并整理了当前内核版本的字符设备驱动编写流程。

驱动程序是操作系统很重要组成部分,往往驱动程序的稳定性和支持设备数量,是衡量操作系统性能的重要指标,在Linux中主要有 字符设备和块设备 。

字符设备:

        按照字符流的方式顺序访问的设备(串口、键盘等)

块设备:

        按照指定的数据块,随机访问数据的设备(硬盘、软盘、CD-ROM等)

使用 ls -l /dev 可以看到当前Linux中的设备列表

字符设备

 块设备

 我们通过系统调用来使用这些设备提供的功能,如 open、write、read等,系统内核最终通过调用对应设备的驱动程序来完成我们的功能调用。

  • Linux字符设备数据结构

字符设备结构体cdev

struct cdev {
	struct kobject kobj; //内核对象
	struct module *owner; //驱动程序由外部模块进入内核时,owner指驱动程序所属的内核模块实例
	const struct file_operations *ops; //包含设备功能函数的结构体
	struct list_head list; //设备链表元素,系统中的字符设备通过链表关联。
	dev_t dev; //设备号 主设备:从设备号
	unsigned int count; //设备被引用数量,当驱动程序卸载时,如果count!=0,内核会阻止卸载。
};

设备操作函数结构体

struct file_operations {
	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 *); //写设备文件
...

设备号(内核使用32位无符号整数存储 主设备号和从设备号)

typedef __kernel_dev_t		dev_t;
typedef u32 __kernel_dev_t;

字符设备驱动开发涉及到的内核函数

//内核为设备分配主设备号(可以手动分配,但不建议那样做,容易发生设备号冲突)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

//创建设备类,实际上就是导出到/sys/class
struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)

//初始化字符设备结构
void cdev_init(struct cdev *cdev, const struct file_operations *fops)

//添加字符设备到内核(实际就是将cdev添加到内核的设备链表)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)

//创建设备节点(用户空间进程udev会根据设备节点,在/dev目录创建设备文件,也可以手工在用户空间通过mkmod命令创建设备文件)
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)

//移除设备节点
void device_destroy(struct class *class, dev_t devt)

//从内核删除字符设备
void cdev_del(struct cdev *p)

//从内核删除设备类
void class_destroy(struct class *cls)

//释放注册的主从设备号
void unregister_chrdev_region(dev_t from, unsigned count)
  • 向内核注册字符设备驱动流程
  1. 分配主从设备号
  2. 初始化字符设备
  3. 添加字符设备实例到内核
  4. 创建设备节点
  • 删除已注册的字符设备流程
  1. 删除创建的设备节点
  2. 删除字符设备实例
  3. 删除注册的设备类
  4. 删除主从设备号

一个基于当前最新5.14内核的字符设备驱动程序demo

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>

//设备名称
#define MYCDEV_NAME "mycdev_simple"
//设备类名称
#define MYCDEV_CLASS "mycdev_class"
//设备数量
#define CDEV_NUM 1

//设备类
static struct class *mycdev_class;
//所有设备的数组
static struct cdev cdevs[CDEV_NUM];
static struct device devices[CDEV_NUM];

//设备号
static dev_t dev_num;


static int strLength(const char *s);
static int mycdev_open(struct inode *inode, struct file *file);
static ssize_t mycdev_read(struct file *file, char __user *buff, size_t len , loff_t *pos);
static ssize_t mycdev_write(struct file *, const char __user *buff, size_t len, loff_t *pos);
static int mycdev_release(struct inode *inode, struct file *file);

static struct file_operations op_f={
	.read=mycdev_read,
	.write=mycdev_write,
	.open=mycdev_open,
	.release=mycdev_release
};



//模块初始化函数
static int __init simple_chardev_init(void)
{
	int i,retVal;
    /*设备号*/
    dev_t curr_devnum; 

    /*请求内核分配设备号*/
    retVal=alloc_chrdev_region(&dev_num,0,CDEV_NUM,MYCDEV_CLASS);
    if(retVal!=0)
    {
    	printk("alloc_chrdev_region error %d\n",retVal);
    	return retVal;
    }

    //为字符设备创建设备类,在/sys/class中可见
    mycdev_class=class_create(THIS_MODULE,MYCDEV_CLASS);

    /*创建字符设备结构并注册到内核*/
    for(i=0;i<CDEV_NUM;i++)
    {
    	/*初始化字符设备*/
    	cdev_init(&cdevs[i],&op_f);
    	cdevs[i].owner=THIS_MODULE;

    	/*设置设备号*/
    	curr_devnum=MKDEV(MAJOR(dev_num),MINOR(dev_num)+i);
    	/*添加设备到内核*/
    	retVal=cdev_add(&cdevs[i],curr_devnum,1);
        printk("cdev_add retVal %d\n",retVal);

    	/*创建设备节点*/
    	devices[i]=*(device_create(mycdev_class,NULL,curr_devnum,NULL,MYCDEV_NAME "%d",i));
        printk("device_create %p\n",&devices[i]);
    }
	return retVal;
}

static void __exit simple_chardev_exit(void)
{
    dev_t _devnum;
    int i;

    for(i=0;i<CDEV_NUM;i++)
    {
        _devnum=MKDEV(MAJOR(dev_num),MINOR(dev_num)+i);
        device_destroy(mycdev_class, _devnum);
        cdev_del(&(cdevs[i]));
    }
    class_destroy(mycdev_class);

    unregister_chrdev_region(dev_num,CDEV_NUM);
    printk("simple_chardev_exit\n");
    return;
}


/*打开设备*/
static int mycdev_open(struct inode *inode, struct file *file)
{
	/*打印一条消息到内核日志*/
	printk("call mycdev_open\n");
	return 0;
}

/*读操作*/
static ssize_t mycdev_read(struct file *file, char __user *buff, size_t len , loff_t *pos)
{
    int count,retVal;
    const char *s1="from mycdev_simple!";
	printk("call mycdev_read\n");
	count=strLength(s1);
	retVal=copy_to_user(buff,s1,count+1);
    if(retVal==-EFAULT)
    {
        printk("mycdev_read error\n");
        return -EFAULT;
    }
	return count+1;
}

/*写操作*/
static ssize_t mycdev_write(struct file *file, const char __user *buff, size_t len, loff_t *pos)
{
	char *buffer;
    int retVal;
	buffer=NULL;

    printk("call mycdev_write\n");
    buffer = (char*)kmalloc(len, GFP_KERNEL);
    if(buffer==NULL)
    {
    	printk("kmalloc error\n");
    	return -EFAULT;
    }

    retVal=copy_from_user(buffer, buff, len);
    if(retVal==-EFAULT)
    {
        printk("mycdev_write error\n");
        return -EFAULT;
    }
    printk("recv %s\n",buffer);
    return len;
}

/*关闭文件*/
static int mycdev_release(struct inode *inode, struct file *file)
{
	printk("call mycdev_release\n");
	return 0;
}

/*计算字符串长度*/
static int strLength(const char *s)
{
	int len;
	len=0;
	while((*s)!=0)
	{
		s++;
		len++;
	}
	return len;
}


module_init(simple_chardev_init);
module_exit(simple_chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxxxxx");
MODULE_DESCRIPTION("内核描述");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值