一、Linux内核对设备的分类
linux的文件种类:
-
-:普通文件
-
d:目录文件
-
p:管道文件
-
s:本地socket文件
-
l:链接文件
-
c:字符设备
-
b:块设备
Linux内核按驱动程序实现模型框架的不同,将设备分为三类:
-
字符设备:按字节流形式进行数据读写的设备,一般情况下按顺序访问,数据量不大,一般不设缓存
-
块设备:按整块进行数据读写的设备,最小的块大小为512字节(一个扇区),块的大小必须是扇区的整数倍,Linux系统的块大小一般为4096字节,随机访问,设缓存以提高效率
-
网络设备:针对网络数据收发的设备
总体框架图:
二、设备号------内核中同类设备的区分
内核用设备号来区分同类里不同的设备,设备号是一个无符号32位整数,数据类型为dev_t,设备号分为两部分:
-
主设备号:占高12位,用来表示驱动程序相同的一类设备
-
次设备号:占低20位,用来表示被操作的哪个具体设备
应用程序打开一个设备文件时,通过设备号来查找定位内核中管理的设备。
MKDEV宏用来将主设备号和次设备号组合成32位完整的设备号,用法:
dev_t devno;
int major = 251;//主设备号
int minor = 2;//次设备号
devno = MKDEV(major,minor);
MAJOR宏用来从32位设备号中分离出主设备号,用法:
dev_t devno = MKDEV(249,1);
int major = MAJOR(devno);
MINOR宏用来从32位设备号中分离出次设备号,用法:
dev_t devno = MKDEV(249,1);
int minor = MINOR(devno);
如果已知一个设备的主次设备号,应用层指定好设备文件名,那么可以用mknod命令在/dev目录创建代表这个设备的文件,即此后应用程序对此文件的操作就是对其代表的设备操作,mknod用法如下:
@ cd /dev
@ mknod 设备文件名 设备种类(c为字符设备,b为块设备) 主设备号 次设备号
//ubuntu下需加sudo执行
在应用程序中如果要创建设备可以调用系统调用函数mknod,其原型如下:
int mknod(const char *pathname,mode_t mode,dev_t dev);
pathname:带路径的设备文件名,无路径默认为当前目录,一般都创建在/dev下
mode:文件权限 位或 S_IFCHR/S_IFBLK
dev:32位设备号
返回值:成功为0,失败-1
三、申请和注销设备号
字符驱动开发的第一步是通过模块的入口函数向内核添加本设备驱动的代码框架,主要完成:
-
申请设备号(唯一)
-
定义、初始化、向内核添加代表本设备的结构体元素
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:手动分配设备号,先验证设备号是否被占用,如果没有则申请占用该设备号
参数:
from:自己指定的设备号
count:申请的设备数量
name:/proc/devices文件中与该设备对应的名字,方便用户层查询主设备号
返回值:
成功为0,失败负数,绝对值为错误码
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count, const char *name)
功能:动态分配设备号,查询内核里未被占用的设备号,如果找到则占用该设备号
参数:
dev:分配设备号成功后用来存放分配到的设备号
baseminior:起始的次设备号,一般为0
count:申请的设备数量
name:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号
返回值:
成功为0,失败负数,绝对值为错误码
分配成功后在/proc/devices 可以查看到申请到主设备号和对应的设备名,mknod时参数可以参考查到的此设备信息
void unregister_chrdev_region(dev_t from, unsigned count)
功能:释放设备号
参数:
from:已成功分配的设备号将被释放
count:申请成功的设备数量
释放后/proc/devices文件对应的记录消失
函数指针复习
概念
每个函数在编译后都是存储在内存中,并且每个函数都有一个入口地址,根据这个地址,我们便可以访问并使用这个函数。函数指针就是通过指向这个函数的入口,从而调用这个函数
函数指针虽然也是指针,但它的定义方式却和其他指针看上去很不一样,我们来看看它是如何定义的:
/* 方法1 */
void (*p_func)(int, int, float) = NULL;
/* 方法2 */
typedef void (*tp_func)(int, int, float);
tp_func p_func = NULL;
这两种方式都是定义了一个指向返回值为 void 类型,参数为 (int, int, float) 的函数指针。第二种方法是为了让函数指针更容易理解,尤其是在复杂的环境下;
内存的作用-----用来存放程序运行过程中的
- 数据
- 指令
内存四区
- 堆区
- 栈区
- 数据区
- 代码区
C语言中内存数据的访问方式
- 直接访问:通过所在空间名称去访问
- 间接访问:通过所在空间首地址去访问 *地址值 此时的*为间接访问运算符
C语言中函数调用方式:
- 直接调用:通过函数名去调用函数
- 间接调用:通过函数在代码区所对应的那份空间的首地址去调用
int func(int a,int b)
{
//......
}
int (int a,int b) * pf;//语法错误
int *pf(int a,int b);//函数声明语句
int (*pf)(int a,int b);//定义一个函数指针 参数名可以没有
//下列两句等价
pf = &func;//&运算符后面如果是函数名的话可以省略不写
pf = func; //常用
y = func(3,4);//直接调用
//下列两句等价
y = (*pf)(3,4);//间接调用,*运算符后面如果是函数指针类型则可以省略不写
y = pf(3,4);//间接调用
typedef int myint;
typedef int (*)(int,int) pft;//语法错误
typedef int (*pft)(int,int) ;
pft pt;
格式:
ElemType (*函数指针名)(形参1,形参2,.....)
适用场合
前提:当有很多个同类函数待被调用时
A处:知道所有函数名,由此处来决定B处将会调用哪个函数
B处:负责调用A处指定的函数
思考:A处如何告诉B处被调用的是哪个函数呢,无非两个办法:
- 告诉B处函数名,怎么做呢?传字符串----“函数名”? C语言没有对应语法支持
- 告诉B处对应函数在代码区的地址(使用函数指针)
五、注册字符设备
struct cdev
{
struct kobject kobj;//表示该类型实体是一种内核对象
struct module *owner;//填THIS_MODULE,表示该字符设备从属于哪个内核模块
const struct file_operations *ops;//指向空间存放着针对该设备的各种操作函数地址
struct list_head list;//链表指针域
dev_t dev;//设备号
unsigned int count;//设备数量
};
自己定义的结构体中必须有一个成员为 struct cdev cdev,两种方法定义一个设备:
-
直接定义:定义结构体全局变量
-
动态申请:
struct cdev * cdev_alloc()
void cdev_init(struct cdev *cdev,const struct file_operations *fops)
struct file_operations
{
struct module *owner; //填THIS_MODULE,表示该结构体对象从属于哪个内核模块
int (*open) (struct inode *, struct file *); //打开设备
int (*release) (struct inode *, struct file *); //关闭设备
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读设备
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写设备
loff_t (*llseek) (struct file *, loff_t, int); //定位
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
unsigned int (*poll) (struct file *, struct poll_table_struct *); //POLL机制,实现多路复用的支持
int (*mmap) (struct file *, struct vm_area_struct *); //映射内核空间到用户层
int (*fasync) (int, struct file *, int); //信号驱动
//......
};
该对象各个函数指针成员都对应相应的系统调用函数,应用层通过调用系统函数来间接调用这些函数指针成员指向的设备驱动函数:
一般定义一个struct file_operations类型的全局变量并用自己实现各种操作函数名对其进行初始化
int cdev_add(struct cdev *p,dev_t dev,unsigned int count)
功能:将指定字符设备添加到内核
参数:
p:指向被添加的设备
dev:设备号
count:设备数量,一般填1
void cdev_del(struct cdev *p)
功能:从内核中移除一个字符设备
参数:
p:指向被移除的字符设备
示例代码:mychar.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
int major = 11;
int minor = 0;
int mychar_num = 1;
struct cdev mydev;
int mychar_open(struct inode *pnode, struct file *pfile)
{
printk("mychar_open is called\n");
return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{
printk("mychar_close is called\n");
return 0;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
};
int mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major, minor);
/* 申请设备号 */
ret = register_chrdev_region(devno, mychar_num, "mychar");
if (ret) {
ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
if (ret) {
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno); // 容易遗漏,注意
}
/* 给struct cdev对象指定操作函数集 */
cdev_init(&mydev, &myops);
/* 将 struct cdev对象添加到内核对应的数据结构里 */
mydev.owner = THIS_MODULE;
cdev_add(&mydev, devno, mychar_num);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major, minor);
cdev_del(&mydev);
unregister_chrdev_region(devno, mychar_num);
}
//表示支持GPL的开源协议
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
这段代码用于创建和管理一个简单的字符设备。
包含的头文件
#include <linux/module.h> // 必须的,用于内核模块的开发
#include <linux/kernel.h> // 包含了必要的内核函数
#include <linux/fs.h> // 文件系统的头文件,包含了文件操作相关的结构体
#include <linux/cdev.h> // 包含了字符设备结构cdev的定义
全局变量定义
int major = 11; // 主设备号
int minor = 0; // 次设备号
int mychar_num = 1; // 设备数量
struct cdev mydev; // 定义一个字符设备结构体
设备操作函数
这些函数将被设备操作结构体引用,用于打开和关闭设备。
int mychar_open(struct inode *pnode, struct file *pfile)
{
printk("mychar_open is called\n"); // 打印信息,表示设备被打开
return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{
printk("mychar_close is called\n"); // 打印信息,表示设备被关闭
return 0;
}
文件操作结构体
这定义了设备的操作函数。
struct file_operations myops = {
.owner = THIS_MODULE, // 指明该模块拥有者
.open = mychar_open, // 打开设备时调用的函数
.release = mychar_close, // 关闭设备时调用的函数
};
初始化和退出函数
这些是模块加载和卸载时调用的函数。
int mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major, minor); // 根据主次设备号创建设备编号
/* 申请设备号 */
ret = register_chrdev_region(devno, mychar_num, "mychar");
if (ret) {
ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
if (ret) {
printk("get devno failed\n"); // 如果获取设备号失败,则打印错误信息
return -1;
}
major = MAJOR(devno); // 更新主设备号,容易遗漏,注意
}
/* 给struct cdev对象指定操作函数集 */
cdev_init(&mydev, &myops);
/* 将 struct cdev对象添加到内核对应的数据结构里 */
cdev_add(&mydev, devno, mychar_num);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major, minor);
cdev_del(&mydev); // 删除cdev对象
unregister_chrdev_region(devno, mychar_num); // 注销设备号
}
模块的许可和入口/出口声明
这定义了模块的许可证,并指定了加载和卸载的函数。
MODULE_LICENSE("GPL"); // GPL许可证声明
module_init(mychar_init); // 指定模块加载时调用的函数
module_exit(mychar_exit); // 指定模块卸载时调用的函数
这个模块创建了一个字符设备,它具备基本的打开和关闭操作。设备号的分配处理了两种情况:如果静态分配的设备号已被占用,它会尝试动态分配一个设备号。
testmychar.c:
这段代码是一个简单的C程序,用于打开并随后关闭一个文件。该程序包含在UNIX/Linux环境下常用的几个头文件,并执行一些基础的文件操作。下面是每一部分的详细解释和注释:
包含的头文件
#include <sys/types.h> // 提供数据类型,如pid_t等
#include <sys/stat.h> // 提供了与文件系统状态相关的定义
#include <fcntl.h> // 提供了文件控制选项常数
#include <unistd.h> // 提供了对POSIX操作系统API的访问功能
#include <sys/ioctl.h> // 提供了设备控制相关的定义
#include <stdio.h> // 提供了输入和输出功能
主函数
程序的主入口点,接受命令行参数。
int main(int argc, char *argv[])
{
int fd = -1; // 文件描述符初始化为-1,代表无效值
if(argc < 2) // 检查命令行参数是否足够
{
printf("The argument is too few\n"); // 如果参数不足,打印错误信息并退出
return 1;
}
fd = open(argv[1], O_RDWR); // 以读写方式打开传入的文件名
if(fd < 0) // 检查文件是否成功打开
{
printf("open %s failed\n", argv[1]); // 打开文件失败,打印错误信息并退出
return 2;
}
close(fd); // 关闭文件
fd = -1; // 将文件描述符重置为无效值
return 0; // 正常退出
}
详细说明
- 文件描述符(fd):在UNIX/Linux中,所有打开的文件都通过文件描述符来引用。这是一个整数,指向内核中的一个文件表项。
open
函数:这是一个系统调用,用于打开或创建一个文件。它返回一个文件描述符,如果操作失败则返回-1。O_RDWR
标志表示文件将以读写模式打开。- 错误处理:如果文件打开失败,程序使用
printf
打印一个错误消息,并返回一个非零值来表示错误。 close
函数:另一个系统调用,用于关闭一个已打开的文件描述符。正确关闭文件是好的编程习惯,可以防止资源泄漏。
这个程序主要用于验证能否成功打开一个给定的文件,这在测试文件访问权限或文件存在性时很有用。
Makefile:
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/fs4412/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
CONFIG_MODULE_SIG=n
obj-m += myhello.o
obj-m += xtt.o
xtt-objs = test.o func.o
obj-m += testparam.o
obj-m += mychar_sema.o
obj-m += multimychar.o
obj-m += openonce_atomic.o
obj-m += openonce_spinlock.o
obj-m += mychar_mutex.o
obj-m += second.o
endif
为了使上述用户空间的程序(如提供的C程序)能够调用并触发在 mychar.c
内核模块中定义的 mychar_open
和 mychar_close
函数,有几个必要的步骤需要正确执行:
步骤1: 正确加载内核模块
首先,确保 mychar.c
中定义的内核模块已正确编译并加载到内核中。这可以通过使用 insmod
或 modprobe
命令来完成。例如:
sudo insmod mychar.ko
加载模块时,系统会为这个模块分配一个设备号,除非在模块中已经指定了静态设备号。
步骤2: 创建设备文件
在UNIX和Linux系统中,设备被表示为文件,位于 /dev/
目录下。要与新的内核模块交互,需要创建一个对应的设备文件。可以使用 mknod
命令手动创建设备文件,例如:
sudo mknod /dev/mychar c 11 0
这里,c
表示这是一个字符设备,11
是主设备号,0
是次设备号。这些数字需要与 mychar.c
中定义的一致。
步骤3: 确保设备权限正确
设备文件的权限可能需要调整,以确保普通用户或特定的用户组可以访问该设备。可以使用 chmod
或 chown
命令调整权限。
步骤4: 用户空间程序的操作
一旦设备文件创建并具有正确的权限,用户空间的程序通过调用 open
和 close
系统调用,并传递设备文件的路径作为参数,实际上是与内核空间的 mychar_open
和 mychar_close
函数交互。这些系统调用将被内核路由到 mychar.c
中定义的函数。
如果用户程序尝试打开 /dev/mychar
设备文件:
fd = open("/dev/mychar", O_RDWR);
这将触发内核模块中 mychar_open
函数的执行。同样地,调用 close(fd);
将触发 mychar_close
函数的执行。
总结
通过这种方式,用户空间的程序能够与内核空间的模块交互,从而不是简单地使用库文件中的 open
和 close
实现,而是实际上调用了内核模块中定义的特定函数。这就是用户空间如何触发内核模块中定义的打印信息的基本机制。
字符设备驱动开发步骤:
-
如果设备有自己的一些控制数据,则定义一个包含struct cdev cdev成员的结构体struct mydev,其它成员根据设备需求,设备简单则直接用struct cdev
-
定义一个struct mydev或struct cdev的全局变量来表示本设备;也可以定义一个struct mydev或struct cdev的全局指针(记得在init时动态分配)
-
定义三个全局变量分别来表示主设备号、次设备号、设备数
-
定义一个struct file_operations结构体变量,其owner成员置成THIS_MODULE
-
module init函数流程:
-
申请设备号
-
如果是全局设备指针则动态分配代表本设备的结构体元素
-
初始化struct cdev成员
-
设置struct cdev的owner成员为THIS_MODULE
-
添加字符设备到内核
-
-
module exit函数:
-
注销设备号
-
从内核中移除struct cdev
-
如果如果是全局设备指针则释放其指向空间
-
-
编写各个操作函数并将函数名初始化给struct file_operations结构体变量
验证操作步骤:
-
编写驱动代码mychar.c
-
make生成ko文件
-
insmod内核模块
-
查阅字符设备用到的设备号(主设备号):cat /proc/devices | grep 申请设备号时用的名字
-
创建设备文件(设备节点) : mknod /dev/??? c 上一步查询到的主设备号 代码中指定初始次设备号
-
编写app验证驱动(testmychar_app.c)
-
编译运行app,dmesg命令查看内核打印信息
六、字符设备驱动框架解析
设备的操作函数如果比喻是桩的话(性质类似于设备操作函数的函数,在一些场合被称为桩函数),则:
驱动实现设备操作函数 ----------------------------------------- 做桩
insmod调用的init函数主要作用 ------------------------------- 钉桩
rmmod调用的exitt函数主要作用 ------------------------------ 拔桩
应用层通过系统调用函数间接调用这些设备操作函数 --- 用桩
1 两个操作函数中常用的结构体说明
//内核中记录文件元信息的结构体
struct inode
{
//....
dev_t i_rdev;//设备号
struct cdev *i_cdev;//如果是字符设备才有此成员,指向对应设备驱动程序中的加入系统的struct cdev对象
//....
}
/*
1. 内核中每个该结构体对象对应着一个实际文件,一对一
2. open一个文件时如果内核中该文件对应的inode对象已存在则不再创建,不存在才创建
3. 内核中用此类型对象关联到对此文件的操作函数集(对设备而言就是关联到具体驱动代码)
*/
//读写文件内容过程中用到的一些控制性数据组合而成的对象------文件操作引擎(文件操控器)
struct file
{
//...
mode_t f_mode;//不同用户的操作权限,驱动一般不用
loff_t f_pos;//position 数据位置指示器,需要控制数据开始读写位置的设备有用
unsigned int f_flags;//open时的第二个参数flags存放在此,驱动中常用
struct file_operations *f_op;//open时从struct inode中i_cdev的对应成员获得地址,驱动开发中用来协助理解工作原理,内核中使用
void *private_data;//本次打开文件的私有数据,驱动中常来在几个操作函数间传递共用数据
struct dentry *f_dentry;//驱动中一般不用,除非需要访问对应文件的inode,用法flip->f_dentry->d_inode
int refcnt;//引用计数,保存着该对象地址的位置个数,close时发现refcnt为0才会销毁该struct file对象
//...
};
/*
1. open函数被调用成功一次,则创建一个该对象,因此可以认为一个该类型的对象对应一次指定文件的操作
2. open同一个文件多次,每次open都会创建一个该类型的对象
3. 文件描述符数组中存放的地址指向该类型的对象
4. 每个文件描述符都对应一个struct file对象的地址
*/
2 字符设备驱动程序框架分析
驱动实现端:
驱动使用端:
syscall_open函数实现的伪代码:
int syscall_open(const char *filename,int flag)
{
dev_t devno;
struct inode *pnode = NULL;
struct cdev *pcdev = NULL;
struct file *pfile = NULL;
int fd = -1;
/*根据filename在内核中查找该文件对应的struct inode对象地址
找到则pnode指向该对象
未找到则创建新的struct inode对象,pnode指向该对象,并从文件系统中读取文件的元信息到该对象*/
if(/*未找到对应的struct inode对象*/)
{/*根据文件种类决定如何进行下面的操作,如果是字符设备则执行如下操作*/
/*从pnode指向对象中得到设备号*/
devno = pnode->i_rdev;
/*用devno在字符设备链表查找对应节点,并将该节点的地址赋值给pcdev*/
/*pcdev赋值给pnode的i_cdev成员*/
pnode->i_cdev = pcdev;
}
/*创建struct file对象,并将该对象的地址赋值给pfile*/
pfile->f_op = pnode->i_cdev->ops;
pfile->f_flags = flag;
/*调用驱动程序的open函数*/
pfile->f_op->open(pnode,pfile,flag);
/*将struct file对象地址填入进程的描述符数组,得到对应位置的下标赋值给fd*/
return fd;
}
syscall_read函数实现的伪代码
int syscall_read(int fd,void *pbuf,int size)
{
struct file *pfile = NULL;
struct file_operations *fops = NULL;
int cnt;
/*将fd作为下标,在进程的描述符数组中获得struct file对象的地址赋值给pfile*/
/*从struct file对象的f_op成员中得到操作函数集对象地址赋值给fops*/
/*从操作函数集对象的read成员得到该设备对应的驱动程序中read函数,并调用之*/
cnt = fops->read(pfile,pbuf,size,&pfile->f_pos);
。。。。
return cnt;
}
3 参考原理图
4 常用操作函数说明
int (*open) (struct inode *, struct file *); //打开设备
/*
指向函数一般用来对设备进行硬件上的初始化,对于一些简单的设备该函数只需要return 0,对应open系统调用,是open系统调用函数实现过程中调用的函数,
*/
int (*release) (struct inode *, struct file *); //关闭设备
/*
指向函数一般用来对设备进行硬件上的关闭操作,对于一些简单的设备该函数只需要return 0,对应close系统调用,是close系统调用函数实现过程中调用的函数
*/
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读设备
/*
指向函数用来将设备产生的数据读到用户空间,对应read系统调用,是read系统调用函数实现过程中调用的函数
*/
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写设备
/*
指向函数用来将用户空间的数据写进设备,对应write系统调用,是write系统调用函数实现过程中调用的函数
*/
loff_t (*llseek) (struct file *, loff_t, int); //数据操作位置的定位
/*
指向函数用来获取或设置设备数据的开始操作位置(位置指示器),对应lseek系统调用,是lseek系统调用函数实现过程中调用的函数
*/
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
/*
指向函数用来获取、设置设备一些属性或设备的工作方式等非数据读写操作,对应ioctl系统调用,是ioctl系统调用函数实现过程中调用的函数
*/
unsigned int (*poll) (struct file *, struct poll_table_struct *);//POLL机制,实现对设备的多路复用方式的访问
/*
指向函数用来协助多路复用机制完成对本设备可读、可写数据的监控,对应select、poll、epoll_wait系统调用,是select、poll、epoll_wait系统调用函数实现过程中调用的函数
*/
int (*fasync) (int, struct file *, int); //信号驱动
/*
指向函数用来创建信号驱动机制的引擎,对应fcntl系统调用的FASYNC标记设置,是fcntl系统调用函数FASYNC标记设置过程中调用的函数
*/
七、读写操作实现
读操作实现
ssize_t xxx_read(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos);
/*
完成功能:读取设备产生的数据 把内核空间的数据向用户空间拷贝
参数:
filp:指向open产生的struct file类型的对象,表示本次read对应的那次open
pbuf:指向用户空间一块内存,用来保存读到的数据
count:用户期望读取的字节数
ppos:对于需要位置指示器控制的设备操作有用,用来指示读取的起始位置,读完后也需要变更位置指示器的指示位置
返回值:
本次成功读取的字节数,失败返回-1
*/
put_user(x,ptr)
x:char、int类型的简单变量名
unsigned long copy_to_user (void __user * to, const void * from, unsigned long n)
//内核空间向用户空间拷贝数据
成功为返回0,失败非0
写操作实现
ssize_t xxx_write (struct file *filp, const char __user *pbuf, size_t count, loff_t *ppos);
/*
完成功能:向设备写入数据 把用户空间的数据写入内核设备
参数:
filp:指向open产生的struct file类型的对象,表示本次write对应的那次open
pbuf:指向用户空间一块内存,用来保存被写的数据
count:用户期望写入的字节数
ppos:对于需要位置指示器控制的设备操作有用,用来指示写入的起始位置,写完后也需要变更位置指示器的指示位置
返回值:
本次成功写入的字节数,失败返回-1
*/
get_user(x,ptr)
x:char、int类型的简单变量名
unsigned long copy_from_user (void * to, const void __user * from, unsigned long n)
//内核空间从用户空间拷贝数据
成功为返回0,失败非0
count:想要写多少数据 size:实际能够写多少数据
示例代码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#define BUF_LEN 100
int major = 11;
int minor = 0;
int mychar_num = 1;
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen = 0;
int mychar_open(struct inode *pnode,struct file *pfile)
{
printk("mychar_open is called\n");
return 0;
}
int mychar_close(struct inode *pnode,struct file *pfile)
{
printk("mychar_close is called\n");
return 0;
}
ssize_t mychar_read(struct file *pfile, char __user *puser, size_t count, loff_t *p_pos)
{
int size = 0;
int ret = 0;
if(count > curlen)
{
size = curlen;
}
else
{
size = count;
}
ret = copy_to_user(puser,mydev_buf,size);
if(ret)
{
printk("copy_to_user failed\n");
return -1;
}
memcpy(mydev_buf,mydev_buf + size,curlen - size);
curlen = curlen - size;
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count, loff_t *p_pos)
{
int size = 0;
int ret = 0;
if(count > BUF_LEN-curlen)
{
size = BUF_LEN - curlen;
}
else
{
size = count;
}
ret = copy_from_user(mydev_buf + curlen,puser,size);
if(ret)
{
printk("copy_from user failed\n");
return -1;
}
curlen = curlen + size;
return size;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
/*申请设备号*/
ret = register_chrdev_region(devno,mychar_num, "mychar");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//容易遗漏,注意
}
/*给struct cdev对象指定操作函数集*/
cdev_init(&mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
mydev.owner = THIS_MODULE;
cdev_add(&mydev,devno,mychar_num);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&mydev);
unregister_chrdev_region(devno,mychar_num);
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
编译过程:
设备操作很熟避免使用全局变量:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include "mychar.h"
#define BUF_LEN 100
#define MYCHAR_DEV_CNT 3
int major = 11;
int minor = 0;
int mychar_num = MYCHAR_DEV_CNT;
struct mychar_dev
{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen;
};
struct mychar_dev gmydev;
int mychar_open(struct inode *pnode,struct file *pfile)
{
pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mychar_dev,mydev));
printk("mychar_open is called\n");
return 0;
}
int mychar_close(struct inode *pnode,struct file *pfile)
{
printk("mychar_close is called\n");
return 0;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
int size = 0;
int ret = 0;
if(count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret)
{
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
pmydev->curlen -= size;
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(count > BUF_LEN-pmydev->curlen)
{
size = BUF_LEN - pmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret)
{
printk("copy_from_user failed\n");
return -1;
}
pmydev->curlen += size;
return size;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
int i = 0;
/*申请设备号*/
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//容易遗漏,注意
}
/*给struct cdev对象指定操作函数集*/
cdev_init(&gmydev.mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
gmydev.mydev.owner = THIS_MODULE;
cdev_add(&gmydev.mydev,devno,1);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
int i = 0;
for(i = 0;i < MYCHAR_DEV_CNT;i++)
{
cdev_del(&gmydev_arr[i].mydev);
}
unregister_chrdev_region(devno,mychar_num);
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
八、ioctl操作实现
已知成员的地址获得所在结构体变量的地址:
container_of(成员地址,结构体类型名,成员在结构体中的名称)
例:container_of(pnode->i_cdev,struct mychar_dev,mydev)
long xxx_ioctl (struct file *filp, unsigned int cmd, unsigned long arg);
功能:对相应设备做指定的控制操作(各种属性的设置获取等等)
参数:
filp:指向open产生的struct file类型的对象,表示本次ioctl对应的那次open
cmd:用来表示做的是哪一个操作
arg:和cmd配合用的参数
返回值:成功为0,失败-1
cmd组成
- dir(direction),ioctl 命令访问模式(属性数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
- type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如 ‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
- nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
- size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;
#define _IOC(dir,type,nr,size) (((dir)<<_IOC_DIRSHIFT)| \
((type)<<_IOC_TYPESHIFT)| \
((nr)<<_IOC_NRSHIFT)| \
((size)<<_IOC_SIZESHIFT))
/* used to create numbers */
// 定义不带参数的 ioctl 命令
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
//定义带读参数的ioctl命令(copy_to_user) size为类型名
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
//定义带写参数的 ioctl 命令(copy_from_user) size为类型名
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
//定义带读写参数的 ioctl 命令 size为类型名
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
/* used to decode ioctl numbers */
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include "mychar.h"
#define BUF_LEN 100
#define MYCHAR_DEV_CNT 3
int major = 11;
int minor = 0;
int mychar_num = MYCHAR_DEV_CNT;
struct mychar_dev
{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen;
};
struct mychar_dev gmydev_arr[MYCHAR_DEV_CNT];
int mychar_open(struct inode *pnode,struct file *pfile)
{
pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mychar_dev,mydev));
printk("mychar_open is called\n");
return 0;
}
int mychar_close(struct inode *pnode,struct file *pfile)
{
printk("mychar_close is called\n");
return 0;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
int size = 0;
int ret = 0;
if(count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret)
{
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
pmydev->curlen -= size;
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(count > BUF_LEN-pmydev->curlen)
{
size = BUF_LEN - pmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret)
{
printk("copy_from_user failed\n");
return -1;
}
pmydev->curlen += size;
return size;
}
long mychar_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)
{
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
switch(cmd)
{
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret)
{
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
if(ret)
{
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The cmd is unknow\n");
return -1;
}
return 0;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.unlocked_ioctl = mychar_ioctl,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
int i = 0;
/*申请设备号*/
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//容易遗漏,注意
}
for(i = 0;i < MYCHAR_DEV_CNT;i++)
{
devno = MKDEV(major,minor+i);
/*给struct cdev对象指定操作函数集*/
cdev_init(&gmydev_arr[i].mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
gmydev_arr[i].mydev.owner = THIS_MODULE;
cdev_add(&gmydev_arr[i].mydev,devno,1);
}
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
int i = 0;
for(i = 0;i < MYCHAR_DEV_CNT;i++)
{
cdev_del(&gmydev_arr[i].mydev);
}
unregister_chrdev_region(devno,mychar_num);
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
mychar.h:
#ifndef MY_CHAR_H
#define MY_CHAR_H
#include <asm/ioctl.h>
#define MY_CHAR_MAGIC 'k'
#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)
#endif
printk
//日志级别
//错误
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
//警告
#define KERN_WARNING "<4>" /* warning conditions */
//描述、通知类
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
用法:printk(KERN_INFO"....",....)
printk(KERN_INFO"Hello World"); =====> printk("<6>""Hello World") ====> printk("<6>Hello World")
dmesg --level=emerg,alert,crit,err | warn | notice,info,debug
#define HELLO_DEBUG
#undef PDEBUG
#ifdef HELLO_DEBUG
#define PDEBUG(fmt, args...) printk(KERN_DEBUG fmt, ##args)
#else
#define PDEBUG(fmt, args...)
#endif
多个次设备的支持
//一份设备代码 能够支持多个同类的次设备
每一个具体设备(次设备不一样的设备),必须有一个struct cdev来代表它
cdev_init
cdev.owner赋值
cdev_add
以上三个操作对每个具体设备都要进行。
在申请设备号时写一个循环:已经写在上面代码中。
/* ....................*/
int __init mychar_init(void){
//************
//申请设备号
//**********
int i = 0;
for(i = 0;i < MYCHAR_DEV_CNT;i++)
{
devno = MKDEV(major,minor+i);
/*给struct cdev对象指定操作函数集*/
cdev_init(&gmydev_arr[i].mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
gmydev_arr[i].mydev.owner = THIS_MODULE;
cdev_add(&gmydev_arr[i].mydev,devno,1);
}
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
int i = 0;
for(i = 0;i < MYCHAR_DEV_CNT;i++)
{
cdev_del(&gmydev_arr[i].mydev);
}
unregister_chrdev_region(devno,mychar_num);
}
五种IO模型------读写外设数据的方式
1.*阻塞: 不能操作就睡觉
2.*非阻塞:不能操作就返回错误
3.*多路复用:委托中介监控
4.信号驱动:让内核如果能操作时发信号,在信号处理函数中操作(不可靠,如同一时间段收到多个信号 可能会导致后两个信号不被处理)
5.异步IO:向内核注册操作请求,内核完成操作后发通知信号(也是基于信号驱动)
阻塞与非阻塞
应用层:
- open时由O_NONBLOCK指示read、write时是否阻塞
- open以后可以由fcntl函数来改变是否阻塞:
flags = fcntl(fd,F_GETFL,0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
驱动层:通过等待队列
wait_queue_head_t //等待队列头数据类型
init_waitqueue_head(wait_queue_head_t *pwq) //初始化等待队列头
wait_event_interruptible(wq,condition)
/*
功能:条件不成立则让任务进入浅度睡眠,直到条件成立醒来
wq:等待队列头
condition:C语言表达式
返回:正常唤醒返回0,信号唤醒返回非0(此时读写操作函数应返回-ERESTARTSYS)
*/
wait_event(wq,condition) //深度睡眠
wake_up_interruptible(wait_queue_head_t *pwq)
wake_up(wait_queue_head_t *pwq)
/*
1. 读、写用不同的等待队列头rq、wq
2. 无数据可读、可写时调用wait_event_interruptible(rq、wq,条件)
3. 写入数据成功时唤醒rq,读出数据成功唤醒wq
*/
代码:
//添加头文件
#include <linux/wait.h>
#include <linux/sched.h>
//在字符设备结构体中添加等待命令
struct mychar_dev
{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen;
//此处添加
wait_queue_head_t rq;
wait_queue_head_t wq;
};
//在init函数中对其进行初始化
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
int i = 0;
/*申请设备号*/
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//容易遗漏,注意
}
/*给struct cdev对象指定操作函数集*/
cdev_init(&gmydev.mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
gmydev.mydev.owner = THIS_MODULE;
cdev_add(&gmydev.mydev,devno,1);
//此处添加
init_waitqueue_head(&gmydev->rq);
init_waitqueue_head(&gmydev->wq);
return 0;
}
//改写读写函数
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
int size = 0;
int ret = 0;
//此处添加
if(pmydev->curlen <= 0) //没有数据可读
{
if(pfile->f_flags & O_NONBLOCK) //此结果为真 即为非阻塞
{//非阻塞
printk("O_NONBLOCK No Data Read\n");
return -1;
}
else //阻塞
{//进入等待队列当中 直到curlen大于0
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret)
{
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
}
}
if(count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret)
{
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
pmydev->curlen -= size;
//有数据了 唤醒
wake_up_interruptible(&pmydev->wq);
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
//此处添加
if(pmydev->curlen >= BUF_LEN)
{
if(pfile->f_flags & O_NONBLOCK)
{
printk("O_NONBLOCK Can not write data\n");
return -1;
}
else
{
ret = wait_event_interruptible(pmydev->wq,pmydev->curlen < BUF_LEN);
if(ret)
{
printk("wake up by signal\n");
return -ERESTARTSYS;
}
}
}
if(count > BUF_LEN-pmydev->curlen)
{
size = BUF_LEN - pmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret)
{
printk("copy_from_user failed\n");
return -1;
}
pmydev->curlen += size;
//此处添加
wake_up_interruptible(&pmydev->rq);
return size;
}
(默认是阻塞的)
测试程序中:
fd = open(argv[1],O_RDWR | O_NONBLOCK); //非阻塞
fd = open(argv[1],O_RDWR ); //阻塞
多路复用
描述符:
-
文件描述符:设备文件、管道文件
-
socket描述符
1 应用层:三套接口select、poll、epoll
- select:位运算实现 监控的描述符数量有限(32位机1024,64位机2048) 效率差
- poll:链表实现,监控的描述符数量不限 效率差
- epoll:效率最高,监控的描述符数量不限
select
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
/* 功能:监听多个描述符,阻塞等待有一个或者多个文件描述符,准备就绪。
内核将没有准备就绪的文件描述符,从集合中清掉了。
参数: nfds 最大文件描述符数 ,加1
readfds 读文件描述符集合
writefds 写文件描述符集合
exceptfds 其他异常的文件描述符集合
timeout 超时时间(NULL)
返回值:当timeout为NULL时返回0,成功:准备好的文件描述的个数 出错:-1
当timeout不为NULL时,如超时设置为0,则select为非阻塞,超时设置 > 0,则无描述符可被操作的情况下阻塞指定长度的时间
*/
void FD_CLR(int fd, fd_set *set);
//功能:将fd 从集合中清除掉
int FD_ISSET(int fd, fd_set *set);
//功能:判断fd 是否存在于集合中
void FD_SET(int fd, fd_set *set);
//功能:将fd 添加到集合中
void FD_ZERO(fd_set *set);
//功能:将集合清零
//使用模型:
while(1)
{
/*得到最大的描述符maxfd*/
/*FD_ZERO清空描述符集合*/
/*将被监控描述符加到相应集合rfds里 FD_SET*/
/*设置超时*/
ret = select(maxfd+1,&rfds,&wfds,NULL,NULL);
if(ret < 0)
{
if(errno == EINTR)//错误时信号引起的
{
continue;
}
else
{
break;
}
}
else if(ret == 0)
{//超时
//.....
}
else
{ //> 0 ret为可被操作的描述符个数
if(FD_ISSET(fd1,&rfds))
{//读数据
//....
}
if(FD_ISSET(fd2,&rfds))
{//读数据
//....
}
///.....
if(FD_ISSET(fd1,&wfds))
{//写数据
//....
}
}
}
2 驱动层:实现poll函数
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);
/*功能:将等待队列头添加至poll_table表中
参数:struct file :设备文件
Wait_queue_head_t :等待队列头
Poll_table :poll_table表
*/
/*该函数与select、poll、epoll_wait函数相对应,协助这些多路监控函数判断本设备是否有数据可读写*/
unsigned int xxx_poll(struct file *filp, poll_table *wait) //函数名初始化给struct file_operations的成员.poll
{
unsigned int mask = 0;
/*
1. 将所有等待队列头加入poll_table表中
2. 判断是否可读,如可读则mask |= POLLIN | POLLRDNORM;
3. 判断是否可写,如可写则mask |= POLLOUT | POLLWRNORM;
*/
return mask;
}
驱动代码:
#include <linux/module.h> // 所有内核模块都需包含
#include <linux/kernel.h> // 包含 KERN_INFO 等级信息
#include <linux/fs.h> // 文件系统支持
#include <linux/cdev.h> // 字符设备支持
#include <linux/wait.h> // 等待队列
#include <linux/sched.h> // 调度相关
#include <linux/poll.h> // 轮询功能支持
#include <linux/mm.h> // 内存管理
#include <linux/slab.h> // 内存分配
#include <asm/uaccess.h> // 用户空间和内核空间数据传输
#include "mychar.h" // 自定义的头文件,用于定义设备
//设备定义
#define BUF_LEN 100 // 缓冲区长度
int major = 11; // 设备主号
int minor = 0; // 设备次号
int mychar_num = 1; // 设备号数量
struct mychar_dev // 设备结构体
{
struct cdev mydev; // 字符设备结构体
char mydev_buf[BUF_LEN]; // 设备缓冲区
int curlen; // 缓冲区当前数据长度
struct mutex lock; // 互斥锁
wait_queue_head_t rq; // 读操作等待队列
wait_queue_head_t wq; // 写操作等待队列
struct fasync_struct *pasync_obj; // 异步通知对象
};
struct mychar_dev *pgmydev = NULL;
//文件操作函数
int mychar_open(struct inode *pnode,struct file *pfile)
{
// 使用container_of宏从inode中的cdev结构体指针获取包含它的mychar_dev结构体指针
pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mychar_dev,mydev));
return 0;
}
int mychar_close(struct inode *pnode,struct file *pfile)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(pmydev->pasync_obj != NULL)
fasync_helper(-1,pfile,0,&pmydev->pasync_obj);
return 0;
}
// 读函数实现,包括阻塞和非阻塞读,以及从设备缓冲区复制数据到用户空间
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
int size = 0;
int ret = 0;
mutex_lock(&pmydev->lock);
if(pmydev->curlen <= 0)
{
if(pfile->f_flags & O_NONBLOCK)
{//非阻塞
mutex_unlock(&pmydev->lock);
printk("O_NONBLOCK No Data Read\n");
return -1;
}
else
{//阻塞
mutex_unlock(&pmydev->lock);
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret)
{
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
mutex_lock(&pmydev->lock);
}
}
if(count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret)
{
mutex_unlock(&pmydev->lock);
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
pmydev->curlen -= size;
mutex_unlock(&pmydev->lock);
wake_up_interruptible(&pmydev->wq);
return size;
}
// 写函数实现,包括阻塞和非阻塞写,以及从用户空间复制数据到设备缓冲区
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
mutex_lock(&pmydev->lock);
if(pmydev->curlen >= BUF_LEN)
{
if(pfile->f_flags & O_NONBLOCK)
{
mutex_unlock(&pmydev->lock);
printk("O_NONBLOCK Can not write data\n");
return -1;
}
else
{
mutex_unlock(&pmydev->lock);
ret = wait_event_interruptible(pmydev->wq,pmydev->curlen < BUF_LEN);
if(ret)
{
printk("wake up by signal\n");
return -ERESTARTSYS;
}
mutex_lock(&pmydev->lock);
}
}
if(count > BUF_LEN-pmydev->curlen)
{
size = BUF_LEN - pmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret)
{
mutex_unlock(&pmydev->lock);
printk("copy_from_user failed\n");
return -1;
}
pmydev->curlen += size;
mutex_unlock(&pmydev->lock);
wake_up_interruptible(&pmydev->rq);
if(pmydev->pasync_obj != NULL)
{
kill_fasync(&pmydev->pasync_obj,SIGIO,POLL_IN);
}
return size;
}
// 控制函数实现,处理设备特定命令
long mychar_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)
{
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
switch(cmd)
{
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret)
{
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
mutex_lock(&pmydev->lock);
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
mutex_unlock(&pmydev->lock);
if(ret)
{
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The cmd is unknow\n");
return -1;
}
return 0;
}
// 轮询函数实现,确定设备是否可读写
unsigned int mychar_poll(struct file *pfile,poll_table *ptb)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb);
poll_wait(pfile,&pmydev->wq,ptb);
mutex_lock(&pmydev->lock);
if(pmydev->curlen > 0)
{
mask |= POLLIN | POLLRDNORM;
}
if(pmydev->curlen < BUF_LEN)
{
mask |= POLLOUT | POLLWRNORM;
}
mutex_unlock(&pmydev->lock);
return mask;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.unlocked_ioctl = mychar_ioctl,
.poll = mychar_poll,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
/*申请设备号*/
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//容易遗漏,注意
}
pgmydev = (struct mychar_dev *)kmalloc(sizeof(struct mychar_dev),GFP_KERNEL);
if(NULL == pgmydev)
{
unregister_chrdev_region(devno,mychar_num);
printk("kmallc for struct mychar_dev failed\n");
return -1;
}
/*给struct cdev对象指定操作函数集*/
cdev_init(&pgmydev->mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev,devno,mychar_num);
init_waitqueue_head(&pgmydev->rq);
init_waitqueue_head(&pgmydev->wq);
mutex_init(&pgmydev->lock);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,mychar_num);
kfree(pgmydev);
pgmydev = NULL;
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
测试代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <stdio.h>
#include "mychar.h"
int main(int argc,char *argv[])
{
int fd = -1;
char buf[8] = "";
int ret = 0;
fd_set rfds;
if(argc < 2)
{
printf("The argument is too few\n");
return 1;
}
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("open %s failed\n",argv[1]);
return 2;
}
while(1)
{
FD_ZERO(&rfds);
FD_SET(fd,&rfds);
ret = select(fd + 1,&rfds,NULL,NULL,NULL);
if(ret < 0)
{
if(errno == EINTR)
{
continue;
}
else
{
printf("select error\n");
break;
}
}
if(FD_ISSET(fd,&rfds))
{
read(fd,buf,8);
printf("buf=%s\n",buf);
}
}
close(fd);
fd = -1;
return 0;
}
信号驱动
1 应用层:信号注册+fcntl
signal(SIGIO, input_handler); //注册信号处理函数
fcntl(fd, F_SETOWN, getpid());//将描述符设置给对应进程,好由描述符获知PID
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);//将该设备的IO模式设置成信号驱动模式
void input_handler(int signum)//应用自己实现的信号处理函数,在此函数中完成读写
{
//读数据
}
//应用模板
int main()
{
int fd = open("/dev/xxxx",O_RDONLY);
fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
signal(SIGIO,xxxx_handler);
//......
}
void xxxx_handle(int signo)
{//读写数据
}
2 驱动层:实现fasync函数
/*设备结构中添加如下成员*/
struct fasync_struct *pasync_obj;
/*应用调用fcntl设置FASYNC时调用该函数产生异步通知结构对象,并将其地址设置到设备结构成员中*/
static int hello_fasync(int fd, struct file *filp, int mode) //函数名初始化给struct file_operations的成员.fasync
{
struct hello_device *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->pasync_obj);
}
/*写函数中有数据可读时向应用层发信号*/
if (dev->pasync_obj)
kill_fasync(&dev->pasync_obj, SIGIO, POLL_IN);
/*release函数中释放异步通知结构对象*/
if (dev->pasync_obj)
fasync_helper(-1, filp, 0, &dev->pasync_obj);
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **pp);
/*
功能:产生或释放异步通知结构对象
参数:
返回值:成功为>=0,失败负数
*/
void kill_fasync(struct fasync_struct **, int, int);
/*
功能:发信号
参数:
struct fasync_struct ** 指向保存异步通知结构地址的指针
int 信号 SIGIO/SIGKILL/SIGCHLD/SIGCONT/SIGSTOP
int 读写信息POLLIN、POLLOUT
*/
驱动代码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include "mychar.h"
#define BUF_LEN 100
int major = 11;
int minor = 0;
int mychar_num = 1;
struct mychar_dev
{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen;
struct mutex lock;
wait_queue_head_t rq;
wait_queue_head_t wq;
//此处添加成员
struct fasync_struct *pasync_obj;
};
struct mychar_dev *pgmydev = NULL;
int mychar_open(struct inode *pnode,struct file *pfile)
{
pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mychar_dev,mydev));
return 0;
}
int mychar_close(struct inode *pnode,struct file *pfile)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
//此处添加
if(pmydev->pasync_obj != NULL)
fasync_helper(-1,pfile,0,&pmydev->pasync_obj);
return 0;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
int size = 0;
int ret = 0;
mutex_lock(&pmydev->lock);
if(pmydev->curlen <= 0)
{
if(pfile->f_flags & O_NONBLOCK)
{//非阻塞
mutex_unlock(&pmydev->lock);
printk("O_NONBLOCK No Data Read\n");
return -1;
}
else
{//阻塞
mutex_unlock(&pmydev->lock);
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret)
{
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
mutex_lock(&pmydev->lock);
}
}
if(count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret)
{
mutex_unlock(&pmydev->lock);
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
pmydev->curlen -= size;
mutex_unlock(&pmydev->lock);
wake_up_interruptible(&pmydev->wq);
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
mutex_lock(&pmydev->lock);
if(pmydev->curlen >= BUF_LEN)
{
if(pfile->f_flags & O_NONBLOCK)
{
mutex_unlock(&pmydev->lock);
printk("O_NONBLOCK Can not write data\n");
return -1;
}
else
{
mutex_unlock(&pmydev->lock);
ret = wait_event_interruptible(pmydev->wq,pmydev->curlen < BUF_LEN);
if(ret)
{
printk("wake up by signal\n");
return -ERESTARTSYS;
}
mutex_lock(&pmydev->lock);
}
}
if(count > BUF_LEN-pmydev->curlen)
{
size = BUF_LEN - pmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret)
{
mutex_unlock(&pmydev->lock);
printk("copy_from_user failed\n");
return -1;
}
pmydev->curlen += size;
mutex_unlock(&pmydev->lock);
wake_up_interruptible(&pmydev->rq);
//此处添加
//如果对象不为空 则发送sigio信号 代表有数据可读
if(pmydev->pasync_obj != NULL)
{
kill_fasync(&pmydev->pasync_obj,SIGIO,POLL_IN);
}
return size;
}
long mychar_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)
{
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
switch(cmd)
{
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret)
{
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
mutex_lock(&pmydev->lock);
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
mutex_unlock(&pmydev->lock);
if(ret)
{
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The cmd is unknow\n");
return -1;
}
return 0;
}
unsigned int mychar_poll(struct file *pfile,poll_table *ptb)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb);
poll_wait(pfile,&pmydev->wq,ptb);
mutex_lock(&pmydev->lock);
if(pmydev->curlen > 0)
{
mask |= POLLIN | POLLRDNORM;
}
if(pmydev->curlen < BUF_LEN)
{
mask |= POLLOUT | POLLWRNORM;
}
mutex_unlock(&pmydev->lock);
return mask;
}
//此处添加
int mychar_fasync(int fd,struct file *pfile,int mode)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
return fasync_helper(fd,pfile,mode,&pmydev->pasync_obj);
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.unlocked_ioctl = mychar_ioctl,
.poll = mychar_poll,
.fasync = mychar_fasync,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
/*申请设备号*/
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//容易遗漏,注意
}
pgmydev = (struct mychar_dev *)kmalloc(sizeof(struct mychar_dev),GFP_KERNEL);
if(NULL == pgmydev)
{
unregister_chrdev_region(devno,mychar_num);
printk("kmallc for struct mychar_dev failed\n");
return -1;
}
/*给struct cdev对象指定操作函数集*/
cdev_init(&pgmydev->mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev,devno,mychar_num);
init_waitqueue_head(&pgmydev->rq);
init_waitqueue_head(&pgmydev->wq);
mutex_init(&pgmydev->lock);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,mychar_num);
kfree(pgmydev);
pgmydev = NULL;
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
测试程序:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <stdio.h>
#include "mychar.h"
int fd = -1;
void sigio_handler(int signo);
int main(int argc,char *argv[])
{
int flg = 0;
if(argc < 2)
{
printf("The argument is too few\n");
return 1;
}
signal(SIGIO,sigio_handler);
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("open %s failed\n",argv[1]);
return 2;
}
fcntl(fd,F_SETOWN,getpid());
flg = fcntl(fd,F_GETFL);
flg |= FASYNC;
fcntl(fd,F_SETFL,flg);
while(1)
{
//死循环
}
close(fd);
fd = -1;
return 0;
}
void sigio_handler(int signo)
{
char buf[8] = "";
read(fd,buf,8);
printf("buf=%s\n",buf);
}