字符设备驱动是Linux驱动中最基本的一类设备驱动,能够像字节流一样被访问的设备,可按照字节进行读写操作设备,读写数据是分先后顺序的,字符设备驱动程序一般可以实现open、close、read、write等系统调用,应用层可以通过设备节点来访问。
逻辑图
字符设备驱动
驱动源码
#include <linux/init.h> //宏定义声明__init和__exit
#include <linux/module.h> //声明module_init和module_exit
#include <linux/fs.h> //声明file_operations结构体表示虚拟文件系统文件操作API
#include <linux/cdev.h> //声明字符设备类型cdev
#include <linux/device.h> //声明设备节点类型class和设备类型device
#include <linux/types.h> //声明设备号类型dev_t
#include <linux/uaccess.h> //声明函数copy_from_user和copy_to_user
#include <linux/io.h> //声明函数ioremap和iounmap
#define DEMO_NAME "c_dev" //设备名
#define DEMO_CONUT 1 //次设备号数量
struct mkdev_t {
struct cdev *c_dev; //字符设备cdev
struct class *class; //设备节点类
struct device *device; //设备实例
dev_t dev; //设备号
int major; //主设备号
int minor; //次设备号
};
static struct mkdev_t mkdev; //自定义的字符设备结构体
static char readbuf[100]; //读缓冲区
static char writebuf[100]; //写缓冲区
static char kerneldata[] = {"kernel data!"};
static int c_drv_open(struct inode *inode, struct file *filp)
{
struct mkdev_t *dev = (struct mkdev_t *)filp->private_data; //获取设备私有数据
int major = MAJOR(inode->i_rdev); //从设备号提取主设备号
int minor = MINOR(inode->i_rdev); //从设备号提取次设备号
printk("%s: major=%d, minor=%d\n", __func__, major, minor);
return 0;
}
static int c_drv_release(struct inode *inode, struct file *filp)
{
printk("%s: enter\n", __func__ );
return 0;
}
static ssize_t c_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
//向用户空间发送数据
memcpy(readbuf, kerneldata, sizeof(kerneldata));
int ret = copy_to_user(buf, readbuf, count);
if(!ret) {
printk("kernel senddata ok!\n");
} else {
printk("kernel senddata failed!\n");
}
printk("%s: enter\n", __func__ );
return 0;
}
static ssize_t c_drv_write(struct file *filp, char __user *buf, size_t count,loff_t *ppos)
{
//接收用户空间的数据并打印
int ret = copy_from_user(writebuf, buf, count);
if(!ret) {
printk("kernel getdata ok: %s\n", writebuf);
} else {
printk("kernel getdata failed!\n");
}
printk("%s: enter\n", __func__ );
return 0;
}
static const struct file_operations c_drv_fops = {
.owner = THIS_MODULE,
.open = c_drv_open,
.release = c_drv_release,
.read = c_drv_read,
.write = c_drv_write,
};
static int __init c_drivers_init(void)
{
int ret;
/*1、申请设备号*/
if(mkdev.major) { //若指定主设备后则静态分配
mkdev.dev = MKDEV(mkdev.major, 0); //将主次设备号拼凑成设备号
ret = register_chrdev_region(mkdev.dev, DEMO_CONUT, DEMO_NAME); //静态申请设备号
} else {
ret = alloc_chrdev_region(&mkdev.dev, 0, DEMO_CONUT, DEMO_NAME); //动态申请设备号 (0表示从0开始动态申请)
mkdev.major = MAJOR(mkdev.dev); //从设备号提取主设备号
mkdev.minor = MINOR(mkdev.dev); //从设备号提取次设备号
}
if(ret) {
printk("failed to allocate char device region\n");
return ret;
}
printk("successed register char device: %S\n", DEMO_NAME);
printk("Major number = %d, minor number = %d\n", mkdev.major, mkdev.minor);
/*2、创建并注册字符设备cdev*/
mkdev.c_cdev = cdev_alloc(); //动态申请内存cdev对象
if(!mkdev.c_cdev) {
printk("cdev alloc failed\n");
goto unregister_chrdev;
}
cdev_init(mkdev.c_cdev, &c_drv_fops); //初始化cdev成员,并建立与file_operations的联系
ret = cdev_add(mkdev.c_cdev, mkdev.dev, DEMO_CONUT); //注册cdev设备对象,添加到系统字符设备列表
if(ret) {
printk("cdev add failed\n");
goto cdev_fail;
}
/*3、创建设备节点类*/
mkdev.class = class_create(THIS_MODULE, DEMO_NAME);
if(IS_ERR(mkdev.class)) {
return PTR_ERR(mkdev.class);
}
/*4、创建设备实例*/
mkdev.device = device_create(mkdev.class, NULL, mkdev.dev, NULL, DEMO_NAME);
if(IS_ERR(mkdev.device)) {
return PTR_ERR(mkdev.device);
}
/*5、错误处理*/
cdev_fail:
cdev_del(mkdev.c_cdev); //注销cdev设备对象
unregister_chrdev:
unregister_chrdev_region(mkdev.dev, DEMO_CONUT); //释放设备号
ratura ret;
}
static void __exit c_drivers_exit(void)
{
printk("removing device\n");
device_destory(mkdev.class, mkdev.device); //注销设备实例
class_destory(mkdev.class); //注销设备节点类
cdev_del(mkdev.c_cdev); //注销cdev设备对象
unregister_chrdev_region(mkdev.dev, DEMO_CONUT); //释放设备号
}
module_init(c_drivers_init);
module_exit(c_drivers_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxxx");
MODULE_DESCRIPTION("cdev test driver");
MODULE_VERSION("1.0.0");
驱动Makefile
SRC_PATH := $(shell pwd)
KERNEL := $(shell uname -r)
BUILD_PATH := /usr/src/linux-headers-$(KERNEL)
obj-m := c_driver.o
build: kernel_modules
kernel_modules:
make -C $(BUILD_PATH) M=$(SRC_PATH) modules
clean:
make -C $(BUILD_PATH) M=$(SRC_PATH) clean
demo
demo源码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h">
#include <fcntl.h>
#include <string.h>
static char usrdata[] = {"usr data!"};
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
char readbuf[100], writebuf[100];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("Can't open file %s\r\n", filename);
return -1;
}
if(atoi(argv[2]) == 1){
/* 从驱动文件读取数据 */
ret = read(fd, readbuf, 50);
if(ret < 0){
printf("read file %s failed!\r\n", filename);
}else{
/* 读取成功,打印出读取成功的数据 */
printf("read data:%s\r\n",readbuf);
}
}
if(atoi(argv[2]) == 2){
/* 向设备驱动写数据 */
memcpy(writebuf, usrdata, sizeof(usrdata));
ret = write(fd, writebuf, 50);
if(ret < 0){
printf("write file %s failed!\r\n", filename);
}
}
/* 关闭设备 */
ret = close(fd);
if(ret < 0){
printf("Can't close file %s\r\n", filename);
return -1;
}
return 0;
}
demo的Makefile
CFLAG=-I/usr/include/sys
LDFLAG=-ldrm
SRC=demo.c
TARGET=demo
all:
gcc -g -O2 $(SRC) -o $(TARGET) $(CTLAG) $(LDFLAG)
clean:
rm -rf $(TARGET) *.o
测试
test@ubuntu:~$ cat /dev |grep cdev
100 0 cdev
test@ubuntu:~$ ./demo /dev/cdev 1 //读操作
test@ubuntu:~$ ./demo /dev/cdev 2 //写操作
参考资料:https://gitee.com/xiebaoyou/dim-sum