ldd3中scullv模块vm_operations_struct -> nopage到vm_operations_struct ->fault的变换

本文介绍如何将scullv设备驱动更新以适应现代Linux内核的变化,重点在于vm_operations_struct中操作nopage被fault替代的调整过程。文章详细说明了在main.c、scullv.h及mmap.c文件中的必要改动。

关于ldd3的scullv,在编译过程中,主要是vm_operations_struct中现在的kernel中已经没有.nopage这一operation了,其被 fault替换了。下面逐一说明:

(1)main.c中,主要就是就是上一篇文章提到的INIT_WORK, schedule_delayed_work等改变。可以参考逐一改变即可。

(2)在scullv.h中,需要包含头文件<linux/semaphore.h>,否则会提示struct semaphore sem没有定义之类的错误。

(3)关键的改变在mmap.c中,由于vm_operations_struct的变化,需要对mmap.c做如下变化:

a.包含头文件<linux/fs.h>,否则在函数scull_mmap中,会提示

        vma->vm_private_data = filp->private_data;

dereferencing pointer to incomplete type.这是因为其找不到struct file的定义。

b. 以前的函数

        struct page *scullv_vma_nopage(struct vm_area_struct *vma, unsigned long address, int *type)

现在其后面的参数address, type都可以通过结构struct vm_fault来获得。该函数需要改变为:

        static int scullv_vma_fault (struct vm_area_struct *vma, struct vm_fault *vmf)

其函数体也需要作出相应改变:

static int scullv_vma_fault(struct vm_area_struct *vma,
                            struct vm_fault *vmf)
{
    unsigned long offset;
    struct scullv_dev *ptr, *dev = vma->vm_private_data;
        struct page *page;
    void *pageptr = NULL; /* default to "missing" */
        pgoff_t pgoff = vmf->pgoff;

    down(&dev->sem);
    offset = (pgoff << PAGE_SHIFT) + (vma->vm_pgoff << PAGE_SHIFT);
    if (offset >= dev->size) goto out; /* out of range */

    /*
     * Now retrieve the scullv device from the list,then the page.
     * If the device has holes, the process receives a SIGBUS when
     * accessing the hole.
    */
   offset >>= PAGE_SHIFT; /* offset is a number of pages */
   for (ptr = dev; ptr && offset >= dev->qset;) {
        ptr = ptr->next;
        offset -= dev->qset;
    }
    if (ptr && ptr->data) pageptr = ptr->data[offset];
    if (!pageptr) goto out; /* hole or end-of-file */

    /*
     * After scullv lookup, "page" is now the address of the page
     * needed by the current process. Since it's a vmalloc address,
     * turn it into a struct page.
     */
     page = vmalloc_to_page(pageptr);
     if (!page)
          return VM_FAULT_SIGBUS;

     /* got it, now increment the count */
     vmf->page = page;

out:
     up(&dev->sem);
     return 0;
}

c. 在结构scull_vm_ops初始化时:

去掉.nopage = scullv_vma_nopage;

修改为.fault = scullv_vma_fault;

<think> 我们正在解决用户空间如何获取驱动中通过`input_event`上报的状态值(具体为`EV_MSC`类型,`MSC_RAW`事件码)的问题。驱动上报的代码为: `input_event(xs9922->input_dev, EV_MSC, MSC_RAW, xs9922->detect_status);` 在用户空间,我们需要通过读取输入设备节点(如`/dev/input/eventX`)来获取这些事件。以下将详细说明步骤和方法。 ### 一、用户空间读取事件的基本方法 #### 1. **查找设备节点** 首先需要确定设备对应的`eventX`节点。可以通过以下方式: ```bash # 查看所有输入设备信息 cat /proc/bus/input/devices ``` 在输出中搜索设备名称(如`xs9922_sensor`),记录其对应的`Handlers`,例如`event5`。 #### 2. **使用`evtest`工具测试(推荐)** ```bash sudo apt install evtest sudo evtest /dev/input/event5 ``` 当驱动上报事件时,工具会显示原始事件数据,例如: ``` Event: time 1620000000.123456, type 4 (EV_MSC), code 4 (MSC_RAW), value 1 ``` #### 3. **编写用户空间程序读取** 以下是一个C语言示例程序,使用`libevdev`库(推荐)来读取事件: ```c #include <fcntl.h> #include <unistd.h> #include <linux/input.h> #include <libevdev/libevdev.h> int main() { struct libevdev *dev = NULL; int fd = open("/dev/input/event5", O_RDONLY|O_NONBLOCK); if (fd < 0) { perror("打开设备失败"); return -1; } int rc = libevdev_new_from_fd(fd, &dev); if (rc < 0) { perror("初始化libevdev失败"); close(fd); return -1; } printf("设备名称: %s\n", libevdev_get_name(dev)); printf("设备ID: bus %04x vendor %04x product %04x\n", libevdev_get_id_bustype(dev), libevdev_get_id_vendor(dev), libevdev_get_id_product(dev)); struct input_event ev; while (1) { rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); if (rc == LIBEVDEV_READ_STATUS_SUCCESS) { if (ev.type == EV_MSC && ev.code == MSC_RAW) { printf("检测到状态变化: value = %d\n", ev.value); } } usleep(100000); // 避免CPU占用过高 } libevdev_free(dev); close(fd); return 0; } ``` #### 4. **使用`ioctl`直接读取(不推荐)** 如果不使用`libevdev`,可以直接读取`input_event`结构体: ```c #include <linux/input.h> #include <fcntl.h> #include <unistd.h> int main() { int fd = open("/dev/input/event5", O_RDONLY); struct input_event ev; while (1) { if (read(fd, &ev, sizeof(ev)) == sizeof(ev)) { if (ev.type == EV_MSC && ev.code == MSC_RAW) { printf("状态值: %d\n", ev.value); } } } close(fd); return 0; } ``` ### 二、常见问题及解决 #### 1. **权限不足** - **现象**:无法打开`/dev/input/eventX` - **解决**: ```bash # 临时方案 sudo chmod 666 /dev/input/eventX # 永久方案(创建udev规则) echo &#39;KERNEL=="event*", SUBSYSTEM=="input", MODE="0666"&#39; | sudo tee /etc/udev/rules.d/99-input.rules sudo udevadm control --reload-rules ``` #### 2. **读取不到事件** - **驱动层检查**: - 确保驱动中调用了`input_sync()`提交事件 - 检查驱动是否成功注册输入设备(`dmesg | grep input`) - **用户层检查**: - 确认打开的`eventX`节点正确 - 使用`evtest`验证驱动是否上报 #### 3. **事件延迟或丢失** - 使用`O_NONBLOCK`非阻塞模式配合`select`/`poll`提高效率: ```c #include <sys/select.h> fd_set fds; FD_ZERO(&fds); FD_SET(fd, &fds); struct timeval tv = { .tv_sec = 5, .tv_usec = 0 }; // 5秒超时 if (select(fd+1, &fds, NULL, NULL, &tv) > 0) { if (FD_ISSET(fd, &fds)) { read(fd, &ev, sizeof(ev)); // 处理事件 } } ``` ### 三、状态解析建议 驱动上报的`detect_status`值含义需与用户空间约定一致,例如: ```c #define STATUS_NORMAL 0 #define STATUS_FAULT 1 #define STATUS_HOTPLUG 2 ``` 用户空间程序根据读取到的`ev.value`执行相应操作。 ### 四、替代方案 #### 1. **通过sysfs暴露状态** 如果事件上报不适用,可在驱动中创建sysfs属性: ```c // 驱动中 static ssize_t detect_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", xs9922->detect_status); } static DEVICE_ATTR_RO(detect); ``` 用户空间通过读取`/sys/class/.../xs9922/detect`获取状态。 #### 2. **Netlink套接字** 适用于高频或复杂数据,但实现较复杂。 --- §§相关问题§§ 1. 如何在内核驱动中正确注册输入设备? 2. 用户空间使用`select`监听输入设备事件的完整示例? 3. 除了输入子系统,Linux内核还有哪些事件上报机制? [^1]: [Linux输入子系统文档](https://www.kernel.org/doc/html/latest/input/input.html) [^2]: [libevdev官方教程](https://www.freedesktop.org/software/libevdev/doc/latest/index.html) [^3]: [Linux设备驱动开发详解(第3版)](https://lwn.net/Kernel/LDD3) </think>### 用户空间获取驱动中 `input_event` 状态的方法 在用户空间获取 `input_event(xs9922->input_dev, EV_MSC, MSC_RAW, xs9922->detect_status)` 上报的状态数据,需通过 Linux 输入子系统的设备节点实现。以下是完整解决方案: --- ### 一、核心方法:读取 `/dev/input/eventX` 设备节点 #### 1. **定位设备节点** ```bash # 查看所有输入设备 cat /proc/bus/input/devices | grep -A5 "xs9922" # 输出示例: N: Name="xs9922_sensor" H: Handlers=event5 # 设备节点为 /dev/input/event5 ``` #### 2. **使用 C 语言读取事件** ```c #include <linux/input.h> #include <fcntl.h> #include <unistd.h> int main() { int fd = open("/dev/input/event5", O_RDONLY); struct input_event ev; while (1) { read(fd, &ev, sizeof(ev)); if (ev.type == EV_MSC && ev.code == MSC_RAW) { printf("检测状态: %d\n", ev.value); } } close(fd); return 0; } ``` #### 3. **使用 Python 读取(推荐)** ```python import struct from fcntl import ioctl # 打开设备 with open(&#39;/dev/input/event5&#39;, &#39;rb&#39;) as f: # 事件结构:时间戳(8B) + 类型(2B) + 编码(2B) + 值(4B) event_format = &#39;llHHI&#39; event_size = struct.calcsize(event_format) while True: data = f.read(event_size) ts_sec, ts_usec, ev_type, code, value = struct.unpack(event_format, data) if ev_type == 0x04 and code == 0x04: # EV_MSC=0x04, MSC_RAW=0x04 print(f"状态更新: {value}") ``` --- ### 二、高级方法:使用 libevdev 库 ```c #include <libevdev/libevdev.h> int main() { struct libevdev *dev = NULL; int fd = open("/dev/input/event5", O_RDONLY); libevdev_new_from_fd(fd, &dev); struct input_event ev; while (1) { libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); if (ev.type == EV_MSC && ev.code == libevdev_event_code_from_name(EV_MSC, "MSC_RAW")) { printf("DRIVER STATUS: %d\n", ev.value); } } libevdev_free(dev); close(fd); } ``` **优势**:自动处理协议细节,支持事件名称解析[^1]。 --- ### 三、调试与权限管理 #### 1. **实时调试工具** ```bash # 安装调试工具 sudo apt install evtest # 监听事件 sudo evtest /dev/input/event5 ``` **输出示例**: ``` Event: time 168000000.123456, type 4 (EV_MSC), code 4 (MSC_RAW), value 1 ``` #### 2. **永久权限配置** ```bash # 创建UDEV规则 echo &#39;SUBSYSTEM=="input", ATTRS{name}=="xs9922_sensor", MODE="0666"&#39; | sudo tee /etc/udev/rules.d/99-xs9922.rules # 重载规则 sudo udevadm control --reload ``` --- ### 四、状态值解析参考 驱动与用户空间需约定状态含义(与驱动保持一致): ```c // 驱动中定义(需共享给用户空间) #define STATUS_NORMAL 0 // 正常状态 #define STATUS_FAULT 1 // 硬件故障 #define STATUS_HOTPLUG 2 // 设备热插拔 ``` --- ### 五、替代方案 #### 1. **通过 sysfs 暴露状态** ```c // 驱动中添加 static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", xs9922->detect_status); } static DEVICE_ATTR_RO(status); // 用户空间读取 cat /sys/class/sensors/xs9922/status ``` #### 2. **Netlink 套接字** 适合高频状态更新,需驱动实现自定义协议。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值