Linux驱动开发笔记(四):设备驱动介绍、熟悉杂项设备驱动和ubuntu开发杂项设备Demo
前言
驱动的开发需要先熟悉基本概念类型,本篇讲解linux杂项设备基础,还是基于虚拟机ubuntu去制作驱动,只需要虚拟机就可以尝试编写注册杂项设备的基本流程。
linux三大设备驱动
- 字符设备:IO的传输过程是以字符为单位的,没有缓冲,比如I2C(SDA、SCL),SPI(MISO、MOSI、SCLK、CS)。
- 块设备:IO的传输过程是以块为单位的,跟存储相关的都属于块设备,比如tf卡,sd卡。
- 网络设备:IO的传输以socket套接字来访问的。
杂项设备
- 杂项设备是属于字符设备,可以自动生成设备节点,设备节点位于/dev/目录下,是设备名称,如/dev/ttyS9等。
- 主设备号相同,统一为10,次设备号不同,主设备相同可以节省内核资源。
通过下列指令,可以查看系统杂项设备
cat /proc/misc
在虚拟机上测试,查看杂项:

- 设备号分为主设备号和次设备号,主设备号是唯一的,次设备号不一定唯一。
通过下列指令,可以查看系统主设备号:
cat /proc/devices

杂项设备描述结构体
ubuntu来说,自带的/usr/src下的就是内核的头文件。
cd /usr/src/linux-headers-4.18.0-15
vi include/linux/miscdevice.h
定位到之前ubuntu自带的内核头文件下:


查看到杂项设备的结构体:
struct miscdevice {
int minor; // 次设备号
const char *name; // 设备节点名称(如/dev/ttyS8,则ttyS是名称)
const struct file_operations *fops; // 文件操作集(非常重要)
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
(注意:没打注释的,一般不管)
杂项设备文件操作集
cd /usr/src/linux-headers-4.18.0-15
vi include/linux/fs.h
搜索到(vi则直接使用“/”):

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 *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*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 *);
unsigned long mmap_supported_flags;
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 (*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 (*setfl)(struct file *, unsigned long);
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
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
u64);
} __randomize_layout;
例如read函数,那么就是打开驱动使用系统read,打开这个设备驱动的句柄,那么久会调用read函数,其他的以此类推,还比较好理解。
以我们一个registerHelloWorld为例子,来简单说明。
驱动编写空模板准备
首先复制之前的hello world的驱动,改个名字为:registerMiscDev:
cd ~/work/drive
cp -arf hellowolrd registerMiscDev

cd registerMiscDev/
rm *.ko *.o *.order *.symvers
这里删除起来麻烦,修改makefile,添加clean:

然后测试一下:

继续修改源码文件名称:
mv helloworld.c registerMiscDev.c
修改完如下:

然后修改makefile里面的(obj-m模块名称改下),模板准备好了

下面基于registerMiscDev.c文件进行注册杂项设备,在修改.c文件:

#include <linux/init.h>
#include <linux/module.h>
static int registerMiscDev_init(void)
{
// 在内核里面无法使用基础c库printf,需要使用内核库printk
printk("Hello, I’m hongPangZi, registerMiscDev_init\n");
return 0;
}
static void registerMiscDev_exit(void)
{
printk("bye-bye!!!\n");
}
MODULE_LICENSE("GPL");
module_init(registerMiscDev_init);
module_exit(registerMiscDev_exit);
杂项设备注册流程Demo
步骤一:填充miscdevice结构体
在编写驱动的时候,代码中填充信息结构体。
添加头文件miscdevice.h
#include <linux/miscdevice.h>
#include <linux/fs.h>

然后填充杂项设备结构体:

(注意:开始为“.”,结束为“,”,最后一行习惯加“,”了,这样可以全部统一复制啥的,省的加没加的)
struct miscdevice misc_dev {
.minor = MISC_DYNAMIC_MINRO, // 这个宏是动态分配次设备号,避免冲突
.name = "register_hongPangZi_misc, // 设备节点名称
.fops = misc_fops, // 这个变量记住,自己起的,步骤二使用
}

步骤二:填充file_operations结构体
在编写驱动的时候,代码中填充文件操作结构体。

struct file_operations misc_fops {
.owner = THIS_MODULE
}

步骤三:注册杂项设备并生成设备节点
注册到内核:
static int registerMiscDev_init(void)
{
// 在内核里面无法使用基础c库printf,需要使用内核库printk
printk("Hello, I’m hongPangZi, registerMiscDev_init\n");
int ret = 0;
ret = misc_register(misc_dev);
if(ret < 0)
{
printk("Failed to misc_register(misc_dev)\n");
return -1;
}
return 0;
}

有注册就有注销:
static int registerMiscDev_init(void)
{
// 在内核里面无法使用基础c库printf,需要使用内核库printk
printk("Hello, I’m hongPangZi, registerMiscDev_init\n");
int ret = 0;
ret = misc_register(&misc_dev);
if(ret < 0)
{
printk("Failed to misc_register(misc_dev)\n");
return -1;
}
return 0;
}

完整的文件源码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
struct file_operations misc_fops = {
.owner = THIS_MODULE,
};
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR, // 这个宏是动态分配次设备号,避免冲突
.name = "register_hongPangZi_misc", // 设备节点名称
.fops = &misc_fops, // 这个变量记住,自己起的,步骤二使用
};
static int registerMiscDev_init(void)
{
// 在内核里面无法使用基础c库printf,需要使用内核库printk
printk("Hello, I’m hongPangZi, registerMiscDev_init\n");
int ret = 0;
ret = misc_register(&misc_dev);
if(ret < 0)
{
printk("Failed to misc_register(&misc_dev)\n");
return -1;
}
return 0;
}
static void registerMiscDev_exit(void)
{
misc_deregister(&misc_dev);
printk("bye-bye!!!\n");
}
MODULE_LICENSE("GPL");
module_init(registerMiscDev_init);
module_exit(registerMiscDev_exit);
步骤四:编译make
make
直接在驱动工程目录编译:

下面这个警告,实际上定义要在任何使用函数之前:

修改下:


编译成功

步骤五:加载卸载驱动测试
将驱动拷贝到开发板或者目标系统,然后使用加载指令:
sudo insmod registerMiscDev.ko
会打印入口加载的printk输出。

出现问题可能原因一是内核编译使用的编译器和模块使用的编译器版本不一致。ubuntu中printk终端打入内核日志消息了,可以使用dmesg进行查看:
dmesg

然后查看是否加入了杂项设备节点:

然后注销:
sudo rmmod registerMiscDev.ko

跟随着,结点消失了:

入坑
入坑一:编译报错,结构体之后未加分号
问题
编译错误,结构体后面加分号
解决
加分号,脑袋有点蒙

入坑二:编译错误,文件操作指针问题
问题

解决
这是写错了,是指针,需要加取地址&。
Linux驱动开发笔记(四):设备驱动介绍、熟悉杂项设备驱动和ubuntu开发杂项设备Demo的更多相关文章
- Django开发笔记四
Django开发笔记一 Django开发笔记二 Django开发笔记三 Django开发笔记四 Django开发笔记五 Django开发笔记六 1.邮箱激活 users app下,models.py: ...
- OsgEarth开发笔记(一):Osg3.6.3+OsgEarth3.1+vs2019x64开发环境搭建(上)
前言 OSG研究之后,做地理GIS显示了地球:<项目实战:Qt+OSG教育学科工具之地理三维星球>,这一文章是基于OSG做的,而基于OsgEarth是可以进一步对地球进行深度操作,所以 ...
- OsgEarth开发笔记(三):Osg3.6.3+OsgEarth3.1+vs2019x64开发环境搭建(下)
前言 上一篇编译了proj6.2.0.gdal3.2.1,本篇继续. OsgEarth编译过程简介 OsgEarth的编译,是基于Osg和OsgEarth结合在一起的,先要编译Osg,然后 ...
- Android移动APP开发笔记——最新版Cordova 5.3.1(PhoneGap)搭建开发环境
引言 简单介绍一下Cordova的来历,Cordova的前身叫PhoneGap,自被Adobe收购后交由Apache管理,并将其核心功能开源改名为Cordova.它能让你使用HTML5轻松调用本地AP ...
- 地图开发笔记(一):百度地图介绍、使用和Qt内嵌地图Demo
前言 Qt在地图方面的研发. 百度地图 介绍 百度的地图分为多个开发,都是在线的(离线的需要自己提取,本篇解说在线地图). 百度地图JavaScript API支持HTTP和HTTPS, ...
- 张高兴的 Windows 10 IoT 开发笔记:使用 Lightning 中的软件 PWM 驱动 RGB LED
感觉又帮 Windows 10 IoT 开荒了,所以呢,正儿八经的写篇博客吧.其实大概半年前就想写的,那时候想做个基于 Windows 10 IoT 的小车,但树莓派原生不支持 PWM 啊.百度也搜不 ...
- STM32F412应用开发笔记之八:迪文串口屏显示驱动
迪文的显示屏使用起来比较方便,其使用串口通讯,即可支持RS232,又可以支持TTL电平.在NUCLEO-F412ZG实验板上,USART2已经引到了CN9上,我们就利用USART2来实现与迪文串口屏的 ...
- Qt开发笔记:OpenSSL库介绍、windows上mingw32版本的OpenSSL编译模块化
前言 Windows上mingw32版本的openssl的编译是属于比较棘手的,OpenSSL本身不提供支持.. OpenSSL 介绍 OpenSSL是一个开放源代码的软件库包,应用程序可 ...
- Linux内核分析 笔记四 系统调用的三个层次 ——by王玥
一.知识点总结 (一)用户态.内核态和中断 1.内核态:在高的执行级别下,代码可以执行特权指令,访问任意的物理地址,这时的CPU就对应内核态 2.用户态:在低级别的指令状态下,代码 只能在级别允许的特 ...
- Gif开发笔记(一):gif介绍、编译和工程模板
前言 实现gif图片的解码和生成. Gif 简介 GIF格式的名称是Graphics Interchange Format的缩写,是在1987年由Compu Serve公司为了填补跨平 ...
随机推荐
- [转帖]Nginx反向代理中使用proxy_redirect重定向url
https://www.cnblogs.com/kevingrace/p/8073646.html 在使用Nginx做反向代理功能时,有时会出现重定向的url不是我们想要的url,这时候就可以使用pr ...
- [转帖]Welcome to the di-kafkameter wiki!
https://github.com/rollno748/di-kafkameter/wiki#producer-elements Introduction DI-Kafkameter is a JM ...
- [转帖]Linux IO调度之队列、队列深度
有关数据结构 请求队列:struct request_queue 请求描述符:struct request 队列深度 可以在端口队列中等待IO请求数量: 具体代表其值的是request_queue的成 ...
- [转帖]jemalloc内存分配算法
https://www.cnblogs.com/xiaojiesir/p/15450732.html jemalloc内存分配算法简介 jemalloc 是由 Jason Evans 在 FreeBS ...
- K8S 使用loki 监控 应用日志的搭建办法
1. 背景 这几天一直在用k8s部署分SU的测试环境,开发反馈看日志比较麻烦. 昨天晚上在家里本来想搭建ELK 发现比较重, 又说有一个比较轻量级的 loki 可以实现使用grafana进行监控和查看 ...
- charles如何抓取https请求
我们都知道charles下载安装后只能抓取http请求,要想抓取https请求需要下载安装证书 下面介绍pc端和移动端的配置方法 一.pc端(win) 1.打开charles,点击help>SS ...
- Spring 应用合并之路(二):峰回路转,柳暗花明 | 京东云技术团队
书接上文,前面在 Spring 应用合并之路(一):摸石头过河 介绍了几种不成功的经验,下面继续折腾- 四.仓库合并,独立容器 在经历了上面的尝试,在同事为啥不搞两个独立的容器提醒下,决定抛开 Spr ...
- node中的优先从缓存中加载模块与模块的加载规则
执行 node main.js 请问 b模块会被加载几次 //main.js require('./a.js') var fn = require('./b.js') console.log(fn.s ...
- windowsbat命令大全
Bat文件的创建及其命令大全 一.bat文件的创建 新建txt文本文件 向文本文件中输入命令 保存并修改文本文件后缀为.bat 双击保存后的bat文件,运行 二.bat命令大全 echo 和 @ @ ...
- 🛠 Open Source Instant Messaging (IM) Project OpenIM Source Code
Deploying OpenIM involves multiple components and supports various methods, including source code, D ...