Linux内核驱动开发-USB热插拔信息调取
写在前面
本博客是同步的本人在电子科技大学学术交流月课程《嵌入式安卓系统开发实践》的笔记。如有问题请及时提出。
可以联系:1160712160@qq.com
GitHhub:https://github.com/WindDevil (目前啥也没有
这里感谢高建武老师!
参考博客
Linux内核驱动开发-USB热插拔信息调取_suspend monitor-CSDN博客
嵌入式安卓系统开发实践笔记一_usb callback作用-CSDN博客
Kernel Module实战指南(一):Hello World!_kernel module hello world-CSDN博客
中文翻译 — The Linux Kernel documentation
linux等待队列 wait_queue的使用_waitqueue-CSDN博客
Linux的notifier机制在TP中的应用-CSDN博客
Linux suspend命令详解:如何暂停和唤醒系统(附实例教程和注意事项)-Linux入门自学网 (bashcommandnotfound.cn)
kernel syscore 学习笔记 - Hello-World3 - 博客园 (cnblogs.com)
Linux环境
.-/+oossssoo+/-.
`:+ssssssssssssssssss+:`
-+ssssssssssssssssssyyssss+-
.ossssssssssssssssssdMMMNysssso.
/ssssssssssshdmmNNmmyNMMMMhssssss/
+ssssssssshmydMMMMMMMNddddyssssssss+ winddevil@winddevil-virtual-machine
/sssssssshNMMMyhhyyyyhmNMMMNhssssssss/ -----------------------------------
.ssssssssdMMMNhsssssssssshNMMMdssssssss. OS: Ubuntu 22.04.4 LTS x86_64
+sssshhhyNMMNyssssssssssssyNMMMysssssss+ Host: VMware Virtual Platform None
ossyNMMMNyMMhsssssssssssssshmmmhssssssso Kernel: 6.5.0-41-generic
ossyNMMMNyMMhsssssssssssssshmmmhssssssso Uptime: 2 days, 21 hours, 24 mins
+sssshhhyNMMNyssssssssssssyNMMMysssssss+ Packages: 1900 (dpkg), 9 (snap)
.ssssssssdMMMNhsssssssssshNMMMdssssssss. Shell: bash 5.1.16
/sssssssshNMMMyhhyyyyhdNMMMNhssssssss/ Resolution: 1918x966
+sssssssssdmydMMMMMMMMddddyssssssss+ Terminal: node
/ssssssssssshdmNNNNmyNMMMMhssssss/ CPU: Intel i5-9300H (2) @ 2.400GHz
.ossssssssssssssssssdMMMNysssso. GPU: 00:0f.0 VMware SVGA II Adapter
-+sssssssssssssssssyyyssss+- Memory: 1528MiB / 3870MiB
`:+ssssssssssssssssss+:`
.-/+oossssoo+/-.
开发环境
vscode+vmware+ssh
作业要求
参考 suspend_monitor,实现 USB 拔插监控驱动+应用程序
- 编写一个 kernel module, 注册 usb callback, 用于监控 USB 设备的插拔情况,提供 /proc/usb_monitor 设备节点,提供读取接口,功能开关接口。
- Userspace 应用程序,读取 /proc/usb_monitor 设备节点数据,以 RingBuffer 形式存储插拔数据(最多512条),数据格式自定义,但必须包括:USB设备名称+插入时间+拔离时间。
什么是kernel module
Linux Kernel Module是一段可以在运行时被加载到Linux Kernel中的代码,可以使用Kernel Functions。Linux Kernel Module的用途很广,最常见的例子就是Device Driver,也就是设备驱动程序。
如果没有Linux Kernel Module,每一行修改Kernel代码,每一个新增的Kernel功能特性,都需要重新编译Kernel,大大浪费了时间和效率。
编译参考代码并安装
从博客了解什么是kernel module
之后,我们可以轻松地发现老师给出的suspend_monitor.c
就是一个典型的kernel module
.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/uaccess.h>
#include <linux/fb.h>
#include <linux/notifier.h>
#include <linux/proc_fs.h>
#include <linux/syscore_ops.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/ktime.h>
#include <linux/time.h>
#include <linux/mutex.h>
#define LOGI(...) (pr_info(__VA_ARGS__))
#define LOGE(...) (pr_err(__VA_ARGS__))
#define MESSAGE_BUFFER_SIZE 5
#define CMD_GET_STATUS _IOR(0xFF, 123, unsigned char)
enum STATUS_TYPE {
STATUS_NONE = 0,
STATUS_SCREEN_ON = 1,
STATUS_SCREEN_OFF = 2,
STATUS_SUSPEND = 3,
};
/* 32 bytes in total*/
struct suspend_message_t {
signed long long kernel_time; /* 8 bytes */
struct timespec64 timeval_utc; /* 16 bytes */
enum STATUS_TYPE status_old; /* 4 bytes */
enum STATUS_TYPE status_new; /* 4 bytes */
};
struct suspend_monitor_t {
struct notifier_block fb_notif;
struct syscore_ops sys_ops;
struct suspend_message_t message[MESSAGE_BUFFER_SIZE];
int suspend_message_count;
int suspend_message_index_read;
int suspend_message_index_write;
char write_buff[10];
int enable_suspend_monitor;
wait_queue_head_t suspend_monitor_queue;
struct mutex suspend_monitor_mutex;
};
static struct suspend_monitor_t *monitor;
static char *TAG = "MONITOR";
static ssize_t suspend_monitor_read(struct file *filp, char __user *buf,
size_t size, loff_t *ppos)
{
int index;
size_t message_size = sizeof(struct suspend_message_t);
LOGI("%s:%s\n", TAG, __func__);
if (size < message_size) {
LOGE("%s:read size is smaller than message size!\n", TAG);
return -EINVAL;
}
wait_event_interruptible(monitor->suspend_monitor_queue,
monitor->suspend_message_count > 0);
LOGI("%s:read wait event pass\n", TAG);
mutex_lock(&monitor->suspend_monitor_mutex);
if (monitor->suspend_message_count > 0) {
index = monitor->suspend_message_index_read;
if (copy_to_user(buf, &monitor->message[index], message_size)) {
LOGE("%s:copy_from_user error!\n", TAG);
mutex_unlock(&monitor->suspend_monitor_mutex);
return -EFAULT;
}
monitor->suspend_message_index_read++;
if (monitor->suspend_message_index_read >= MESSAGE_BUFFER_SIZE)
monitor->suspend_message_index_read = 0;
monitor->suspend_message_count--;
}
mutex_unlock(&monitor->suspend_monitor_mutex);
LOGI("%s:read count:%d\n", TAG, message_size);
return message_size;
}
static ssize_t suspend_monitor_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
char end_flag = 0x0a, cmd;
LOGI("%s:%s\n", TAG, __func__);
/* only support size=2, such as "echo 0 > suspend_monitor" */
if (size != 2) {
LOGE("%s:invalid cmd size: size = %d\n", TAG, (int)size);
return -EINVAL;
}
if (copy_from_user(monitor->write_buff, buf, size)) {
LOGE("%s:copy_from_user error!\n", TAG);
return -EFAULT;
}
if (monitor->write_buff[1] != end_flag) {
LOGE("%s:invalid cmd: end_flag != 0x0a\n", TAG);
return -EINVAL;
}
cmd = monitor->write_buff[0];
mutex_lock(&monitor->suspend_monitor_mutex);
switch (cmd) {
case '0':
monitor->enable_suspend_monitor = 0;
LOGI("%s:disable suspend monitor\n", TAG);
break;
case '1':
monitor->enable_suspend_monitor = 1;
LOGI("%s:enable suspend monitor\n", TAG);
break;
default:
LOGE("%s:invalid cmd: cmd = %d\n", TAG, cmd);
mutex_unlock(&monitor->suspend_monitor_mutex);
return -EINVAL;
}
mutex_unlock(&monitor->suspend_monitor_mutex);
return size;
}
static unsigned int suspend_monitor_poll(struct file *filp,
struct poll_table_struct *wait)
{
unsigned int mask = 0;
LOGI("%s:%s\n", TAG, __func__);
poll_wait(filp, &monitor->suspend_monitor_queue, wait);
mutex_lock(&monitor->suspend_monitor_mutex);
if (monitor->suspend_message_count > 0)
mask |= POLLIN | POLLRDNORM;
mutex_unlock(&monitor->suspend_monitor_mutex);
return mask;
}
static long suspend_monitor_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
void __user *ubuf = (void __user *)arg;
unsigned char status;
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->suspend_monitor_mutex);
switch (cmd) {
case CMD_GET_STATUS:
LOGI("%s:ioctl:get enable status\n", TAG);
if (monitor->enable_suspend_monitor == 0)
status = 0x00;
else
status = 0xff;
LOGI("%s:ioctl:status=0x%x\n", TAG, status);
if (copy_to_user(ubuf, &status, sizeof(status))) {
LOGE("%s:ioctl:copy_to_user fail\n", TAG);
mutex_unlock(&monitor->suspend_monitor_mutex);
return -EFAULT;
}
break;
default:
LOGE("%s:invalid cmd\n", TAG);
mutex_unlock(&monitor->suspend_monitor_mutex);
return -ENOTTY;
}
mutex_unlock(&monitor->suspend_monitor_mutex);
return 0;
}
static const struct file_operations suspend_monitor_fops = {
.owner = THIS_MODULE,
.read = suspend_monitor_read,
.write = suspend_monitor_write,
.poll = suspend_monitor_poll,
.unlocked_ioctl = suspend_monitor_ioctl,
};
static void write_message(enum STATUS_TYPE status_new)
{
enum STATUS_TYPE status_old;
int index;
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->suspend_monitor_mutex);
index = monitor->suspend_message_index_write;
status_old = monitor->message[index].status_new;
monitor->suspend_message_index_write++;
if (monitor->suspend_message_index_write >= MESSAGE_BUFFER_SIZE)
monitor->suspend_message_index_write = 0;
index = monitor->suspend_message_index_write;
monitor->message[index].kernel_time = ktime_to_ns(ktime_get());
ktime_get_ts64(&monitor->message[index].timeval_utc);
monitor->message[index].status_old = status_old;
monitor->message[index].status_new = status_new;
if (monitor->suspend_message_count < MESSAGE_BUFFER_SIZE)
monitor->suspend_message_count++;
wake_up_interruptible(&monitor->suspend_monitor_queue);
mutex_unlock(&monitor->suspend_monitor_mutex);
}
static int fb_notifier_callback(struct notifier_block *self,
unsigned long event, void *data)
{
struct fb_event *evdata = data;
int blank;
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->suspend_monitor_mutex);
if (monitor->enable_suspend_monitor == 0) {
LOGE("%s:suspend monitor is disable\n", TAG);
mutex_unlock(&monitor->suspend_monitor_mutex);
return 0;
}
mutex_unlock(&monitor->suspend_monitor_mutex);
/* make sure it is a hardware display blank change occurred */
if (event != FB_EVENT_BLANK) {
LOGE("%s:not a FB_EVENT_BLANK event\n", TAG);
return 0;
}
blank = *(int *)evdata->data;
switch (blank) {
case FB_BLANK_UNBLANK:
LOGI("%s:FB_BLANK_UNBLANK\n", TAG);
write_message(STATUS_SCREEN_ON);
LOGI("%s:wake up for STATUS_SCREEN_ON\n", TAG);
break;
case FB_BLANK_POWERDOWN:
LOGI("%s:FB_BLANK_POWERDOWN\n", TAG);
write_message(STATUS_SCREEN_OFF);
LOGI("%s:wake up for STATUS_SCREEN_OFF\n", TAG);
break;
default:
break;
}
return 0;
}
static int suspend_callback(void)
{
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->suspend_monitor_mutex);
if (monitor->enable_suspend_monitor == 0) {
LOGE("%s:suspend monitor is disable\n", TAG);
mutex_unlock(&monitor->suspend_monitor_mutex);
return 0;
}
mutex_unlock(&monitor->suspend_monitor_mutex);
write_message(STATUS_SUSPEND);
LOGI("%s:wake up for STATUS_SUSPEND\n", TAG);
return 0;
}
static void resume_callback(void)
{
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->suspend_monitor_mutex);
if (monitor->enable_suspend_monitor == 0) {
LOGE("%s:suspend monitor is disable\n", TAG);
mutex_unlock(&monitor->suspend_monitor_mutex);
return;
}
mutex_unlock(&monitor->suspend_monitor_mutex);
write_message(STATUS_SCREEN_OFF);
LOGI("%s:wake up for STATUS_SCREEN_OFF\n", TAG);
}
static int __init suspend_monitor_init(void)
{
int i, err;
LOGI("%s:%s\n", TAG, __func__);
monitor = kzalloc(sizeof(struct suspend_monitor_t), GFP_KERNEL);
if (!monitor) {
LOGE("%s:failed to kzalloc\n", TAG);
return -ENOMEM;
}
for (i = 0; i < MESSAGE_BUFFER_SIZE; i++) {
monitor->message[i].status_old = STATUS_NONE;
monitor->message[i].status_new = STATUS_NONE;
}
monitor->suspend_message_count = 0;
monitor->suspend_message_index_read = 1;
monitor->suspend_message_index_write = 0;
monitor->enable_suspend_monitor = 1;
proc_create("suspend_monitor", 0644, NULL, &suspend_monitor_fops);
init_waitqueue_head(&monitor->suspend_monitor_queue);
mutex_init(&monitor->suspend_monitor_mutex);
monitor->fb_notif.notifier_call = fb_notifier_callback;
err = fb_register_client(&monitor->fb_notif);
if (err)
LOGE("%s:failed to register fb client, err=%d\n", TAG, err);
monitor->sys_ops.suspend = suspend_callback;
monitor->sys_ops.resume = resume_callback;
register_syscore_ops(&monitor->sys_ops);
return 0;
}
static void __exit suspend_monitor_exit(void)
{
int err;
LOGI("%s:%s\n", TAG, __func__);
remove_proc_entry("suspend_monitor", NULL);
err = fb_unregister_client(&monitor->fb_notif);
if (err)
LOGE("%s:failed to unregister fb client, err=%d\n", TAG, err);
unregister_syscore_ops(&monitor->sys_ops);
kfree(monitor);
}
module_init(suspend_monitor_init);
module_exit(suspend_monitor_exit);
MODULE_AUTHOR("MediaTek");
MODULE_DESCRIPTION("Suspend Monitor");
MODULE_LICENSE("GPL v2");
这时候我们想到我们可以make
它.我们查看它的Makefile
文件,看看端倪:
KERDIR = /lib/modules/$(shell uname -r)/build
obj-m += suspend_monitor.o
build:
make -C $(KERDIR) M=$(CURDIR) modules
clean:
make -C $(KERDIR) M=$(CURDIR) clean
这段文件的意思是定义了两个make
命令build
和clean
.
build
表示构建当前目录下的源码clean
表示清楚构建模块的产物
但是我们执行make
之后报错:
make -C /lib/modules/6.5.0-41-generic/build M=/home/winddevil/workspace/suspendmonitor/drv/suspend_monitor modules
make[1]: Entering directory '/usr/src/linux-headers-6.5.0-41-generic'
warning: the compiler differs from the one used to build the kernel
The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
You are using:
CC [M] /home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.o
/bin/sh: 1: gcc-12: not found
make[3]: *** [scripts/Makefile.build:251: /home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.o] Error 127
make[2]: *** [/usr/src/linux-headers-6.5.0-41-generic/Makefile:2039: /home/winddevil/workspace/suspendmonitor/drv/suspend_monitor] Error 2
make[1]: *** [Makefile:234: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.5.0-41-generic'
make: *** [Makefile:5: build] Error 2
报错缺少gcc-12
,那么我们尝试安装它:
sudo apt-get update
sudo apt-get install gcc-12 g++-12
这时候重新进行make
,然后仍然报错:
error: passing argument 4 of ‘proc_create’ from incompatible pointer type [-Werror=incompatible-pointer-types]
354 | proc_create("suspend_monitor", 0644, NULL, suspend_monitor_fops);
| ^~~~~~~~~~~~~~~~~~~~~
| |
| const struct file_operations *
In file included from /home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.c:9:
./include/linux/proc_fs.h:110:122: note: expected ‘const struct proc_ops *’ but argument is of type ‘const struct file_operations *’
110 | struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct proc_ops *proc_ops);
| ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
proc_create
这个函数要求传入的第四个参数为const struct proc_ops
,但是suspend_monitor_fops
是const struct file_operations *
.
这里提到是Linux
更新了,和老师推荐使用Ubuntu18,我用的Ubuntu22有关系,但是实在是不想重新搭载一个虚拟机.
那么我们需要把suspend_monitor_fops
改成struct proc_ops
类型.参照这里.
static const struct proc_ops suspend_monitor_fops = {
.proc_read = suspend_monitor_read,
.proc_write = suspend_monitor_write,
.proc_poll = suspend_monitor_poll,
.proc_ioctl = suspend_monitor_ioctl,
};
这时候我们重新make
,虽然有warning
但是我们还是开车冲下了悬崖(不是 XD ,总之就是成功了:
make -C /lib/modules/6.5.0-41-generic/build M=/home/winddevil/workspace/suspendmonitor/drv/suspend_monitor modules
make[1]: Entering directory '/usr/src/linux-headers-6.5.0-41-generic'
warning: the compiler differs from the one used to build the kernel
The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
You are using: gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
CC [M] /home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.o
In file included from ./include/linux/kernel.h:30,
from ./arch/x86/include/asm/percpu.h:27,
from ./arch/x86/include/asm/nospec-branch.h:14,
from ./arch/x86/include/asm/paravirt_types.h:27,
from ./arch/x86/include/asm/ptrace.h:97,
from ./arch/x86/include/asm/math_emu.h:5,
from ./arch/x86/include/asm/processor.h:13,
from ./arch/x86/include/asm/timex.h:5,
from ./include/linux/timex.h:67,
from ./include/linux/time32.h:13,
from ./include/linux/time.h:60,
from ./include/linux/stat.h:19,
from ./include/linux/module.h:13,
from /home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.c:2:
/home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.c: In function ‘suspend_monitor_read’:
./include/linux/kern_levels.h:5:25: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘size_t’ {aka ‘long unsigned int’} [-Wformat=]
5 | #define KERN_SOH "\001" /* ASCII Start Of Header */
| ^~~~~~
./include/linux/printk.h:427:25: note: in definition of macro ‘printk_index_wrap’
427 | _p_func(_fmt, ##__VA_ARGS__); \
| ^~~~
./include/linux/printk.h:528:9: note: in expansion of macro ‘printk’
528 | printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
| ^~~~~~
./include/linux/kern_levels.h:14:25: note: in expansion of macro ‘KERN_SOH’
14 | #define KERN_INFO KERN_SOH "6" /* informational */
| ^~~~~~~~
./include/linux/printk.h:528:16: note: in expansion of macro ‘KERN_INFO’
528 | printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
| ^~~~~~~~~
/home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.c:18:26: note: in expansion of macro ‘pr_info’
18 | #define LOGI(...) (pr_info(__VA_ARGS__))
| ^~~~~~~
/home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.c:95:9: note: in expansion of macro ‘LOGI’
95 | LOGI("%s:read count:%d\n", TAG, message_size);
| ^~~~
MODPOST /home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/Module.symvers
CC [M] /home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.mod.o
LD [M] /home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.ko
BTF [M] /home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.ko
Skipping BTF generation for /home/winddevil/workspace/suspendmonitor/drv/suspend_monitor/suspend_monitor.ko due to unavailability of vmlinux
make[1]: Leaving directory '/usr/src/linux-headers-6.5.0-41-generic'
我们尝试安装编译出来的suspend_monitor.ko
文件
sudo insmod suspend_monitor.ko
然后我们插入U盘并且拔掉,注意U盘要选择连接在虚拟机上,使用指令查看log
:
sudo dmesg
得到结果,可以看到有MONITOR
开头的和这个包相关的信息,也能看到关于usb
的信息,老师的代码是监测suspend
,而我们的目的是检测usb
:
[248997.946909] MONITOR:suspend_monitor_init
[249264.188917] usb 2-1: new high-speed USB device number 2 using ehci-pci
[249264.549645] usb 2-1: New USB device found, idVendor=abcd, idProduct=1234, bcdDevice= 1.00
[249264.549657] usb 2-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[249264.549659] usb 2-1: Product: UDisk
[249264.549683] usb 2-1: Manufacturer: General
[249264.549710] usb 2-1: SerialNumber: Љ
[249264.761273] usb-storage 2-1:1.0: USB Mass Storage device detected
[249264.770546] scsi host33: usb-storage 2-1:1.0
[249264.800732] usbcore: registered new interface driver usb-storage
[249264.877409] usbcore: registered new interface driver uas
[249265.820820] scsi 33:0:0:0: Direct-Access General UDisk 5.00 PQ: 0 ANSI: 2
[249265.838686] sd 33:0:0:0: Attached scsi generic sg3 type 0
[249265.857613] sd 33:0:0:0: [sdb] 15728640 512-byte logical blocks: (8.05 GB/7.50 GiB)
[249265.860078] sd 33:0:0:0: [sdb] Write Protect is off
[249265.860084] sd 33:0:0:0: [sdb] Mode Sense: 0b 00 00 08
[249265.862873] sd 33:0:0:0: [sdb] No Caching mode page found
[249265.862881] sd 33:0:0:0: [sdb] Assuming drive cache: write through
[249265.986713] sdb: sdb1
[249266.001284] sd 33:0:0:0: [sdb] Attached SCSI removable disk
[249274.795991] usb 2-1: USB disconnect, device number 2
[249276.615479] usb 2-1: new high-speed USB device number 3 using ehci-pci
[249281.038639] e1000: ens33 NIC Link is Down
[249287.096009] e1000: ens33 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None
编译并且运行应用程序
首先进入老师给的代码目录
cd native/native_ioctl/NativeService_Auto_AIDL/
编译一下代码:
g++ -o SuspendMonitorService_Server SuspendMonitorService_Server.cpp
虽然又来了点warning
,但是我们程序员是不会理会警告的(不是XD,可以看到只是类型不对,但是只要指针对应的内存块访问的时候是一个大小就不会出事,所以无伤大雅:
SuspendMonitorService_Server.cpp: In function ‘void* DoSuspendMonitor(void*)’:
SuspendMonitorService_Server.cpp:128:42: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘ssize_t’ {aka ‘long int’} [-Wformat=]
128 | printf("kernel_time[%d] = 0x%x \n", i, deviceinfo.info.kernel_time[i]);
| ~^ ~
| | |
| int ssize_t {aka long int}
| %ld
SuspendMonitorService_Server.cpp:133:39: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘ssize_t’ {aka ‘long int’} [-Wformat=]
133 | printf("utc_time[%d] = 0x%x \n", i, deviceinfo.info.utc_time[i]);
| ~^ ~
| | |
| int ssize_t {aka long int}
| %ld
SuspendMonitorService_Server.cpp:138:41: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘ssize_t’ {aka ‘long int’} [-Wformat=]
138 | printf("status_old[%d] = %d \n", i, deviceinfo.info.status_old[i]);
| ~^ ~
| | |
| int ssize_t {aka long int}
| %ld
SuspendMonitorService_Server.cpp:143:41: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘ssize_t’ {aka ‘long int’} [-Wformat=]
143 | printf("status_new[%d] = %d \n", i, deviceinfo.info.status_new[i]);
| ~^ ~
| | |
| int ssize_t {aka long int}
| %ld
运行:
sudo ./SuspendMonitorService_Server
这里虽然没有找到在虚拟机中改变FrameBuffer
状态的方法,应该是在手机上按息屏按键可以触发它,应用的输出没有具体改变和输出,但是我们运行sudo dmesg|grep "MONITOR"
,可以得到:
[ 188.009651] MONITOR:suspend_monitor_init
[ 1568.755085] MONITOR:suspend_monitor_poll
可以看到我们运行了SuspendMonitorService_Server
,驱动确实是得到了反馈的,到这里已经足够我们仿照这个方法来编写一个usb_monitor
了.
查看具体驱动的实现方法
首先我们拉到代码最下边:
module_init(suspend_monitor_init);
module_exit(suspend_monitor_exit);
MODULE_AUTHOR("MediaTek");
MODULE_DESCRIPTION("Suspend Monitor");
MODULE_LICENSE("GPL v2");
我们可以看到代码驱动包的入口函数为suspend_monitor_init
.我们跳转到这个函数去看它的内容:
static int __init suspend_monitor_init(void)
{
int i, err;
LOGI("%s:%s\n", TAG, __func__);
monitor = kzalloc(sizeof(struct suspend_monitor_t), GFP_KERNEL);
if (!monitor) {
LOGE("%s:failed to kzalloc\n", TAG);
return -ENOMEM;
}
for (i = 0; i < MESSAGE_BUFFER_SIZE; i++) {
monitor->message[i].status_old = STATUS_NONE;
monitor->message[i].status_new = STATUS_NONE;
}
monitor->suspend_message_count = 0;
monitor->suspend_message_index_read = 1;
monitor->suspend_message_index_write = 0;
monitor->enable_suspend_monitor = 1;
proc_create("suspend_monitor", 0644, NULL, &suspend_monitor_fops);
init_waitqueue_head(&monitor->suspend_monitor_queue);
mutex_init(&monitor->suspend_monitor_mutex);
monitor->fb_notif.notifier_call = fb_notifier_callback;
err = fb_register_client(&monitor->fb_notif);
if (err)
LOGE("%s:failed to register fb client, err=%d\n", TAG, err);
monitor->sys_ops.suspend = suspend_callback;
monitor->sys_ops.resume = resume_callback;
register_syscore_ops(&monitor->sys_ops);
return 0;
}
我们首先可以看到它申请了一片空间,并且初始化了一个struct suspend_monitor_t
结构体monitor
,然后用一个函数proc_create
进行了一些操作:
- 涉及到一个字符串
"suspend_monitor"
,看起来像是一个任务的名字 - 涉及到一个
0644
看起来很像是Linux系统中的0644
文件权限,即为"用户具有读写权限,组用户和其它用户具有只读权限" - 还涉及到一个空指针
NULL
看似是没有传入或者是遵循默认选项 - 然后传入一个指针
suspend_monitor_fops
,它的类型是proc_ops
,是一个全局的常量.
这时候我们搜索proc_create documentation
,查看proc filesystem相关文档,既然如此,那么我们可以假设这个proc filesystem
可以用来监控磁盘:
The proc file system acts as an interface to internal data structures in the kernel. It can be used to obtain information about the system and to change certain kernel parameters at runtime (sysctl).
找了很久,没有找到proc_create
的api reference
,为了能解决问题先看一些博客.
proc_create
的作用是proc文件的创建:
name就是要创建的文件名
mode是文件的访问权限,以UGO的模式表示。
parent与proc_mkdir中的parent类似。也是父文件夹的proc_dir_entry对象。
proc_fops就是该文件的操作函数了
那么比较简单的前三个参数就很容易理解了,就是创建一个名字为suspend_monitor
的文件,文件权限是0644
,没有父对象.
那么文件的操作函数是比较重要的传入参数:
static const struct proc_ops suspend_monitor_fops = {
.proc_read = suspend_monitor_read,
.proc_write = suspend_monitor_write,
.proc_poll = suspend_monitor_poll,
.proc_ioctl = suspend_monitor_ioctl,
};
可以看到这是一个存有函数指针的结构体,分别定义了该文件的:
- 读:
proc_read
- 写:
proc_write
- 轮询:
proc_poll
- 输入输出控制:``proc_ioctl`
这里对于轮询和输入输出控制这方面很明显是认知不足的:
proc_poll
函数指针用于实现轮询机制,当一个进程调用poll()
或者select()
系统调用来检查文件描述符是否准备好读写操作时,就会调用这个函数。proc_poll
可以告诉调用者文件是否可读、可写或者是否有异常条件发生。这对于那些需要异步通知的应用程序非常重要,比如事件驱动的服务器可能会使用poll()
来检测多个文件描述符的状态变化。在suspend_monitor_fops
中,如果定义了suspend_monitor_poll
函数,那么当用户空间应用程序使用poll()
或select()
来监控这个特定的/proc
文件时,内核就会调用suspend_monitor_poll
函数来检查是否有新的数据可供读取,或者是否有数据可以被写入。proc_ioctl
函数指针用于处理输入输出控制(Input/Output Control)请求。ioctl()
是一个通用的系统调用,用于执行设备驱动程序和特殊文件的特定操作,这些操作通常不能通过标准的读写操作完成。例如,设置串口参数、获取终端窗口大小、或者对硬件设备执行特定的控制操作等。在suspend_monitor_fops
中,如果定义了suspend_monitor_ioctl
函数,那么当用户空间应用程序调用ioctl()
并传入特定的 ioctl 命令时,内核就会调用这个函数。suspend_monitor_ioctl
函数应该解析 ioctl 请求,并根据请求执行相应的操作,然后返回成功或失败的结果给用户空间。
仔细观察这四个函数,看到函数中除了使用互斥锁保护monitor
的线程安全之外,主要是使用了两个api传递消息:
copy_to_user
copy_from_user
我们直接看这两个api
的源码:
static void volatile_memcpy(volatile char *to, const volatile char *from,
unsigned long n)
{
while (n--)
*(to++) = *(from++);
}
static inline int copy_from_user(void *to, const void __user volatile *from,
unsigned long n)
{
volatile_memcpy(to, from, n);
return 0;
}
static inline int copy_to_user(void __user volatile *to, const void *from,
unsigned long n)
{
volatile_memcpy(to, from, n);
return 0;
}
后边比较重要的和线程无关的函数:
fb_register_client
函数,当framebuffer
子系统发生事件时,会调用这个函数.register_syscore_ops
函数,注册系统核心操作,在syscore
状态发生变化的时候变化.
这里我们看不到fb_register_client
的API reference.我们直接看它的源码,看注释可以知道我们代码中传入的monitor->fb_notif
是为了注册一个在发生事件的时候的回调:
static BLOCKING_NOTIFIER_HEAD(fb_notifier_list);
/**
* fb_register_client - register a client notifier
* @nb: notifier block to callback on events
*
* Return: 0 on success, negative error code on failure.
*/
int fb_register_client(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&fb_notifier_list, nb);
}
EXPORT_SYMBOL(fb_register_client);
BLOCKING_NOTIFIER_HEAD
是一个宏,那么fb_notifier_list
就是专门为framebuffer
的notify
储存配置的一个结构体struct blocking_notifier_head
:
#define BLOCKING_NOTIFIER_HEAD(name) \
struct blocking_notifier_head name = \
BLOCKING_NOTIFIER_INIT(name)
fb_notif
是一个strcut notifier_block
类型,我们看源码可以看到,需要编写的回调函数要求的输入参数和回调:
struct notifier_block;
typedef int (*notifier_fn_t)(struct notifier_block *nb,
unsigned long action, void *data);
struct notifier_block {
notifier_fn_t notifier_call;
struct notifier_block __rcu *next;
int priority;
};
这时候我们回看我们代码中的回调函数干了什么,可以看到读取了data
并且强制转换指针类型为struct fb_event *
,然后读取其data
属性,并且根据其情况用write_message
输出不同的信息:
static int fb_notifier_callback(struct notifier_block *self,
unsigned long event, void *data)
{
struct fb_event *evdata = data;
int blank;
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->suspend_monitor_mutex);
if (monitor->enable_suspend_monitor == 0) {
LOGE("%s:suspend monitor is disable\n", TAG);
mutex_unlock(&monitor->suspend_monitor_mutex);
return 0;
}
mutex_unlock(&monitor->suspend_monitor_mutex);
/* make sure it is a hardware display blank change occurred */
if (event != FB_EVENT_BLANK) {
LOGE("%s:not a FB_EVENT_BLANK event\n", TAG);
return 0;
}
blank = *(int *)evdata->data;
switch (blank) {
case FB_BLANK_UNBLANK:
LOGI("%s:FB_BLANK_UNBLANK\n", TAG);
write_message(STATUS_SCREEN_ON);
LOGI("%s:wake up for STATUS_SCREEN_ON\n", TAG);
break;
case FB_BLANK_POWERDOWN:
LOGI("%s:FB_BLANK_POWERDOWN\n", TAG);
write_message(STATUS_SCREEN_OFF);
LOGI("%s:wake up for STATUS_SCREEN_OFF\n", TAG);
break;
default:
break;
}
return 0;
}
这时候只需要看write_message
干了什么就行了,我们可以看到实际上是操作了类型为struct suspend_monitor_t
的monitor
的属性,把信息写进去,主要写的是framebuffer
的新旧状态变化,和系统时间:
static void write_message(enum STATUS_TYPE status_new)
{
enum STATUS_TYPE status_old;
int index;
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->suspend_monitor_mutex);
index = monitor->suspend_message_index_write;
status_old = monitor->message[index].status_new;
monitor->suspend_message_index_write++;
if (monitor->suspend_message_index_write >= MESSAGE_BUFFER_SIZE)
monitor->suspend_message_index_write = 0;
index = monitor->suspend_message_index_write;
monitor->message[index].kernel_time = ktime_to_ns(ktime_get());
ktime_get_ts64(&monitor->message[index].timeval_utc);
monitor->message[index].status_old = status_old;
monitor->message[index].status_new = status_new;
if (monitor->suspend_message_count < MESSAGE_BUFFER_SIZE)
monitor->suspend_message_count++;
wake_up_interruptible(&monitor->suspend_monitor_queue);
mutex_unlock(&monitor->suspend_monitor_mutex);
}
这样我们就可以理解这个程序的作用,是在framebuffer
的状态变化时,记录状态变化和时间.
而对于register_syscore_ops
我们可以看到它的源码,实际上是注册了内核操作,在系统状态变化的时候会调用注册的函数:
struct list_head {
struct list_head *next, *prev;
};
... ...
struct syscore_ops {
struct list_head node;
int (*suspend)(void);
void (*resume)(void);
void (*shutdown)(void);
};
... ...
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
... ...
/**
* register_syscore_ops - Register a set of system core operations.
* @ops: System core operations to register.
*/
void register_syscore_ops(struct syscore_ops *ops)
{
mutex_lock(&syscore_ops_lock);
list_add_tail(&ops->node, &syscore_ops_list);
mutex_unlock(&syscore_ops_lock);
}
TIPS
有的函数不能在中查到,那么可以自己看Linux的源代码,这里在github
后边加上1s
,就可以进入一个在线的类vscode
,这里是Linux源码.
查看应用的具体实现方法
查看SuspendMonitorService_Server.cpp
的源码,发现主要是实现了一个SuspendMonitorDevice
类,然后我们关注这个类的成员函数就可以了:
#include <cinttypes>
#include <numeric>
#include <set>
#include <string>
#include <string.h>
#include <tuple>
#include <vector>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include "RingBuffer.h"
#include "SuspendInfo.h"
using namespace std;
pthread_mutex_t data_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t fifo_nonzero;
static volatile int64_t isEmpty = 0;
static volatile int64_t fifo_size = 0;
class SuspendMonitorDevice {
public:
SuspendMonitorDevice(char* name){
mDev_name = name;
}
~SuspendMonitorDevice(){
epoll_ctl(mEpollfd, EPOLL_CTL_DEL, mFd, &mEpev);
close(mEpollfd);
close(mFd);
}
int InitSetup(){
//open "/proc/suspend_monitor"
mFd = open(mDev_name, O_RDWR);
if (mFd == -1) {
printf("open %s fail, Check!!!\n",mDev_name);
return errno;
}
//epoll
mEpollfd = epoll_create(MAX_EPOLL_EVENTS);
if (mEpollfd == -1) {
printf("epoll_create failed errno = %d ", errno);
return errno;
}
printf("epoll_create ok epollfd= %d \n", mEpollfd);
//add fd for epoll
memset(&mEpev, 0, sizeof(mEpev));
mEpev.data.fd = mFd;
mEpev.events = EPOLLIN;
if (epoll_ctl(mEpollfd, EPOLL_CTL_ADD, mFd, &mEpev) < 0) {
printf("epoll_ctl failed, errno = %d \n", errno);
return errno;
}
return 0;
}
int getFd() { return mFd; };
int getepollfd() { return mEpollfd; };
char* getBuffer() { return mBuf; };
SuspendMonitorInfo& GetFristDataInfo(){
return mRingBuffer.Get(0);
}
void PPopFrontDatainfo(){
mRingBuffer.PopFront();
}
void PopBackDatainfo(){
mRingBuffer.PopBack();
}
SuspendMonitorInfo& GetBackDataInfo(){
return mRingBuffer.Back();
}
void AppendDatainfo(SuspendMonitorInfo& info){
mRingBuffer.Append(info);
}
size_t GetFifoSize(){
return mRingBuffer.GetSize();
}
void FifoReset(size_t capacity) {
mRingBuffer.Reset(capacity);
}
bool FifoIsEmpty() {
return mRingBuffer.IsEmpty();
}
private:
int mFd; //"/proc/suspend_monitor"
int mEpollfd;
struct epoll_event mEpev;
char *mDev_name;
char mBuf[50];
RingBuffer<SuspendMonitorInfo> mRingBuffer;
};
static void * DoSuspendMonitor(void *arg){
int ret;
//static int index = 0;
ssize_t leng = 0, i = 0;
struct epoll_event epev;
SuspendMonitorInfo deviceinfo;
SuspendMonitorDevice* device = (SuspendMonitorDevice*)arg;
char* buf = device->getBuffer();
device->FifoReset(BUFFER_SIZE);
while(1){
printf("suspend_monitor epoll_wait... \n");
ret = epoll_wait(device->getepollfd(), &epev, MAX_EPOLL_EVENTS, -1);
if (ret == -1 && errno != EINTR) {
printf("suspend_monitor epoll_wait failed; errno=%d\n", errno);
return (void*)-1;
}
leng = read(device->getFd(), buf, KERNEL_DATA_LENG); //MAX 32 Byte
if (leng == KERNEL_DATA_LENG){
//8 bute kernel time
for (i = 0; i < 8; i++){
deviceinfo.info.kernel_time[i] = buf[i];
printf("kernel_time[%d] = 0x%x \n", i, deviceinfo.info.kernel_time[i]);
}
//16 byte UTC
for (i = 0; i < 16; i++){
deviceinfo.info.utc_time[i] = buf[i+8];
printf("utc_time[%d] = 0x%x \n", i, deviceinfo.info.utc_time[i]);
}
//4 byte status_old
for (i = 0; i < 4; i++){
deviceinfo.info.status_old[i] = buf[i+24];
printf("status_old[%d] = %d \n", i, deviceinfo.info.status_old[i]);
}
//4byte status_new
for (i = 0; i < 4; i++){
deviceinfo.info.status_new[i] = buf[i+28];
printf("status_new[%d] = %d \n", i, deviceinfo.info.status_new[i]);
}
ret = pthread_mutex_lock(&data_mutex); //get lock
if (ret != 0) {
printf("Error on pthread_mutex_lock(), ret = %d\n", ret);
return (void *)-1;
}
//save
device->AppendDatainfo(deviceinfo);
fifo_size = device->GetFifoSize();
ret = pthread_mutex_unlock(&data_mutex); //unlock
if (ret != 0) {
printf("Error on pthread_mutex_unlock(), ret = %d\n", ret);
return (void *)-1;
}
if (isEmpty){
for (i = 0; i < isEmpty; i++)
pthread_cond_signal(&fifo_nonzero);
}
printf("Current BufferSize = %ld \n", fifo_size);
}
}
}
int main(){
int ret;
pthread_t ptd;
//check device
SuspendMonitorDevice* monitorDevice = new SuspendMonitorDevice((char*)DEV_NAME);
if ( monitorDevice->InitSetup() != 0){
printf("SuspendMonitorDevice::InitSetup fail \n");
return -1;
}
printf("SuspendMonitorDevice::InitSetup OK \n");
/*
ret = pthread_create(&ptd, NULL, DoSuspendMonitor, (void*)monitorDevice);
if (ret) {
printf("DoSuspendMonitor pthread_create failed; errno=%d", errno);
return -1;
}
*/
DoSuspendMonitor((void*)monitorDevice);
pthread_mutex_destroy(&data_mutex);
//not call here
return 0;
}
最值得注意的是SuspendMonitorDevice
的InitSetup
函数和DoSuspendMonitor
函数,这里先看InitSetup
:
int InitSetup(){
//open "/proc/suspend_monitor"
mFd = open(mDev_name, O_RDWR);
if (mFd == -1) {
printf("open %s fail, Check!!!\n",mDev_name);
return errno;
}
//epoll
mEpollfd = epoll_create(MAX_EPOLL_EVENTS);
if (mEpollfd == -1) {
printf("epoll_create failed errno = %d ", errno);
return errno;
}
printf("epoll_create ok epollfd= %d \n", mEpollfd);
//add fd for epoll
memset(&mEpev, 0, sizeof(mEpev));
mEpev.data.fd = mFd;
mEpev.events = EPOLLIN;
if (epoll_ctl(mEpollfd, EPOLL_CTL_ADD, mFd, &mEpev) < 0) {
printf("epoll_ctl failed, errno = %d \n", errno);
return errno;
}
return 0;
}
这个函数调用了三个比较重要的函数:
open
:打开文件,返回一个文件描述符,后续所有对该文件的操作都通过这个文件描述符来决定epoll_create
函数创建一个新的epoll实例,并返回一个与之关联的文件描述符(fd)。这个文件描述符将用于后续的epoll操作,如添加、修改或删除监听的文件描述符,以及查询事件。参数size
是一个提示,表示预期将有多少个文件描述符与这个epoll实例关联。epoll_create
是Linux内核提供的用于高效I/O多路复用的系统调用,它允许一个进程监控多个文件描述符的I/O事件。与传统的select
和poll
相比,epoll
提供了更高的性能和灵活性。
epoll_ctl
是Linux系统中用于控制epoll实例的主要系统调用。它允许用户向epoll实例中添加、修改或删除文件描述符的监控状态。epoll_ctl
提供了对epoll实例的动态管理能力,使得应用程序能够根据需要实时调整监控的文件描述符集合。epfd
:epoll实例的文件描述符,由epoll_create
或epoll_create1
函数返回。op
:操作类型,可以是EPOLL_CTL_ADD
(添加监控)、EPOLL_CTL_MOD
(修改监控条件)、EPOLL_CTL_DEL
(删除监控)。fd
:要操作的文件描述符,即希望添加、修改或删除监控的文件描述符。event
:指向struct epoll_event
结构体的指针,用于指定监控的事件类型和相关数据。
这时候我们可以看到这个函数主要是初始化一个基于epoll的事件监控系统的过程,主要用于监控"/proc/suspend_monitor"
设备文件的读写事件.
而对于DoSuspendMonitor
函数:
static void * DoSuspendMonitor(void *arg){
int ret;
//static int index = 0;
ssize_t leng = 0, i = 0;
struct epoll_event epev;
SuspendMonitorInfo deviceinfo;
SuspendMonitorDevice* device = (SuspendMonitorDevice*)arg;
char* buf = device->getBuffer();
device->FifoReset(BUFFER_SIZE);
while(1){
printf("suspend_monitor epoll_wait... \n");
ret = epoll_wait(device->getepollfd(), &epev, MAX_EPOLL_EVENTS, -1);
if (ret == -1 && errno != EINTR) {
printf("suspend_monitor epoll_wait failed; errno=%d\n", errno);
return (void*)-1;
}
leng = read(device->getFd(), buf, KERNEL_DATA_LENG); //MAX 32 Byte
if (leng == KERNEL_DATA_LENG){
//8 bute kernel time
for (i = 0; i < 8; i++){
deviceinfo.info.kernel_time[i] = buf[i];
printf("kernel_time[%d] = 0x%x \n", i, deviceinfo.info.kernel_time[i]);
}
//16 byte UTC
for (i = 0; i < 16; i++){
deviceinfo.info.utc_time[i] = buf[i+8];
printf("utc_time[%d] = 0x%x \n", i, deviceinfo.info.utc_time[i]);
}
//4 byte status_old
for (i = 0; i < 4; i++){
deviceinfo.info.status_old[i] = buf[i+24];
printf("status_old[%d] = %d \n", i, deviceinfo.info.status_old[i]);
}
//4byte status_new
for (i = 0; i < 4; i++){
deviceinfo.info.status_new[i] = buf[i+28];
printf("status_new[%d] = %d \n", i, deviceinfo.info.status_new[i]);
}
ret = pthread_mutex_lock(&data_mutex); //get lock
if (ret != 0) {
printf("Error on pthread_mutex_lock(), ret = %d\n", ret);
return (void *)-1;
}
//save
device->AppendDatainfo(deviceinfo);
fifo_size = device->GetFifoSize();
ret = pthread_mutex_unlock(&data_mutex); //unlock
if (ret != 0) {
printf("Error on pthread_mutex_unlock(), ret = %d\n", ret);
return (void *)-1;
}
if (isEmpty){
for (i = 0; i < isEmpty; i++)
pthread_cond_signal(&fifo_nonzero);
}
printf("Current BufferSize = %ld \n", fifo_size);
}
}
}
代码使用一个无限循环while(1)
来持续监听epoll事件。在每次循环中,首先调用epoll_wait
函数等待epoll事件的发生,MAX_EPOLL_EVENTS
参数指定了最多等待的事件数量,-1
参数表示无限期等待直到事件发生或被中断信号打断。 如果epoll_wait
成功返回,代码读取设备文件的内容到buf
缓冲区中,读取长度为KERNEL_DATA_LENG
字节。如果读取成功,代码解析读取到的数据,并填充SuspendMonitorInfo
结构体deviceinfo
,其中包含了挂起监控所需的关键信息,如内核时间、UTC时间、旧状态和新状态。
仿写驱动
- 编写一个 kernel module, 注册 usb callback, 用于监控 USB 设备的插拔情况,提供 /proc/usb_monitor 设备节点,提供读取接口,功能开关接口。
于是具体地,我们可以把任务划分为如下:
- 注册一个
/proc/usb_monitor
节点,即使用proc_create
进行创建,于是我们需要一个static const struct proc_ops
,来定义其操作函数.- 这里要考虑实际上我们只需要一个读取函数,因为我们的应用程序只需要对其进行写入
- 注册一个在
usb
状态发生改变时就会产生notify
的回调函数,记录usb
的设备名称和插入拔出时间- 这涉及到对应
fb_register_client
的usb
的api的问题,考虑到之前我们看到的源码,我们可以全局搜索blocking_notifier_chain_register
在usb
子系统中被包装成了哪个api
.
- 这涉及到对应
- 编写回调函数.
- 编写回调函数的时候有没有对应
fb_event
的usb_event
呢? usb_event
的不同事件对应的宏怎么写?
- 编写回调函数的时候有没有对应
- 如何编写自己的
proc_read
函数,如何编写write_message
以记录数据,涉及到monitor
中数据储存管理的问题,需要自己定义一个结构体来作为usb
的信息的储存. - 我们不需要监控系统的状态变化,因此不需要注册
register_syscore_ops
.
我们可以找到函数usb_register_notify
:
/**
* usb_register_notify - register a notifier callback whenever a usb change happens
* @nb: pointer to the notifier block for the callback events.
*
* These changes are either USB devices or busses being added or removed.
*/
void usb_register_notify(struct notifier_block *nb)
{
blocking_notifier_chain_register(&usb_notifier_list, nb);
}
为了管理monitor
的各项属性,我们也照猫画虎建立一个结构体:
struct usb_monitor_t
{
struct usb_message_t message[MESSAGE_BUFFER_SIZE];
int usb_message_count;
int usb_message_index_read;
int usb_message_index_write;
int enable_usb_monitor;
struct notifier_block usb_notif;
wait_queue_head_t usb_monitor_queue;
struct mutex usb_monitor_mutex;
};
经过在源码中搜索usb_register_notify
使用的位置,可以看到这里,回调函数对函数的第二个unsigned long
参数进行判断,可见,如果我们注册了USB事件,那么可以通过对这个参数具体是哪一个宏来判断现在发生了什么事件:
static int usbdev_notify(struct notifier_block *self,
unsigned long action, void *dev)
{
switch (action) {
case USB_DEVICE_ADD:
break;
case USB_DEVICE_REMOVE:
usbdev_remove(dev);
break;
}
return NOTIFY_OK;
}
static struct notifier_block usbdev_nb = {
.notifier_call = usbdev_notify,
};
static struct cdev usb_device_cdev;
int __init usb_devio_init(void)
{
int retval;
retval = register_chrdev_region(USB_DEVICE_DEV, USB_DEVICE_MAX,
"usb_device");
if (retval) {
printk(KERN_ERR "Unable to register minors for usb_device\n");
goto out;
}
cdev_init(&usb_device_cdev, &usbdev_file_operations);
retval = cdev_add(&usb_device_cdev, USB_DEVICE_DEV, USB_DEVICE_MAX);
if (retval) {
printk(KERN_ERR "Unable to get usb_device major %d\n",
USB_DEVICE_MAJOR);
goto error_cdev;
}
usb_register_notify(&usbdev_nb);
out:
return retval;
error_cdev:
unregister_chrdev_region(USB_DEVICE_DEV, USB_DEVICE_MAX);
goto out;
}
我们可以看到这个unsigned long
参数可能是如下宏之一:
/* Events from the usb core */
#define USB_DEVICE_ADD 0x0001
#define USB_DEVICE_REMOVE 0x0002
#define USB_BUS_ADD 0x0003
#define USB_BUS_REMOVE 0x0004
那么我们就可以得到USB的插入和拔出情况,这时候就思考到我们现在仍然缺少一个USB设备名称没有得到,那么这时候关注到回调函数的第三个参数void *dev
,它被传入到usbdev_remove
中,而usbdev_remove
需要的参数是一个struct usb_device *
,我们观察这个结构体的结构:
/**
* struct usb_device - kernel's representation of a USB device
* @devnum: device number; address on a USB bus
* @devpath: device ID string for use in messages (e.g., /port/...)
* @route: tree topology hex string for use with xHCI
* @state: device state: configured, not attached, etc.
* @speed: device speed: high/full/low (or error)
* @rx_lanes: number of rx lanes in use, USB 3.2 adds dual-lane support
* @tx_lanes: number of tx lanes in use, USB 3.2 adds dual-lane support
* @ssp_rate: SuperSpeed Plus phy signaling rate and lane count
* @tt: Transaction Translator info; used with low/full speed dev, highspeed hub
* @ttport: device port on that tt hub
* @toggle: one bit for each endpoint, with ([0] = IN, [1] = OUT) endpoints
* @parent: our hub, unless we're the root
* @bus: bus we're part of
* @ep0: endpoint 0 data (default control pipe)
* @dev: generic device interface
* @descriptor: USB device descriptor
* @bos: USB device BOS descriptor set
* @config: all of the device's configs
* @actconfig: the active configuration
* @ep_in: array of IN endpoints
* @ep_out: array of OUT endpoints
* @rawdescriptors: raw descriptors for each config
* @bus_mA: Current available from the bus
* @portnum: parent port number (origin 1)
* @level: number of USB hub ancestors
* @devaddr: device address, XHCI: assigned by HW, others: same as devnum
* @can_submit: URBs may be submitted
* @persist_enabled: USB_PERSIST enabled for this device
* @reset_in_progress: the device is being reset
* @have_langid: whether string_langid is valid
* @authorized: policy has said we can use it;
* (user space) policy determines if we authorize this device to be
* used or not. By default, wired USB devices are authorized.
* WUSB devices are not, until we authorize them from user space.
* FIXME -- complete doc
* @authenticated: Crypto authentication passed
* @lpm_capable: device supports LPM
* @lpm_devinit_allow: Allow USB3 device initiated LPM, exit latency is in range
* @usb2_hw_lpm_capable: device can perform USB2 hardware LPM
* @usb2_hw_lpm_besl_capable: device can perform USB2 hardware BESL LPM
* @usb2_hw_lpm_enabled: USB2 hardware LPM is enabled
* @usb2_hw_lpm_allowed: Userspace allows USB 2.0 LPM to be enabled
* @usb3_lpm_u1_enabled: USB3 hardware U1 LPM enabled
* @usb3_lpm_u2_enabled: USB3 hardware U2 LPM enabled
* @string_langid: language ID for strings
* @product: iProduct string, if present (static)
* @manufacturer: iManufacturer string, if present (static)
* @serial: iSerialNumber string, if present (static)
* @filelist: usbfs files that are open to this device
* @maxchild: number of ports if hub
* @quirks: quirks of the whole device
* @urbnum: number of URBs submitted for the whole device
* @active_duration: total time device is not suspended
* @connect_time: time device was first connected
* @do_remote_wakeup: remote wakeup should be enabled
* @reset_resume: needs reset instead of resume
* @port_is_suspended: the upstream port is suspended (L2 or U3)
* @slot_id: Slot ID assigned by xHCI
* @l1_params: best effor service latency for USB2 L1 LPM state, and L1 timeout.
* @u1_params: exit latencies for USB3 U1 LPM state, and hub-initiated timeout.
* @u2_params: exit latencies for USB3 U2 LPM state, and hub-initiated timeout.
* @lpm_disable_count: Ref count used by usb_disable_lpm() and usb_enable_lpm()
* to keep track of the number of functions that require USB 3.0 Link Power
* Management to be disabled for this usb_device. This count should only
* be manipulated by those functions, with the bandwidth_mutex is held.
* @hub_delay: cached value consisting of:
* parent->hub_delay + wHubDelay + tTPTransmissionDelay (40ns)
* Will be used as wValue for SetIsochDelay requests.
* @use_generic_driver: ask driver core to reprobe using the generic driver.
*
* Notes:
* Usbcore drivers should not set usbdev->state directly. Instead use
* usb_set_device_state().
*/
struct usb_device {
int devnum;
char devpath[16];
u32 route;
enum usb_device_state state;
enum usb_device_speed speed;
unsigned int rx_lanes;
unsigned int tx_lanes;
enum usb_ssp_rate ssp_rate;
struct usb_tt *tt;
int ttport;
unsigned int toggle[2];
struct usb_device *parent;
struct usb_bus *bus;
struct usb_host_endpoint ep0;
struct device dev;
struct usb_device_descriptor descriptor;
struct usb_host_bos *bos;
struct usb_host_config *config;
struct usb_host_config *actconfig;
struct usb_host_endpoint *ep_in[16];
struct usb_host_endpoint *ep_out[16];
char **rawdescriptors;
unsigned short bus_mA;
u8 portnum;
u8 level;
u8 devaddr;
unsigned can_submit:1;
unsigned persist_enabled:1;
unsigned reset_in_progress:1;
unsigned have_langid:1;
unsigned authorized:1;
unsigned authenticated:1;
unsigned lpm_capable:1;
unsigned lpm_devinit_allow:1;
unsigned usb2_hw_lpm_capable:1;
unsigned usb2_hw_lpm_besl_capable:1;
unsigned usb2_hw_lpm_enabled:1;
unsigned usb2_hw_lpm_allowed:1;
unsigned usb3_lpm_u1_enabled:1;
unsigned usb3_lpm_u2_enabled:1;
int string_langid;
/* static strings from the device */
char *product;
char *manufacturer;
char *serial;
struct list_head filelist;
int maxchild;
u32 quirks;
atomic_t urbnum;
unsigned long active_duration;
unsigned long connect_time;
unsigned do_remote_wakeup:1;
unsigned reset_resume:1;
unsigned port_is_suspended:1;
int slot_id;
struct usb2_lpm_parameters l1_params;
struct usb3_lpm_parameters u1_params;
struct usb3_lpm_parameters u2_params;
unsigned lpm_disable_count;
u16 hub_delay;
unsigned use_generic_driver:1;
};
通过看注释,我们可以看到 * @product: iProduct string, if present (static)
,而且看到结构体中可能储存名字这种信息的char *
类型的成员也就product
,manufacturer
,serial
.那么很显然是product
储存了这个文件名.
那么我们就可以仿照struct suspend_message_t
的形式把这些信息储存起来,因此我们可以编写出回调函数usb_notifier_callback
:
static int usb_notifier_callback(struct notifier_block *self, unsigned long event, void *data)
{
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->usb_monitor_mutex);
if (monitor->enable_usb_monitor == 0)
{
LOGE("%s:usb monitor is disable\n", TAG);
mutex_unlock(&monitor->usb_monitor_mutex);
return 0;
}
mutex_unlock(&monitor->usb_monitor_mutex);
switch (event)
{
case USB_DEVICE_ADD:
LOGI("%s: USB_DEVICE_ADD\n", TAG);
write_message(event,data);
break;
case USB_DEVICE_REMOVE:
LOGI("%s: USB_DEVICE_REMOVE\n", TAG);
write_message(event,data);
break;
case USB_BUS_ADD:
break;
case USB_BUS_REMOVE:
break;
default:
break;
}
return 0;
}
上述代码中write_message
还没有实现暂时算是伪代码,但是体现了把回调函数第二个和第三个参数传进去这一点,这时候我们自行实现这个函数:
static void write_message(unsigned long event, struct usb_device *data)
{
int index;
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->usb_monitor_mutex);
monitor->suspend_message_index_write++;
if (monitor->suspend_message_index_write >= MESSAGE_BUFFER_SIZE)
monitor->suspend_message_index_write = 0;
index = monitor->suspend_message_index_write;
monitor->message[index].kernel_time = ktime_to_ns(ktime_get());
if(event == USB_DEVICE_ADD)
{
monitor->message[index].status = ADDED;
}
else if(event == USB_DEVICE_REMOVE)
{
monitor->message[index].status = REMOVED;
}
if(data->product == NULL)
{
memcpy(monitor->message[index].usb_name, "Not detected name", 18);
}
else
{
memcpy(monitor->message[index].usb_name, data->product, strlen(data->product));
}
if (monitor->suspend_message_count < MESSAGE_BUFFER_SIZE)
monitor->suspend_message_count++;
wake_up_interruptible(&monitor->suspend_monitor_queue);
mutex_unlock(&monitor->suspend_monitor_mutex);
}
这里需要提到的一点就是,我们需要判断设备的名称是不是空,根据这篇博客提到的内容,设备名称可能是空.
此时还有一个proc_read
函数没有实现,因为我们的APP
是只需要对usb_monitor
进行读取操作的,因此我们只需要实现并且注册这个函数即可:
static ssize_t usb_monitor_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
int index;
size_t message_size = sizeof(struct usb_message_t);
LOGI("%s:%s\n", TAG, __func__);
if (size < message_size)
{
LOGE("%s:read size is smaller than message size!\n", TAG);
return -EINVAL;
}
wait_event_interruptible(monitor->usb_monitor_queue, monitor->usb_message_count > 0);
LOGI("%s:read wait event pass\n", TAG);
mutex_lock(&monitor->usb_monitor_mutex);
if (monitor->usb_message_count > 0)
{
index = monitor->usb_message_index_read;
if (copy_to_user(buf, &monitor->message[index], message_size))
{
LOGE("%s:copy_from_user error!\n", TAG);
mutex_unlock(&monitor->usb_monitor_mutex);
return -EFAULT;
}
monitor->usb_message_index_read++;
if (monitor->usb_message_index_read >= MESSAGE_BUFFER_SIZE)
monitor->usb_message_index_read = 0;
monitor->usb_message_count--;
}
mutex_unlock(&monitor->usb_monitor_mutex);
LOGI("%s:read count:%d\n", TAG, message_size);
return message_size;
}
最后得出的完整版驱动为:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/notifier.h>
#include <linux/proc_fs.h>
#include <linux/syscore_ops.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/ktime.h>
#include <linux/time.h>
#include <linux/mutex.h>
#define LOGI(...) (pr_info(__VA_ARGS__))
#define LOGE(...) (pr_err(__VA_ARGS__))
#define MESSAGE_BUFFER_SIZE 512
static ssize_t usb_monitor_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos);
static const struct proc_ops usb_monitor_fops =
{
.proc_read = usb_monitor_read
};
enum STATUS_TYPE
{
ADDED = 0,
REMOVED = 1
};
struct usb_message_t
{
signed long long kernel_time;
enum STATUS_TYPE status;
char usb_name[32];
};
struct usb_monitor_t
{
struct usb_message_t message[MESSAGE_BUFFER_SIZE];
int usb_message_count;
int usb_message_index_read;
int usb_message_index_write;
int enable_usb_monitor;
struct notifier_block usb_notif;
wait_queue_head_t usb_monitor_queue;
struct mutex usb_monitor_mutex;
};
static struct usb_monitor_t *monitor;
static char *TAG = "USB_MONITOR";
static ssize_t usb_monitor_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
int index;
size_t message_size = sizeof(struct usb_message_t);
LOGI("%s:%s\n", TAG, __func__);
if (size < message_size)
{
LOGE("%s:read size is smaller than message size!\n", TAG);
return -EINVAL;
}
wait_event_interruptible(monitor->usb_monitor_queue, monitor->usb_message_count > 0);
LOGI("%s:read wait event pass\n", TAG);
mutex_lock(&monitor->usb_monitor_mutex);
if (monitor->usb_message_count > 0)
{
index = monitor->usb_message_index_read;
if (copy_to_user(buf, &monitor->message[index], message_size))
{
LOGE("%s:copy_from_user error!\n", TAG);
mutex_unlock(&monitor->usb_monitor_mutex);
return -EFAULT;
}
monitor->usb_message_index_read++;
if (monitor->usb_message_index_read >= MESSAGE_BUFFER_SIZE)
monitor->usb_message_index_read = 0;
monitor->usb_message_count--;
}
mutex_unlock(&monitor->usb_monitor_mutex);
LOGI("%s:read count:%d\n", TAG, message_size);
return message_size;
}
static void write_message(unsigned long event, struct usb_device *data)
{
int index;
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->usb_monitor_mutex);
monitor->usb_message_index_write++;
if (monitor->usb_message_index_write >= MESSAGE_BUFFER_SIZE)
monitor->usb_message_index_write = 0;
index = monitor->usb_message_index_write;
monitor->message[index].kernel_time = ktime_to_ns(ktime_get());
if(event == USB_DEVICE_ADD)
{
monitor->message[index].status = ADDED;
}
else if(event == USB_DEVICE_REMOVE)
{
monitor->message[index].status = REMOVED;
}
if(data->product == NULL)
{
memcpy(monitor->message[index].usb_name, "Not detected name", 18);
}
else
{
memcpy(monitor->message[index].usb_name, data->product, strlen(data->product));
}
if (monitor->usb_message_count < MESSAGE_BUFFER_SIZE)
monitor->usb_message_count++;
wake_up_interruptible(&monitor->usb_monitor_queue);
mutex_unlock(&monitor->usb_monitor_mutex);
}
static int usb_notifier_callback(struct notifier_block *self, unsigned long event, void *data)
{
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->usb_monitor_mutex);
if (monitor->enable_usb_monitor == 0)
{
LOGE("%s:usb monitor is disable\n", TAG);
mutex_unlock(&monitor->usb_monitor_mutex);
return 0;
}
mutex_unlock(&monitor->usb_monitor_mutex);
switch (event)
{
case USB_DEVICE_ADD:
LOGI("%s: USB_DEVICE_ADD\n", TAG);
write_message(event,data);
break;
case USB_DEVICE_REMOVE:
LOGI("%s: USB_DEVICE_REMOVE\n", TAG);
write_message(event,data);
break;
case USB_BUS_ADD:
break;
case USB_BUS_REMOVE:
break;
default:
break;
}
return 0;
}
static int __init usb_monitor_init(void)
{
int i, err;
LOGI("%s:%s\n", TAG, __func__);
monitor = kzalloc(sizeof(struct usb_monitor_t), GFP_KERNEL);
if (!monitor)
{
LOGE("%s:failed to kzalloc\n", TAG);
return -ENOMEM;
}
monitor->usb_message_count = 0;
monitor->usb_message_index_read = 1;
monitor->usb_message_index_write = 0;
monitor->enable_usb_monitor = 1;
proc_create("usb_monitor", 0644, NULL, &usb_monitor_fops);
init_waitqueue_head(&monitor->usb_monitor_queue);
mutex_init(&monitor->usb_monitor_mutex);
monitor->usb_notif.notifier_call = usb_notifier_callback;
usb_register_notify(&monitor->usb_notif);
return 0;
}
static void __exit usb_monitor_exit(void)
{
int err;
LOGI("%s:%s\n", TAG, __func__);
remove_proc_entry("usb_monitor", NULL);
usb_unregister_notify(&monitor->usb_notif);
kfree(monitor);
}
module_init(usb_monitor_init);
module_exit(usb_monitor_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("1160712160@qq.com");
编写Makefile文件
只需要修改obj-m
为usb_monitor.o
即可:
KERDIR = /lib/modules/$(shell uname -r)/build
obj-m += usb_monitor.o
build:
make -C $(KERDIR) M=$(CURDIR) modules
clean:
make -C $(KERDIR) M=$(CURDIR) clean
仿写程序
- Userspace 应用程序,读取 /proc/usb_monitor 设备节点数据,以 RingBuffer 形式存储插拔数据(最多512条),数据格式自定义,但必须包括:USB设备名称+插入时间+拔离时间。
通过对原本程序的理解,我们可以仿写出程序:
#include <cinttypes>
#include <numeric>
#include <set>
#include <string>
#include <string.h>
#include <tuple>
#include <vector>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include "RingBuffer.h"
#include "USBInfo.h"
pthread_mutex_t data_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t fifo_nonzero;
static volatile int64_t isEmpty = 0;
static volatile int64_t fifo_size = 0;
class USBMonitorDevice
{
public:
USBMonitorDevice(char* name)
{
mDev_name = name;
}
~USBMonitorDevice()
{
epoll_ctl(mEpollfd, EPOLL_CTL_DEL, mFd, &mEpev);
close(mEpollfd);
close(mFd);
}
int InitSetup()
{
//open "/proc/usb_monitor"
mFd = open(mDev_name, O_RDWR);
if (mFd == -1)
{
printf("open %s fail, Check!!!\n",mDev_name);
return errno;
}
//epoll
mEpollfd = epoll_create(MAX_EPOLL_EVENTS);
if (mEpollfd == -1)
{
printf("epoll_create failed errno = %d ", errno);
return errno;
}
printf("epoll_create ok epollfd= %d \n", mEpollfd);
//add fd for epoll
memset(&mEpev, 0, sizeof(mEpev));
mEpev.data.fd = mFd;
mEpev.events = EPOLLIN;
if (epoll_ctl(mEpollfd, EPOLL_CTL_ADD, mFd, &mEpev) < 0)
{
printf("epoll_ctl failed, errno = %d \n", errno);
return errno;
}
return 0;
}
int getFd()
{
return mFd;
}
int getepollfd()
{
return mEpollfd;
}
char* getBuffer()
{
return mBuf;
}
USBMonitorInfo& GetFristDataInfo()
{
return mRingBuffer.Get(0);
}
void PPopFrontDatainfo()
{
mRingBuffer.PopFront();
}
void PopBackDatainfo()
{
mRingBuffer.PopBack();
}
USBMonitorInfo& GetBackDataInfo()
{
return mRingBuffer.Back();
}
void AppendDatainfo(USBMonitorInfo& info)
{
mRingBuffer.Append(info);
}
size_t GetFifoSize()
{
return mRingBuffer.GetSize();
}
void FifoReset(size_t capacity)
{
mRingBuffer.Reset(capacity);
}
bool FifoIsEmpty()
{
return mRingBuffer.IsEmpty();
}
private:
char* mDev_name;
int mFd;
int mEpollfd;
struct epoll_event mEpev;
char mBuf[50];
RingBuffer<USBMonitorInfo> mRingBuffer;
};
static void * DoUSBMonitor(void *arg)
{
int ret;
//static int index = 0;
ssize_t leng = 0, i = 0;
struct epoll_event epev;
USBMonitorInfo deviceinfo;
USBMonitorDevice* device = (USBMonitorDevice*)arg;
char* buf = device->getBuffer();
device->FifoReset(BUFFER_SIZE);
while(1)
{
printf("usb_monitor epoll_wait... \n");
ret = epoll_wait(device->getepollfd(), &epev, MAX_EPOLL_EVENTS, -1);
if (ret == -1 && errno != EINTR)
{
printf("usb_monitor epoll_wait failed; errno=%d\n", errno);
return (void*)-1;
}
leng = read(device->getFd(), buf, KERNEL_DATA_LENG); //MAX 64 Byte
if (leng == KERNEL_DATA_LENG)
{
//8 byte kernel time
for (i = 0; i < 8; i++)
{
deviceinfo.info.kernel_time[i] = buf[i];
printf("kernel_time[%d] = 0x%x \n", i, deviceinfo.info.kernel_time[i]);
}
//4 byte status
for (i = 0; i < 4; i++)
{
deviceinfo.info.status[i] = buf[i+8];
printf("status[%d] = %d \n", i, deviceinfo.info.status[i]);
}
//32 byte status
for (i = 0; i < 32; i++)
{
deviceinfo.info.usb_name[i] = buf[i+12];
printf("usb_name[%d] = %c \n", i, deviceinfo.info.usb_name[i]);
}
ret = pthread_mutex_lock(&data_mutex); //get lock
if (ret != 0)
{
printf("Error on pthread_mutex_lock(), ret = %d\n", ret);
return (void *)-1;
}
//save
device->AppendDatainfo(deviceinfo); //问题
fifo_size = device->GetFifoSize();
ret = pthread_mutex_unlock(&data_mutex); //unlock
if (ret != 0)
{
printf("Error on pthread_mutex_unlock(), ret = %d\n", ret);
return (void *)-1;
}
if (isEmpty)
{
for (i = 0; i < isEmpty; i++)
pthread_cond_signal(&fifo_nonzero);
}
printf("Current BufferSize = %ld \n", fifo_size);
}
}
}
int main()
{
int ret;
pthread_t ptd;
//check device
USBMonitorDevice* monitorDevice = new USBMonitorDevice((char*)DEV_NAME);
if ( monitorDevice->InitSetup() != 0)
{
printf("USBMonitorDevice::InitSetup fail \n");
return -1;
}
printf("USBMonitorDevice::InitSetup OK \n");
DoUSBMonitor((void*)monitorDevice);
pthread_mutex_destroy(&data_mutex);
//not call here
return 0;
}
还有USBInfo.h
:
#ifndef __SUSPEND_INFO_H_
#define __SUSPEND_INFO_H_
#include <linux/types.h>
#include <linux/ioctl.h>
#define CMD_GET_STATUS _IOR(0xFF, 123, unsigned char)
#define DEV_NAME "/proc/usb_monitor"
//#define DEV_NAME "/mnt/test/data"
#define MAX_EPOLL_EVENTS 1
#define KERNEL_DATA_LENG 48
#define MONITOR_DISABLE 0x00
#define MONITOR_ENABLE 0xff
size_t BUFFER_SIZE = 512;
struct DataInfo
{
uint8_t kernel_time[8]; //8 Byte
uint8_t status[8]; //4 Byte
uint8_t usb_name[32]; // 32 Byte
};
class USBMonitorInfo {
public:
struct DataInfo info;
};
#endif
程序中实际上只需要更改DataInfo
的结构和KERNEL_DATA_LENG
的大小即可,另外RingBuffer
的大小BUFFER_SIZE
应该按照要求改为512
,其余都可以仿照老师给的程序完成.
这里有一个非常重要的知识,就是内存对齐:大多数编译器会按照成员中最大的基本类型进行对齐,以提高数据访问的效率,因此对于usb_message_t
实际上其大小为48
,因为enum
要被对其为8 Byte
:
struct usb_message_t
{
signed long long kernel_time; /* 8 Byte */
enum STATUS_TYPE status; /* 4 Byte */
char usb_name[32]; /* 32 Byte */
/* align 4 Byte */
};
尝试运行
cd ./drv
make
sudo insmod usb_monitor.ko
cd ../app
g++ -o USBMonitorService_Server USBMonitorService_Server.cpp
sudo ./USBMonitorService_Server
得到log
,拔插U盘得到结果:
epoll_create ok epollfd= 4
USBMonitorDevice::InitSetup OK
suspend_monitor epoll_wait...
kernel_time[0] = 0x8d
kernel_time[1] = 0xaf
kernel_time[2] = 0x2d
kernel_time[3] = 0xcc
kernel_time[4] = 0x29
kernel_time[5] = 0xf8
kernel_time[6] = 0x0
kernel_time[7] = 0x0
status[0] = 0
status[1] = 0
status[2] = 0
status[3] = 0
usb_name[0] = U
usb_name[1] = D
usb_name[2] = i
usb_name[3] = s
usb_name[4] = k
usb_name[5] =
usb_name[6] =
usb_name[7] =
usb_name[8] =
usb_name[9] =
usb_name[10] =
usb_name[11] =
usb_name[12] =
usb_name[13] =
usb_name[14] =
usb_name[15] =
usb_name[16] =
usb_name[17] =
usb_name[18] =
usb_name[19] =
usb_name[20] =
usb_name[21] =
usb_name[22] =
usb_name[23] =
usb_name[24] =
usb_name[25] =
usb_name[26] =
usb_name[27] =
usb_name[28] =
usb_name[29] =
usb_name[30] =
usb_name[31] =
Current BufferSize = 1
suspend_monitor epoll_wait...
kernel_time[0] = 0x8b
kernel_time[1] = 0xdc
kernel_time[2] = 0x87
kernel_time[3] = 0x62
kernel_time[4] = 0x2a
kernel_time[5] = 0xf8
kernel_time[6] = 0x0
kernel_time[7] = 0x0
status[0] = 1
status[1] = 0
status[2] = 0
status[3] = 0
usb_name[0] = U
usb_name[1] = D
usb_name[2] = i
usb_name[3] = s
usb_name[4] = k
usb_name[5] =
usb_name[6] =
usb_name[7] =
usb_name[8] =
usb_name[9] =
usb_name[10] =
usb_name[11] =
usb_name[12] =
usb_name[13] =
usb_name[14] =
usb_name[15] =
usb_name[16] =
usb_name[17] =
usb_name[18] =
usb_name[19] =
usb_name[20] =
usb_name[21] =
usb_name[22] =
usb_name[23] =
usb_name[24] =
usb_name[25] =
usb_name[26] =
usb_name[27] =
usb_name[28] =
usb_name[29] =
usb_name[30] =
usb_name[31] =
Current BufferSize = 2
运行结果截图
编译后生成文件截图
运行过程中dmesg
结果截图
运行之后bash
截图
总结
实际上需要改动的部分不多,但是难点在于在没有api reference 的情况下凭借对于C语言本身逻辑的熟悉读懂老师给的代码,并且在Linux源码的注释中找到各个API的用法,理解关于线程安全,notify,epoll和framebuffer,usb设备开发相关的知识.通过本次学习,基本理解了Linux
内核驱动的一些基本的流程,为后续继续学习Linux
驱动开发提供了思路和信心.