Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

前言

  驱动写好后,用户层使用系统函数调用操作相关驱动从而实现与系统内核的关联,本篇主要就是理解清楚驱动如何让用户编程来实现与内核的交互。

 

杂项设备文件操作集

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);         ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,                         loff_t, 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          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函数,其他的以此类推,还比较好理解。

 

Linux文件操作集的意义

概述

  Linux一切都是文件,都有对应的打开、关闭和读写等相关操作,而这些操作都是使用打开文件后的句柄来表示,那么函数再根据句柄的类型,如打开的是杂项设备驱动,就会去调用杂项设备操作文件字符集里面对应的函数来执行操作了。
  在编程的时候会使用open打开一个设备节点(可以是文件打开,可以打开设备),这时候返回得到设备节点句柄标识fd(失败是-1),然后使用fd去read、write等各种操作则会相当于调用这个设备驱动里面文件操作集的read、write。
  下面是常用的文件操作。

open函数(实现测试)

int (*open) (struct inode *, struct file *);

read函数(实现测试)

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *)

write函数(实现测试)

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

poll/select函数(本篇没写)

__poll_t (*poll) (struct file *, struct poll_table_struct *);

ioctl函数(本篇没写)

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

close函数(实现测试)

int (*release) (struct inode *, struct file *);

 

驱动模板准备

  首先复制之前的registerMiscDev的驱动,改个名字为:testFileOpts:

cd ~/work/drive cp -arf registerMiscDev testFileOpts cd testFileOpts make clean mv registerMiscDev testFileOpts.c 

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

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

gedit Makefile   

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

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

gedit testFileOpts.c 

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

#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_testFileOpt", // 设备节点名称     .fops = &misc_fops,  // 这个变量记住,自己起的,步骤二使用 };  static int registerMiscDev_init(void) {      int ret;     // 在内核里面无法使用基础c库printf,需要使用内核库printk     printk("Hello, I’m hongPangZi, registerMiscDev_initn");	     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); 

 

杂项设备添加常用操作集open函数Demo

  注意,要是调用的函数没有写,则不会报错也不会有其他操作反应,所以并不是所有函数都是必须写的。

步骤一:实现open函数

// int (*open) (struct inode *, struct file *); int misc_open(struct inode * pInode, struct file * pFile) {     printk("int misc_open(struct inode * pInode, struct file * pFile)");     return 0; } 

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

步骤二:(关键)赋值到文件操作集指针

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

步骤三:编译加载驱动

  先编译试试:
  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

  然后加载驱动:

sudo insmod tesFileOpts.ko 

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

  这时候,设备节点注册成功了。

步骤四:在程序中调用打开设备节点open

  本步骤是c语言编程,使用linux系统函数打开设备节点:
  新建文件:

vi test.c 

  输入代码:

#include <stdio.h> #include <unistd.h> #include <fcntl.h>  int main(int argc, char **argv) {   int fd;   const char devPath[] = "/dev/register_hongPangZi_testFileOpt";   fd = open(devPath, O_RDWR);   if(fd < 0)   {     printf("fialed to open %sn", devPath);     return -1;   } else{     printf("Succeed to open %sn", devPath);   }   return 0; } 

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

  编译:

gcc test.c 

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

  默认输出就是a.out,下面运行一下:
  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

  无法运行,是因为ubuntu对设备需要管理员权限,管理员权限运行:
  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

  查看内核打印输出(这里出现没有打印输出,查看“入坑一”):
  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

  至此,从用户编程层如何对设备结点,然后调用到内核层函数就基本清楚了。

 

补充其他函数Demo

补充read、write

#include <linux/init.h> #include <linux/module.h>  #include <linux/miscdevice.h> #include <linux/fs.h>  // int (*open) (struct inode *, struct file *); int misc_open(struct inode * pInode, struct file * pFile) {     printk("int misc_open(struct inode * pInode, struct file * pFilen)");     return 0; }  // int (*release) (struct inode *, struct file *); int misc_release(struct inode * pInde, struct file * pFile) {     printk("int misc_release(struct inode * pInde, struct file * pFilen)");     return 0; }  // ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t misc_read(struct file * pFile, char __user * pUser, size_t size, loff_t *pLofft) {     printk("ssize_t misc_read(struct file * pFile, char __user * pUser, size_t size, loff_t *pLofft)n");     return 0; }  // ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t misc_write(struct file * pFile, const char __user * pUser, size_t size, loff_t *pLofft) {     printk("ssize_t misc_write(struct file * pFile, const char __user * pUser, size_t size, loff_t *pLofft)n");     return 0; }  struct file_operations misc_fops = {   .owner = THIS_MODULE,   .open = misc_open,   .release = misc_release,   .read = misc_read,   .write = misc_write, };  struct miscdevice misc_dev = {     .minor = MISC_DYNAMIC_MINOR, // 这个宏是动态分配次设备号,避免冲突     .name = "register_hongPangZi_testFileOpt", // 设备节点名称     .fops = &misc_fops,  // 这个变量记住,自己起的,步骤二使用 };  static int registerMiscDev_init(void) {      int ret;     // 在内核里面无法使用基础c库printf,需要使用内核库printk     printk("Hello, I’m hongPangZi, registerMiscDev_initn");	     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); 

修改test.c测试驱动源码

#include <stdio.h> #include <unistd.h> #include <fcntl.h>  int main(int argc, char **argv) {   int fd;   char buf[32] = {0};    const char devPath[] = "/dev/register_hongPangZi_testFileOpt";   fd = open(devPath, O_RDWR);   if(fd < 0)   {     printf("Failed to open %sn", devPath);     return -1;   }else{     printf("Succeed to open %sn", devPath);   }    read(fd, buf, sizeof(buf));   write(fd, buf, sizeof(buf));    close(fd);   printf("exitn");   fd = -1;   return 0; } 

查看输出

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

 

入坑

入坑一:内核未打印open函数

问题

  程序打开设别节点,未打印open函数

原因

  打开函数没有赋值给文件操作集。

解决

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

入坑二:dmesg少了close的release打印

问题

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

测试

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

  研究了dmesg,就是没出来,这个不清楚了,后来问了驱动大佬,提醒是可能是换行的问题,后加上可以了。

解决方法

  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo
  Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

发表评论

评论已关闭。

相关文章