青岛高端网站制作,php 禁止电脑访问网站,网站建设服务器的选择方式包括哪些,django做网站好吗前面我们介绍到怎么编译出内核模块.ko文件#xff0c;然后还加载了这个驱动模块。但是#xff0c;那个驱动代码还不完善#xff0c;驱动写好后怎么在应用层使用也没有介绍。
字符设备抽象
Linux内核中将字符设备抽象成一个具体的数据结构#xff08;struct cdev#xff…前面我们介绍到怎么编译出内核模块.ko文件然后还加载了这个驱动模块。但是那个驱动代码还不完善驱动写好后怎么在应用层使用也没有介绍。
字符设备抽象
Linux内核中将字符设备抽象成一个具体的数据结构struct cdev我们可以理解为字符设备对象cdev记录了字符设备的相关信息设备号、内核对象字符设备的打开、读写和关闭等操作接口file_operations在我们想要添加一个字符设备时就是将这个对象注册到内核中通过创建一个文件设备节点绑定对象的cdev当我们对这个文件进行读写操作时就可以通过虚拟文件系统在内核中找到这个对象及其操作接口从而控制设备。
在Linux中我们使用设备编号来表示设备主设备号区分设备类别次设备号标识具体的设备。cdev结构体被内核用来记录设备号而在使用设备时我们通常会打开设备节点通过设备节点的inode结构体和file结构体最终找到file_operations结构体并从file_operations结构体中得到操作设备的具体方法。
设备号
Linux根目录下有/dev这个文件夹专门用来存放设备中的驱动程序我们可以使用ls -l以列表的形式列出所有设备。 一般来说主设备号指向设备的驱动程序次设备号指向某个具体的设备。如上图i2c-3和i2c-4属于不同设备但是共用一套驱动程序。
typedef u32 __kernel_dev_t;typedef __kernel_dev_t dev_t;#define MINORBITS 20
#define MINORMASK ((1U MINORBITS) - 1)#define MAJOR(dev) ((unsigned int) ((dev) MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) MINORMASK))
#define MKDEV(ma,mi) (((ma) MINORBITS) | (mi))在内核中dev_t用来表示设备编号dev_t是一个32位的数其中高12位表示主设备号低20位表示次设备号。 也就是理论上主设备号取值范围0-2^12次设备号0-2 ^ 20。
在kdev_t中设备编号通过移位操作最终得到主/次设备号码同样主/次设备号也可以通过位运算变成dev_t类型的设备编号 具体实现参看下面代码MAJOR(dev)、MINOR(dev)和MKDEV(ma,mi)。
cdev结构体
内核通过一个散列表来记录设备编号。
struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
} __randomize_layout;struct kobject kobj 内嵌的内核对象通过它将设备统一加入到“Linux设备驱动模型”中管理(如对象的引用计数、电源管理、热插拔、生命周期、与用户通信等)。struct module *owner 字符设备驱动程序所在的内核模块对象的指针。const struct file_operations *ops 文件操作是字符设备驱动中非常重要的数据结构在应用程序通过文件系统(VFS)呼叫到设备设备驱动程序中实现的文件操作类函数过程中ops起着桥梁纽带作用VFS与文件系统及设备文件之间的接口是file_operations结构体成员函数这个结构体包含了对文件进行打开、关闭、读写、控制等一系列成员函数。struct list_head list 用于将系统中的字符设备形成链表(这是个内核链表的一个链接因子可以再内核很多结构体中看到这种结构的身影)。dev_t dev 字符设备的设备号有主设备和次设备号构成。unsigned int count 属于同一主设备好的次设备号的个数用于表示设备驱动程序控制的实际同类设备的数量。
设备节点
设备节点是啥呢其实就是设备文件。设备节点被创建在/dev目录下是连接内核与用户层的枢纽。
加载了设备驱动后我们可以通过mknod命令来手动创建设备节点也可以通过代码实现自动创建设备节点。
应用程序通过一组标准化的调用执行访问设备这些调用独立于任何特点的驱动程序。而驱动程序负责将这些标准调用映射到实际硬件的特有操作。
数据结构
在驱动开发过程中不可避免要涉及到三个重要的内核数据结构。
文件操作方式file_operations文件描述结构体struct fileinode结构体struct inode
file_operations结构体 file_operations就是把系统调用和驱动程序关联起来的关键数据结构。以下代码中只列出本章使用到的部分函数。
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 *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);int (*open) (struct inode *, struct file *)int (*release) (struct inode *, struct file *);
};llseek 用于修改文件的当前读写位置并返回偏移后的位置。参数file传入了对应的文件指针我们可以看到以上代码中所有的函数都有该形参通常用于读取文件的信息如文件类型、读写权限参数loff_t指定偏移量的大小参数int是用于指定新位置指定成从文件的某个位置进行偏移SEEK_SET表示从文件起始处开始偏移SEEK_CUR表示从当前位置开始偏移SEEK_END表示从文件结尾开始偏移。read 用于读取设备中的数据并返回成功读取的字节数。该函数指针被设置为NULL时会导致系统调用read函数报错提示“非法参数”。该函数有三个参数file类型指针变量char__user*类型的数据缓冲区__user用于修饰变量表明该变量所在的地址空间是用户空间的。内核模块不能直接使用该数据需要使用copy_to_user函数来进行操作。size_t类型变量指定读取的数据大小。write 用于向设备写入数据并返回成功写入的字节数write函数的参数用法与read函数类似不过在访问__user修饰的数据缓冲区需要使用copy_from_user函数。unlocked_ioctl 提供设备执行相关控制命令的实现方法它对应于应用程序的fcntl函数以及ioctl函数。在 kernel 3.0 中已经完全删除了 struct file_operations 中的 ioctl 函数指针。open 设备驱动第一个被执行的函数一般用于硬件的初始化。如果该成员被设置为NULL则表示这个设备的打开操作永远成功。release 当file结构体被释放时将会调用该函数。与open函数相反该函数可以用于释放。
在使用read和write函数时需要使用copy_to_user函数以及copy_from_user函数来进行数据访问写入/读取成 功函数返回0失败则会返回未被拷贝的字节数。
static inline long copy_from_user(void *to, const void __user * from, unsigned long n)
static inline long copy_to_user(void __user *to, const void *from, unsigned long n)file结构体
内核中用file结构体来表示每个打开的文件每打开一个文件内核会创建一个结构体并将对该文件上的操作函数传递给 该结构体的成员变量f_op当文件所有实例被关闭后内核会释放这个结构体。如下代码中只列出了我们本章需要了解的成员变量。
struct file {
{......}
const struct file_operations *f_op;
/* needed for tty driver, and maybe others */
void *private_data;
{......}
};f_op存放与文件操作相关的一系列函数指针如open、read、wirte等函数。private_data该指针变量只会用于设备驱动程序中内核并不会对该成员进行操作。因此在驱动程序中通常用于指向描述设备的结构体。
inode结构体
VFS inode 包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。 它是Linux 管理文件系统的最基本单位也是文件系统连接任何子目录、文件的桥梁。 内核使用inode结构体在内核内部表示一个文件。
inode结构体包含了一大堆文件相关的信息但是就针对驱动代码来说我们只要关心其中的两个域即可
struct inode {dev_t i_rdev;{......}union {struct pipe_inode_info *i_pipe; /* linux内核管道 */struct block_device *i_bdev; /* 如果这是块设备则设置并使用 */struct cdev *i_cdev; /* 如果这是字符设备则设置并使用 */char *i_link;unsigned i_dir_seq;};{......}
};dev_t i_rdev 表示设备文件的结点这个域实际上包含了设备号。struct cdev *i_cdev struct cdev是内核的一个内部结构它是用来表示字符设备的当inode结点指向一个字符设备文件时此域为一个指向inode结构的指针。
字符设备驱动程序框架
在驱动入口函数中
分配设备号注册字符设备创建设备节点
在驱动出口函数中
删除字符设备归还设备号删除设备节点
已弃用的注册字符设备函数
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)这一组函数时Linux版本2.4之前的注册方式每注册一个字符设备都还会连续注册0~255个次设备使他们绑定在同一个file_operationis操作方法结构体上而我们在大多数情况下都只会用到极少的次设备号所以会造成资源的极大浪费。所以现在很少用它。
字符设备号的申请和释放
静态申请
register_chrdev_region函数用于静态地为一个字符设备申请一个或多个设备编号
int register_chrdev_region(dev_t from, unsigned count, const char *name)
void unregister_chrdev_region(dev_t from, unsigned count)参数
fromdev_t类型的变量用于指定字符设备的起始设备号如果要注册的设备号已经被其他的设备注册了那么就会导致注册失败。count指定要申请的设备号个数count的值不可以太大否则会与下一个主设备号重叠。name用于指定该设备的名称。
返回值 返回0表示申请成功失败则返回错误码。
静态申请设备号是我们人为确定好了设备号再去申请设备号属于静态申请。可以通过命令cat /proc/devices来查询内核已使用的主设备号。 动态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
void unregister_chrdev_region(dev_t from, unsigned count)参数
dev指向dev_t类型数据的指针变量用于存放分配到的设备编号的起始值baseminor次设备号的起始值通常情况下设置为0count、name同register_chrdev_region类型用于指定需要分配的设备编号的个数以及设备的名称。
返回值 返回0表示申请成功失败则返回错误码
字符设备的注册和注销
前面我们已经提到过了编写一个字符设备最重要的事情就是要实现file_operations这个结构体中的函数。 实现之后如何将该结构体与我们的字符设备结构体相关联呢内核提供了cdev_init函数来实现这个过程。
void cdev_init(struct cdev *cdev, const struct file_operations *fops) 参数
cdevstruct cdev类型的指针变量指向需要关联的字符设备结构体fopsfile_operations类型的结构体指针变量一般将实现操作该设备的结构体file_operations结构体作为实参。
返回值 无
cdev_add函数用于向内核的cdev_map散列表添加一个新的字符设备
int cdev_add(struct cdev *p, dev_t dev, unsigned count)参数
pstruct cdev类型的指针用于指定需要添加的字符设备devdev_t类型变量用于指定设备的起始编号count指定注册多少个设备。
返回值 错误码
从系统中删除cdevcdev设备将无法再打开但任何已经打开的cdev将保持不变 即使在cdev_del返回后它们的FOP仍然可以调用。
void cdev_del(struct cdev *p)参数
pstruct cdev类型的指针用于指定需要删除的字符设备
返回值 无
设备节点的创建和销毁
创建类的定义在/linux-4.1.15/include/linux/device.h中。
#define class_create(owner, name)
({ static struct lock_class_key __key; __class_create(owner, name, __key);
})删除类
extern void class_destroy(struct class *cls)struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)参数
class指向这个设备应该注册到的struct类的指针parent指向此新设备的父结构设备(如果有)的指针devt要添加的char设备号drvdata要添加到设备进行回调的数据fmt输入设备名称。
返回值 成功时返回 struct device 结构体指针, 错误时返回ERR_PTR().
删除使用device_create函数创建的设备
void device_destroy(struct class *class, dev_t devt)参数
class指向注册此设备的struct类的指针devt以前注册的设备号
返回值 无
除了使用代码创建设备节点还可以使用mknod命令创建设备节点。
用法mknod 设备名 设备类型 主设备号 次设备号
如
mknod /dev/haptics c 200 0这个class和device具体是什么我们后面再介绍目前我们只管这样用就行。
源码
/** Silicon Integrated Co., Ltd haptic sih688x haptic driver file** Copyright (c) 2021 kugua daokuan.zhusi-in.com** This program is free software; you can redistribute it and/or modify it* under the terms of the GNU General Public License version 2 as published by* the Free Software Foundation*/#include linux/init.h //包含宏定义的头文件
#include linux/module.h //包含初始化加载模块的头文件
#include linux/fs.h
#include linux/kdev_t.h
#include linux/cdev.h
#include linux/device.h#define HAPTICS_DEV_MAJOR 200
#define HAPTICS_DEV_MINOR 0
#define HAPTICS_DEV_NAME haptics
#define HAPTICS_DEV_CNT 1#define HAPTICS_DEV_CLASS_NAME haptics
#define HAPTICS_DEV_NODE_NAME haptics#define BUFF_SIZE 128
//数据缓冲区
static char vbuf[BUFF_SIZE]this is driver;static dev_t dev_id;//设备号
static struct cdev haptics_dev;//设备
struct class *class;
struct device *device; static int major HAPTICS_DEV_MAJOR;//主设备号
static int minor HAPTICS_DEV_MINOR;//次设备号//打开设备
static int haptics_open(struct inode* inode,struct file * filp)
{printk(%s\n,__FUNCTION__);return 0;
}//关闭设备
static int haptics_release(struct inode* inode ,struct file* filp)
{printk(%s\n,__FUNCTION__);return 0;
}//读设备
static ssize_t haptics_read(struct file* filp, char __user *buf,size_t count,loff_t* ppos)
{unsigned long p *ppos;int ret; int tmp count ;if (p BUFF_SIZE)return 0;if (tmp BUFF_SIZE - p)tmp BUFF_SIZE - p;ret copy_to_user(buf, vbufp, tmp);*ppos tmp;return tmp;
}//写设备
static ssize_t haptics_write(struct file* filp,const char __user *buf,size_t count,loff_t* ppos)
{unsigned long p *ppos;int ret;int tmp count ;if (p BUFF_SIZE)return 0;if (tmp BUFF_SIZE - p)tmp BUFF_SIZE - p;memset(vbuf,0,BUFF_SIZE);ret copy_from_user(vbuf, buf, tmp);*ppos tmp;return tmp;
}static struct file_operations haptics_fops
{.owner THIS_MODULE,.open haptics_open,.release haptics_release,.read haptics_read,.write haptics_write,
};static int __init haptics_init(void)
{int ret 0;//内核层只能使用printk不能使用printfprintk(KERN_EMERG %s\n,__FUNCTION__); //输出等级为0//申请设备号if(major){dev_id MKDEV(major,minor);//静态申请ret register_chrdev_region(dev_id, HAPTICS_DEV_CNT, HAPTICS_DEV_NAME);if(0 ret){printk(register_chrdev_region ok,major%d minor%d\n,MAJOR(dev_id),MINOR(dev_id));}}else{//动态申请ret alloc_chrdev_region(dev_id, minor, HAPTICS_DEV_CNT, HAPTICS_DEV_NAME);if(0 ret){printk(alloc_chrdev_region ok,major%d minor%d\n,MAJOR(dev_id),MINOR(dev_id));}}//注册字符设备haptics_dev.owner THIS_MODULE;cdev_init(haptics_dev,haptics_fops);cdev_add(haptics_dev,dev_id,HAPTICS_DEV_CNT);//自动创建设备节点class class_create(THIS_MODULE,HAPTICS_DEV_CLASS_NAME);device device_create(class,NULL,dev_id,NULL,HAPTICS_DEV_NODE_NAME);return 0;
}static void __exit haptics_exit(void)
{printk(KERN_EMERG %s\n,__FUNCTION__); //输出等级为0//释放设备号unregister_chrdev_region(dev_id,HAPTICS_DEV_CNT);//注销字符设备cdev_del(haptics_dev);//删除设备节点device_destroy(class,dev_id);class_destroy(class);
}module_init(haptics_init);//驱动入口
module_exit(haptics_exit);//驱动出口MODULE_AUTHOR(daokuan.zhugsi-in.com);//声明作者信息
MODULE_DESCRIPTION(Haptic Driver V1.0.0); //对这个模块作一个简单的描述
MODULE_LICENSE(GPL v2);//声明开源许可证// GPL 是指明 这是GNU General Public License的任意版本// “GPL v2” 是指明 这仅声明为GPL的第二版本还有应用层代码
#include sys/types.h
#include sys/stat.h
#include stdio.h
#include stdlib.h
#include string.h
#include fcntl.h
#include unistd.h
/*
argc:应用程序参数个数包括应用程序本身
argv[]:具体的参数内容字符串形式
./main filename r:w r表示读 w表示写
*/int main(int argc,char * argv[])
{char *wbuf Hello World\n;char rbuf[128];char* filename;int fd0;if(argc!3){printf(error usage\n);return -1;}filenameargv[1];fd open(filename,O_RDWR);if(fd0){printf(can not open file %s\n,filename);return -2;}if(0 strcmp(argv[2],r)){read(fd, rbuf, 128);//打印读取的内容printf(The content : %s\n, rbuf);}else if(0 strcmp(argv[2],w)){write(fd, wbuf, strlen(wbuf));}else{printf(error usage\n);}close(fd);
}rootRK3588:/# insmod haptics.ko
[ 53.773224] haptics_init
[ 53.773264] register_chrdev_region ok,major200 minor0
rootRK3588:/# ./main /dev/haptics w
[ 56.270812] haptics_open
[ 56.270867] haptics_release
rootRK3588:/# ./main /dev/haptics r
[ 59.249993] haptics_open
The content : Hello World[ 59.250155] haptics_release
rootRK3588:/#优化源码
对于上述驱动代码有几点需要优化
class名称、node名称一般与驱动名称一致我们定义了很多全局变量其实我们可以抽象出一个设备结构体这个结构体去包含那些成员变量一套驱动代码是需要兼容多个设备的所以应该尽量少用全局变量需要思考如何兼容多设备
/** Silicon Integrated Co., Ltd haptic sih688x haptic driver file** Copyright (c) 2021 heater daokuan.zhusi-in.com** This program is free software; you can redistribute it and/or modify it* under the terms of the GNU General Public License version 2 as published by* the Free Software Foundation*/#include linux/init.h //包含宏定义的头文件
#include linux/module.h //包含初始化加载模块的头文件
#include linux/fs.h
#include linux/kdev_t.h
#include linux/cdev.h
#include linux/device.h#define HAPTICS_CDEV_MAJOR 200
#define HAPTICS_CDEV_MINOR 0
#define HAPTICS_CDEV_NAME haptics
#define HAPTICS_CDEV_CNT 1#define BUFF_SIZE 128struct haptics_dev
{struct cdev haptics_cdev;//字符设备dev_t dev_id;//设备号struct class *class; //类struct device *device; //设备int major;//主设备号int minor;//次设备号char vbuf[BUFF_SIZE];//数据缓冲区
};struct haptics_dev mdev;//定义一个设备结构体//打开设备
static int haptics_open(struct inode* inode,struct file * filp)
{printk(%s\n,__FUNCTION__);return 0;
}//关闭设备
static int haptics_release(struct inode* inode ,struct file* filp)
{printk(%s\n,__FUNCTION__);return 0;
}//读设备
static ssize_t haptics_read(struct file* filp, char __user *buf,size_t count,loff_t* ppos)
{unsigned long p *ppos;int ret; int tmp count ;if (p BUFF_SIZE)return 0;if (tmp BUFF_SIZE - p)tmp BUFF_SIZE - p;ret copy_to_user(buf, mdev.vbufp, tmp);*ppos tmp;return tmp;
}//写设备
static ssize_t haptics_write(struct file* filp,const char __user *buf,size_t count,loff_t* ppos)
{unsigned long p *ppos;int ret;int tmp count ;if (p BUFF_SIZE)return 0;if (tmp BUFF_SIZE - p)tmp BUFF_SIZE - p;memset(mdev.vbuf,0,BUFF_SIZE);ret copy_from_user(mdev.vbuf, buf, tmp);*ppos tmp;return tmp;
}static struct file_operations haptics_fops
{.owner THIS_MODULE,.open haptics_open,.release haptics_release,.read haptics_read,.write haptics_write,
};static int __init haptics_init(void)
{int ret 0;//内核层只能使用printk不能使用printfprintk(KERN_EMERG %s\n,__FUNCTION__); //输出等级为0mdev.major HAPTICS_CDEV_MAJOR;//预定义的主设备号mdev.minor HAPTICS_CDEV_MINOR;//预定义的次设备号//申请设备号if(mdev.major)//如果预定义的主设备号不为0则需要静态申请设备号{mdev.dev_id MKDEV(mdev.major,mdev.minor);//静态申请ret register_chrdev_region(mdev.dev_id, HAPTICS_CDEV_CNT, HAPTICS_CDEV_NAME);if(0 ret){printk(register_chrdev_region ok,major%d minor%d\n,MAJOR(mdev.dev_id),MINOR(mdev.dev_id));}}else//如果预定义的主设备号为0则需要动态申请设备号{//动态申请ret alloc_chrdev_region(mdev.dev_id, mdev.minor, HAPTICS_CDEV_CNT, HAPTICS_CDEV_NAME);if(0 ret){mdev.major MAJOR(mdev.dev_id);mdev.minor MINOR(mdev.dev_id);printk(alloc_chrdev_region ok,major%d minor%d\n,MAJOR(mdev.dev_id),MINOR(mdev.dev_id));}}//注册字符设备mdev.haptics_cdev.owner THIS_MODULE;cdev_init(mdev.haptics_cdev,haptics_fops);cdev_add(mdev.haptics_cdev,mdev.dev_id,HAPTICS_CDEV_CNT);//自动创建设备节点mdev.class class_create(THIS_MODULE,HAPTICS_CDEV_NAME);mdev.device device_create(mdev.class,NULL,mdev.dev_id,NULL,HAPTICS_CDEV_NAME);return 0;
}static void __exit haptics_exit(void)
{//释放设备号unregister_chrdev_region(mdev.dev_id,HAPTICS_CDEV_CNT);//注销字符设备cdev_del(mdev.haptics_cdev);//删除设备节点device_destroy(mdev.class,mdev.dev_id);class_destroy(mdev.class);printk(KERN_EMERG %s\n,__FUNCTION__); //输出等级为0
}module_init(haptics_init);//驱动入口
module_exit(haptics_exit);//驱动出口MODULE_AUTHOR(daokuan.zhugsi-in.com);//声明作者信息
MODULE_DESCRIPTION(Haptics Driver V1.0.0); //对这个模块作一个简单的描述
MODULE_LICENSE(GPL v2);//声明开源许可证// GPL 是指明 这是GNU General Public License的任意版本// “GPL v2” 是指明 这仅声明为GPL的第二版本