Linux字符型设备驱动之初体验

前言

驱动总共分为字符型设备驱动,块设备驱动,网络设备驱动。对于字符型设备驱动的资料,网上比较多,《Linux Kernel Driver》这本书可以了解一下,对于学习Linux驱动有很大的帮助,当然还有很多优秀的书籍,暂不一一列举,本文简单总结了在学习字符型设备驱动的过程中遇到的问题,以及对该类驱动的理解。

框架

字符型设备

什么是字符型设备?字符型以字符(Byte/Char)为单位进行数据传输的设备,如键盘,串口等等设备,所以Linux环境编程中文件I/O进行操作的系统接口如openreadwriteclose等等,在字符型设备驱动中同样需要支持这些接口。这里会用到file_operations结构体,在后面会讲到。

程序实现

下面是一个简单字符型设备驱动程序,可以在系统注册一个字符型设备驱动,目前未实现openreadwriteclose等接口。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h> #define DRIVER_DATA_SIZE 4096
static int major_dev_index = 0; struct cnc_character_st{
struct cdev device;
u8 data[DRIVER_DATA_SIZE];
};
static struct cnc_character_st *character_dev;
//TODO
static ssize_t cnc_character_read (struct file * fd, char __user * data, size_t len, loff_t * offset){
ssize_t ret = 0;
printk("%s call\n",__func__);
return ret;
}
//TODO
static ssize_t cnc_character_write (struct file * fd, const char __user * data, size_t len, loff_t * offset){
ssize_t ret = 0;
return ret;
}
//TODO
static long cnc_character_unlocked_ioctl (struct file * fd, unsigned int data, unsigned long cmd){
long ret = 0;
return ret;
}
//TODO
static int cnc_character_open (struct inode * node, struct file * fd){
int ret = 0;
return ret;
}
//TODO
static int cnc_character_release (struct inode * node, struct file * fd){
int ret = 0;
return ret;
} static const struct file_operations cnc_character_ops = {
.owner = THIS_MODULE,
.read = cnc_character_read,
.write = cnc_character_write,
.open = cnc_character_open,
.unlocked_ioctl = cnc_character_unlocked_ioctl,
.release = cnc_character_release,
}; static int register_device(struct cnc_character_st *mdev,int major_dev_index,int minor_dev_index){ int ret = 0;
int dev_no = MKDEV(major_dev_index, minor_dev_index);
// 初始化dev
cdev_init(&mdev->device, &cnc_character_ops);
mdev->device.owner = THIS_MODULE; ret = cdev_add(&mdev->device,dev_no,1); if(ret){
printk(KERN_ERR "cdev add device failed\n");
}
return ret;
} static int unregister_device(struct cnc_character_st *mdev){
int ret= 0;
kfree(character_dev);
return ret;
} static int __init cnc_character_init(void){ int ret = 0; dev_t devno = MKDEV(major_dev_index, 0); if(major_dev_index){
ret = register_chrdev_region(devno, 1, "cnc_character");
}else{
ret = alloc_chrdev_region(&devno, 0, 1, "cnc_character");
major_dev_index = MAJOR(devno);
} if(ret < 0){
return ret;
}
character_dev = kmalloc(sizeof(struct cnc_character_st),GFP_KERNEL); if(!character_dev){
printk("%s failed malloc character_dev call\n",__func__);
ret = -ENOMEM;
goto failed;
}else{
printk("%s success malloc character_dev call\n",__func__); }
register_device(character_dev,major_dev_index,0);
return 0; failed:
unregister_chrdev_region(devno, 1);
return ret;
}
module_init(cnc_character_init); static void __exit cnc_character_exit(void){
printk("%s call\n",__func__);
unregister_device(character_dev);
}
module_exit(cnc_character_exit); MODULE_AUTHOR("zhaojunhui@cncgroup.top");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL");

cdev

linux/cdev.h中可以阅读相关字符型设备驱动的信息,其中包括cdev结构体可以做一下分析,先定位到源码做一下分析

#ifndef _LINUX_CDEV_H
#define _LINUX_CDEV_H
#include <linux/kobject.h>
#include <linux/kdev_t.h>
#include <linux/list.h>
struct file_operations;
struct inode;
struct module;
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
void cd_forget(struct inode *);
#endif

其中包括结构体cdev和cdev的一系列函数接口cdev_initcdev_alloccdev_putcdev_addcdev_delcd_forget

kobj

kobject是所有设备驱动模型的基类,而cdev可以理解为是它的派生类,这里使用了面向对象的思想,通过访问cdev中的kobj成员,就能使用kobject中所有功能。关于kobject的详细内容可以参考内核文档Documentation/kobject.txt

owner

首先明确一点的是ownerstruct module的指针变量,owner=THIS_MODULE;,这里将指针指向当前的模块,关于THIS_MODULE以及struct module的知识可以参考这篇博客

file_operations

这个结构体位于/linux/include/fs.h,代码如下。

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 (*iterate) (struct file *, struct dir_context *);
unsigned int (*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 *);
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 (*aio_fsync) (struct kiocb *, 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
};

file_operations定义了很多I/O操作接口,这里同样使用了面向对象编程的思想,每个接口可以在重新定义file_operations结构体变量的时候,重新赋于自定义功能的函数,如下,可以理解readwriteopenunlocked_ioctlrelease是对抽象函数的实现。

static const struct file_operations cnc_character_ops = {
.owner = THIS_MODULE,
.read = cnc_character_read,
.write = cnc_character_write,
.open = cnc_character_open,
.unlocked_ioctl = cnc_character_unlocked_ioctl,
.release = cnc_character_release,
};

dev_t

设备注册过程

设备的初始化在函数cnc_character_init中完成具体的功能实现,主要分为两个部分,设备号的申请和设备的注册。其中设备注册单独封装到register_device函数中。

申请设备号

dev_t devno = MKDEV(major_dev_index, 0);

if(major_dev_index){
ret = register_chrdev_region(devno, 1, "cnc_character");
}else{
ret = alloc_chrdev_region(&devno, 0, 1, "cnc_character");
major_dev_index = MAJOR(devno);
}

注册设备

character_dev = kmalloc(sizeof(struct cnc_character_st),GFP_KERNEL);

if(!character_dev){
printk("%s failed malloc character_dev call\n",__func__);
ret = -ENOMEM;
goto failed;
}else{
printk("%s success malloc character_dev call\n",__func__); }
register_device(character_dev,major_dev_index,0);

register_device

register_device中,主要用到了cdev提供的函数接口。

cdev_init初始化一个字符型设备并传入自定义的file_operations类型变量cnc_character_ops

cdev_add将初始化的字符型设备添加到内核,并分配已经申请好的设备号。

static int register_device(struct cnc_character_st *mdev,int major_dev_index,int minor_dev_index){

	int ret = 0;
int dev_no = MKDEV(major_dev_index, minor_dev_index);
// 初始化dev
cdev_init(&mdev->device, &cnc_character_ops);
mdev->device.owner = THIS_MODULE; ret = cdev_add(&mdev->device,dev_no,1); if(ret){
printk(KERN_ERR "cdev add device failed\n");
}
return ret;
}

如何构建

模块编译

使用这个Makefile

KVERS = $(shell uname -r)
# Kernel modules
obj-m += demo_character.o
# Specify flags for the module compilation.
EXTRA_CFLAGS=-g -O0
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

内核编译

Makefile

obj-$(CONFIG_DEMO_CHARACTER_DRIVER) +=demo_character.o

Kconfig

menuconfig DEMO_DRIVERS
tristate "demo drivers"
config DEMO_CHARACTER_DRIVER
tristate "the most simplest character driver"
help
character driver
endif

总结

总体上来说,字符型设备驱动框架还是相对简单的,通过这次学习加深了对cdev的认识和linux内核源码中面向对象的设计思想,但是这里还没有对devfssysfs做相应的介绍,后面继续学习这两者的区别以及总线驱动模型,总之,加油吧。

参考

https://blog.csdn.net/lucky_liuxiang/article/details/83413946

https://www.cnblogs.com/helloahui/p/3677192.html

https://blog.csdn.net/jk110333/article/details/8563647

Linux内核驱动学习(三)字符型设备驱动之初体验的更多相关文章

  1. 嵌入式Linux学习笔记(三) 字符型设备驱动--LED的驱动开发

    在成功构建了一个能够运行在开发板平台的系统后,下一步就要正式开始应用的开发(这里前提是有一定的C语言基础,对ARM体系的软/硬件,这部分有疑问可能要参考其它教程),根据需求仔细分解任务,可以发现包含的 ...

  2. linux 块设备驱动 (三)块设备驱动开发

    一: 块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:int register_blkdev(unsigne ...

  3. Linux设备驱动开发基础--阻塞型设备驱动

    1. 当一个设备无法立刻满足用户的读写请求时(例如调用read时,设备没有数据提供),驱动程序应当(缺省的)阻塞进程,使它进入等待(睡眠)状态,知道请求可以得到满足. 2. Linux内核等待队列:在 ...

  4. 字符型设备驱动程序-first-printf以及点亮LED灯(一)

    学习使用 Linux 的  字符型设备驱动 来 进行 . 学习地址:http://edu.51cto.com/lesson/id-25710.html 第一步: 首先写 三个函数 ,2017年5月17 ...

  5. linux设备驱动归纳总结(三):1.字符型设备之设备申请【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-59416.html linux设备驱动归纳总结(三):1.字符型设备之设备申请 操作系统:Ubunru ...

  6. 【Linux开发】linux设备驱动归纳总结(三):1.字符型设备之设备申请

    linux设备驱动归纳总结(三):1.字符型设备之设备申请 操作系统:Ubunru 10.04 实验平台:S3C2440 + linux2.6.29内核 注:在今后驱动程序的学习中经常需要查看内核源代 ...

  7. linux设备驱动归纳总结(三):2.字符型设备的操作open、close、read、write【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-59417.html linux设备驱动归纳总结(三):2.字符型设备的操作open.close.rea ...

  8. 【Linux开发】linux设备驱动归纳总结(三):2.字符型设备的操作open、close、read、write

    linux设备驱动归纳总结(三):2.字符型设备的操作open.close.read.write 一.文件操作结构体file_operations 继续上次没讲完的问题,文件操作结构体到底是什么东西, ...

  9. 字符型设备驱动程序-first-printf以及点亮LED灯(三)

    根据  字符型设备驱动程序-first-printf以及点亮LED灯(二) 学习 修改函数 中的printf 为 printk. #include <linux/module.h> /* ...

随机推荐

  1. 如何用python爬虫从爬取一章小说到爬取全站小说

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取http ...

  2. stand up meeting 12-3

    因为前后端在参数传递定义不清晰的原因,今天士杰和国庆采用了pair programming的方法,在一台电脑前工作了四十分钟,明确了请求questionpool,请求question,请求rank d ...

  3. [转+自]关于PHP7的新特性(涉及取反和disabled_functions绕过)

    PHP7和PHP5上的安全区别 preg_replace()不再支持/e修饰符 利用\e修饰符执行代码的后门大家也用了不少了,具体看官方的这段描述: 如果设置了这个被弃用的修饰符, preg_repl ...

  4. redis: 主从复制和哨兵模式(十三)

    redis 主从复制 最低要求是一主二从(一个主机和两个从机) 主机才能写 从机只能读 只要从机连接到主机 数据就会全量复制到从机 环境配置(同一台机器) 1:配置文件 redis.conf配置如下: ...

  5. 天池Docker学习赛笔记

    容器的基本概念 什么是容器? 容器就是一个视图隔离.资源可限制.独立文件系统的进程集合.所谓"视图隔离"就是能够看到部分进程以及具有独立的主机名等:控制资源使用率则是可以对于内存大 ...

  6. Scala的Higher-Kinded类型

    Scala的Higher-Kinded类型 Higher-Kinded从字面意思上看是更高级的分类,也就是更高一级的抽象.我们先看个例子. 如果我们要在scala中实现一个对Seq[Int]的sum方 ...

  7. 在线图片资源转换成Base64格式

    function getBase64Image(img) { var canvas = document.createElement("canvas"); canvas.width ...

  8. Python3的日期和时间

    2019独角兽企业重金招聘Python工程师标准>>> python 中处理日期时间数据通常使用datetime和time库 因为这两个库中的一些功能有些重复,所以,首先我们来比较一 ...

  9. JavaScript Array every()&some()&reduce()方法

    every()方法测试数组的所有元素是否都通过了指定函数的测试. // 每一项都要满足条件才会返回true,只要有一项不满足返回false var arr = [1, 2, 3, 4]; let bl ...

  10. 常用的CSS小技巧

    实际开发过程中会遇到一些需要用CSS小技巧处理的布局问题,现在分享几个个人工作中遇到的小问题和解决方案. 1.inline元素间的空白间隙 这里要介绍一个神器font-size:0. 如果你写了个列表 ...