当前位置:网站首页>嵌入式系统驱动初级【2】——字符设备驱动基础上_基础框架
嵌入式系统驱动初级【2】——字符设备驱动基础上_基础框架
2022-08-02 12:57:00 【imysy_22_】
一、Linux内核对设备的分类
linux的文件种类:
1. -:普通文件
2. d:目录文件
3. p:管道文件
4. s:本地socket文件
5. l:链接文件
6. c:字符设备
7. b:块设备
Linux内核按驱动程序实现模型框架的不同,将设备分为三类:
1. 字符设备:按字节流形式进行数据读写的设备,一般情况下按顺序访问,数据量不大,一般不设缓存
2. 块设备:按整块进行数据读写的设备,最小的块大小为512字节(一个扇区),块的大小必须是扇区的整数倍,Linux系统的块大小一般为4096字节,随机访问,设缓存以提高效率
3. 网络设备:针对网络数据收发的设备
总体框架图:
二、设备号------内核中同类设备的区分
内核用设备号来区分同类里不同的设备,设备号是一个无符号32位整数,数据类型为dev_t,设备号分为两部分:
1. 主设备号:占高12位,用来表示驱动程序相同的一类设备
2. 次设备号:占低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
三、申请和注销设备号
字符驱动开发的第一步是通过模块的入口函数向内核添加本设备驱动的代码框架,主要完成:
1. 申请设备号
2. 定义、初始化、向内核添加代表本设备的结构体元素
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文件对应的记录消失
示例程序:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
int major = 11;
int minor = 0;
int num = 1;
int __init imysy_22_test_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,num,"imysy_22_test");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,num,"imysy_22_test");
if(ret)
{
printk("devno failed.\n");
return -1;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
return 0;
}
void __exit imysy_22_test_exit(void)
{
dev_t devno = MKDEV(major,minor);
unregister_chrdev_region(devno, num);
}
MODULE_LICENSE("GPL");
module_init(imysy_22_test_init);
module_exit(imysy_22_test_exit);
注意:刚开始是没添加头文件#include <linux/fs.h>的,编译会提示报错,我们通过以下命令来查找报错的未定义函数的来源:
cd ~/fs4412/linux3.14/include/linux
grep xxx/*函数名*/ -r -n
四、函数指针复习
内存的作用-----用来存放程序运行过程中的
1. 数据
2. 指令
## 4.1、 内存四区
堆区
栈区
数据区
代码区
## 4.2、C语言中内存数据的访问方式
直接访问:通过所在空间名称去访问
间接访问:通过所在空间首地址去访问 \*地址值 此时的\*为间接访问运算符
## 4.3、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;
## 4.4 适用场合
前提:当有很多个同类函数待被调用时
A处:知道所有函数名,由此处来决定B处将会调用哪个函数
B处:负责调用A处指定的函数
思考:A处如何告诉B处被调用的是哪个函数呢,无非两个办法:
1. 告诉B处函数名,怎么做呢?传字符串----“函数名”? C语言没有对应语法支持
2. 告诉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,两种方法定义一个设备:
1. 直接定义:定义结构体全局变量
2. 动态申请:
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:指向被移除的字符设备
小结:
字符设备驱动开发步骤:
1. 如果设备有自己的一些控制数据,则定义一个包含struct cdev cdev成员的结构体struct mydev,其它成员根据设备需求,设备简单则直接用struct cdev
2. 定义一个struct mydev或struct cdev的全局变量来表示本设备;也可以定义一个struct mydev或struct cdev的全局指针(记得在init时动态分配)
3. 定义三个全局变量分别来表示主设备号、次设备号、设备数
4. 定义一个struct file_operations结构体变量,其owner成员置成THIS_MODULE
5. module init函数流程:a. 申请设备号 b. 如果是全局设备指针则动态分配代表本设备的结构体元素 c. 初始化struct cdev成员 d. 设置struct cdev的owner成员为THIS_MODULE e. 添加字符设备到内核
6. module exit函数:a. 注销设备号 b. 从内核中移除struct cdev c. 如果如果是全局设备指针则释放其指向空间
7. 编写各个操作函数并将函数名初始化给struct file_operations结构体变量
验证操作步骤:
1. 编写驱动代码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 num = 1;
struct cdev mydev;
int myopen (struct inode *pnode, struct file *pfile)
{
printk("My OPEN function is running.\n");
return 0;
}
int myclose (struct inode *pnode, struct file *pfile)
{
printk("My CLOSE function is running.\n");
return 0;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = myopen,
.release = myclose,
};
int __init imysy_22_test_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,num,"imysy_22_test");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,num,"imysy_22_test");
if(ret)
{
printk("devno failed.\n");
return -1;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
/*给mydev指定操作函数集*/
cdev_init(&mydev,&myops);
/*将mydev添加到内核对应的数据结构里*/
mydev.owner = THIS_MODULE;
cdev_add(&mydev,devno,num);
return 0;
}
void __exit imysy_22_test_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&mydev);
unregister_chrdev_region(devno, num);
}
MODULE_LICENSE("GPL");
module_init(imysy_22_test_init);
module_exit(imysy_22_test_exit);
2. make生成ko文件
3. insmod内核模块
4. 查阅字符设备用到的设备号(主设备号):cat /proc/devices | grep 申请设备号时用的名字
5. 创建设备文件(设备节点) : mknod /dev/名字 c 上一步查询到的主设备号 代码中指定初始次设备号
6. 编写app验证驱动(testmychar_app.c)
7. 编译运行app,dmesg命令查看内核打印信息
六、字符设备驱动框架解析
设备的操作函数如果比喻是桩的话(性质类似于设备操作函数的函数,在一些场合被称为桩函数),则:
驱动实现设备操作函数 ----------- 做桩
insmod调用的init函数主要作用 --------- 钉桩
rmmod调用的exitt函数主要作用 --------- 拔桩
应用层通过系统调用函数间接调用这些设备操作函数 ------- 用桩
## 6.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对象的地址
*/
## 6.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;
}
## 6.3 参考原理图
## 6.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
读写操作的实现过程:
1、编写imysy_22_test.c
#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 num = 1;
char mydev_buff[BUF_LEN];
int curlen = 0;
struct cdev mydev;
int myopen (struct inode *pnode, struct file *pfile)
{
printk("My OPEN function is running.\n");
return 0;
}
int myclose (struct inode *pnode, struct file *pfile)
{
printk("My CLOSE function is running.\n");
return 0;
}
ssize_t myread(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos)
{
int size;
int ret = 0;
if(count > curlen)
{
size = curlen;
}
else
{
size = count;
}
ret = copy_to_user(pbuf,mydev_buff,size);
if(ret)
{
printk("my read copy_to_user failed\n");
return -1;
}
memcpy(mydev_buff,mydev_buff + size,curlen - size);
curlen = curlen - size;
return size;
}
ssize_t mywrite(struct file *filp,const char __user *pbuf, size_t count, loff_t *ppos)
{
int size;
int ret = 0;
if(count > BUF_LEN - curlen)
{
size = BUF_LEN - curlen;
}
else
{
size = count;
}
ret = copy_from_user(mydev_buff + curlen,pbuf,size);
if(ret)
{
printk("my read copy_from_user failed\n");
return -1;
}
//memcpy(mydev_buff + size,mydev_buff,size);
curlen = curlen + size;
return size;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = myopen,
.release = myclose,
.read = myread,
.write = mywrite,
};
int __init imysy_22_test_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,num,"imysy_22_test");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,num,"imysy_22_test");
if(ret)
{
printk("devno failed.\n");
return -1;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
/*给mydev指定操作函数集*/
cdev_init(&mydev,&myops);
/*将mydev添加到内核对应的数据结构里*/
mydev.owner = THIS_MODULE;
cdev_add(&mydev,devno,num);
return 0;
}
void __exit imysy_22_test_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&mydev);
unregister_chrdev_region(devno, num);
}
MODULE_LICENSE("GPL");
module_init(imysy_22_test_init);
module_exit(imysy_22_test_exit);
2、编写testmychar_app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
char buf[8] = "Hello";
if(argc < 2)
{
printf("The argument is too few.\n");
return -1;
}
int fd = -1;
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("open failed\n");
return -1;
}
write(fd,"Hello",6);
read(fd,buf,8);
printf("buf=%s\n",buf);
close(fd);
fd = -1;
return 0;
}
3、执行下列命令
执行Makefile编译生成.ko文件
make
移除之前已插入的内核模块,插入新的内核模块
sudo rmmod imysy_22_test
sudo insmod imysy_22_test.ko
查看一下分配的设备号
cat /proc/devices | grep imysy_22_test
创造这个设备的节点
sudo mknod /dev/mydev c 11 0
编译生成testmychar_app
gcc testmychar_app.c -o testmychar_app
执行程序
./testmychar_app /dev/mydev
输出的结果:
九、ioctl操作实现
注:在linux驱动开发中,在函数中使用全局变量是非常危险的,我们要能少使用就尽量少使用。
详见:(7条消息) 为什么尽量少使用全局变量?__wangpan的博客-CSDN博客_尽量少使用全局变量
解决办法:
已知成员的地址获得所在结构体变量的地址:container_of(成员地址,结构体类型名,成员在结构体中的名称)
这样就可以不直接使用全局变量
把我们之前的imysy_22_test.c修改为:
1、定义一个结构体的全局变量,把能放在里面的全局变量都放里边
2、在open函数中通过:
pfile->private_data = (void *)container_of(pnode->i_cdev,struct mychar_dev,mydev)
在read和write函数中通过:
和struct mychar_dev *pgmydev = (struct mychar_dev *)filp->private_data;
的方法,通过传参来得知全局变量的地址。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#define BUF_LEN 100
struct mychar_dev {
char mydev_buff[BUF_LEN];
int curlen;
struct cdev mydev;
};
int major = 11;
int minor = 0;
int num = 1;
struct mychar_dev gmydev;
int myopen (struct inode *pnode, struct file *pfile)
{
//能避免不用全局变量名就不用
pfile->private_data = (void *)container_of(pnode->i_cdev,struct mychar_dev,mydev);
printk("My OPEN function is running.\n");
return 0;
}
int myclose (struct inode *pnode, struct file *pfile)
{
printk("My CLOSE function is running.\n");
return 0;
}
ssize_t myread(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos)
{
//能避免不用全局变量名就不用,这里定义的是指针
struct mychar_dev *pgmydev = (struct mychar_dev *)filp->private_data;
int size;
int ret = 0;
if(count > pgmydev->curlen)
{
size = pgmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(pbuf,pgmydev->mydev_buff,size);
if(ret)
{
printk("my read copy_to_user failed\n");
return -1;
}
memcpy(pgmydev->mydev_buff,pgmydev->mydev_buff + size,pgmydev->curlen - size);
pgmydev->curlen = pgmydev->curlen - size;
return size;
}
ssize_t mywrite(struct file *filp,const char __user *pbuf, size_t count, loff_t *ppos)
{
//能避免不用全局变量名就不用,这里定义的是指针
struct mychar_dev *pgmydev = (struct mychar_dev *)filp->private_data;
int size;
int ret = 0;
if(count > BUF_LEN - pgmydev->curlen)
{
size = BUF_LEN - pgmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pgmydev->mydev_buff + pgmydev->curlen,pbuf,size);
if(ret)
{
printk("my read copy_from_user failed\n");
return -1;
}
//memcpy(mydev_buff + size,mydev_buff,size);
pgmydev->curlen = pgmydev->curlen + size;
return size;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = myopen,
.release = myclose,
.read = myread,
.write = mywrite,
};
int __init imysy_22_test_init(void)
{
//在这个函数中没法避免不不使用全局变量名,那就只能用了
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,num,"imysy_22_test");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,num,"imysy_22_test");
if(ret)
{
printk("devno failed.\n");
return -1;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
/*给mydev指定操作函数集*/
cdev_init(&gmydev.mydev,&myops);
/*将mydev添加到内核对应的数据结构里*/
gmydev.mydev.owner = THIS_MODULE;
cdev_add(&gmydev.mydev,devno,num);
return 0;
}
void __exit imysy_22_test_exit(void)
{
//在这个函数中没法避免不不使用全局变量名,那就只能用了
dev_t devno = MKDEV(major,minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno, num);
}
MODULE_LICENSE("GPL");
module_init(imysy_22_test_init);
module_exit(imysy_22_test_exit);
long xxx_ioctl (struct file *filp, unsigned int cmd, unsigned long arg);
功能:对相应设备做指定的控制操作(各种属性的设置获取等等)
参数:
filp:指向open产生的struct file类型的对象,表示本次ioctl对应的那次open
cmd:用来表示做的是哪一个操作
arg:和cmd配合用的参数
返回值:成功为0,失败-1
实现过程:
1、编写imysy_22_test.h
#ifndef IMYSY_22_TEST_H
#define IMYSY_22_TEST_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
2、修改imysy_22_test.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include "imysy_22_test.h"
#define BUF_LEN 100
struct mychar_dev {
char mydev_buff[BUF_LEN];
int curlen;
struct cdev mydev;
};
int major = 11;
int minor = 0;
int num = 1;
struct mychar_dev gmydev;
int myopen (struct inode *pnode, struct file *pfile)
{
//能避免不用全局变量名就不用
pfile->private_data = (void *)container_of(pnode->i_cdev,struct mychar_dev,mydev);
printk("My OPEN function is running.\n");
return 0;
}
int myclose (struct inode *pnode, struct file *pfile)
{
printk("My CLOSE function is running.\n");
return 0;
}
ssize_t myread(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos)
{
//能避免不用全局变量名就不用,这里定义的是指针
struct mychar_dev *pgmydev = (struct mychar_dev *)filp->private_data;
int size;
int ret = 0;
if(count > pgmydev->curlen)
{
size = pgmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(pbuf,pgmydev->mydev_buff,size);
if(ret)
{
printk("my read copy_to_user failed\n");
return -1;
}
memcpy(pgmydev->mydev_buff,pgmydev->mydev_buff + size,pgmydev->curlen - size);
pgmydev->curlen = pgmydev->curlen - size;
return size;
}
ssize_t mywrite(struct file *filp,const char __user *pbuf, size_t count, loff_t *ppos)
{
//能避免不用全局变量名就不用,这里定义的是指针
struct mychar_dev *pgmydev = (struct mychar_dev *)filp->private_data;
int size;
int ret = 0;
if(count > BUF_LEN - pgmydev->curlen)
{
size = BUF_LEN - pgmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pgmydev->mydev_buff + pgmydev->curlen,pbuf,size);
if(ret)
{
printk("my read copy_from_user failed\n");
return -1;
}
//memcpy(mydev_buff + size,mydev_buff,size);
pgmydev->curlen = pgmydev->curlen + size;
return size;
}
long myioctl(struct file *filp,unsigned int cmd, unsigned long arg)
{
//能避免不用全局变量名就不用,这里定义的是指针
struct mychar_dev *pgmydev = (struct mychar_dev *)filp->private_data;
int ret = 0;
int __user * pret = (int *)arg;
int maxlen = BUF_LEN;
switch (cmd)
{
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret, &maxlen, sizeof(int));
if(ret)
{
printk("myioctl copy_to_user failed\n");
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
ret = copy_to_user(pret, &pgmydev->curlen, sizeof(int));
if(ret)
{
printk("myioctl copy_to_user failed\n");
}
break;
default:
printk("myioctl cmd is unknow\n");
return -1;
}
return 0;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = myopen,
.release = myclose,
.read = myread,
.write = mywrite,
.unlocked_ioctl = myioctl,
};
int __init imysy_22_test_init(void)
{
//在这个函数中没法避免不不使用全局变量名,那就只能用了
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,num,"imysy_22_test");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,num,"imysy_22_test");
if(ret)
{
printk("devno failed.\n");
return -1;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
/*给mydev指定操作函数集*/
cdev_init(&gmydev.mydev,&myops);
/*将mydev添加到内核对应的数据结构里*/
gmydev.mydev.owner = THIS_MODULE;
cdev_add(&gmydev.mydev,devno,num);
return 0;
}
void __exit imysy_22_test_exit(void)
{
//在这个函数中没法避免不不使用全局变量名,那就只能用了
dev_t devno = MKDEV(major,minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno, num);
}
MODULE_LICENSE("GPL");
module_init(imysy_22_test_init);
module_exit(imysy_22_test_exit);
2、修改testmychar_app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "imysy_22_test.h"
int main(int argc,char *argv[])
{
if(argc < 2)
{
printf("The argument is too few.\n");
return -1;
}
char buf[8] = "Hello";
int fd = -1;
int maxlen = 0;
int curlen = 0;
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("open failed\n");
return -1;
}
ioctl(fd,MYCHAR_IOCTL_GET_MAXLEN,&maxlen);
printf("ioctl: maxlen=%d\n",maxlen);
write(fd,"Hello",6);
ioctl(fd,MYCHAR_IOCTL_GET_CURLEN,&curlen);
printf("ioctl: curlen=%d\n",curlen);
read(fd,buf,8);
printf("buf=%s\n",buf);
close(fd);
fd = -1;
return 0;
}
make编译
输入以下命令:
sudo rmmod imysy_22_test 移除之前未修改的内核模块
sudo insmod imysy_22_test.ko 插入修改完的内核模块
cat /proc/devices | grep imysy 查询内核模块是否生效
ls /dev/mydev 确保设备文件中有你创建的那个设备
没有的话 sudo mknod /dev/mydev c(字符设备) 主设备号(cat的那个)次设备号(一般是0)
再执行 sudo chmod a+w /dev/mydev
./testmychar_app /dev/mydev
结果:
查看到了设备的一些属性 (下面那个buf的长度)
十、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
以上三个操作对每个具体设备都要进行
1、修改imysy_22_test.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include "imysy_22_test.h"
#define BUF_LEN 100
#define DEV_NUM 3 //三个设备
struct mychar_dev {
char mydev_buff[BUF_LEN];
int curlen;
struct cdev mydev;
};
int major = 11;
int minor = 0;
int num = DEV_NUM;
struct mychar_dev gmydev_arr[DEV_NUM];
int myopen (struct inode *pnode, struct file *pfile)
{
//能避免不用全局变量名就不用
pfile->private_data = (void *)container_of(pnode->i_cdev,struct mychar_dev,mydev);
printk("My OPEN function is running.\n");
return 0;
}
int myclose (struct inode *pnode, struct file *pfile)
{
printk("My CLOSE function is running.\n");
return 0;
}
ssize_t myread(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos)
{
//能避免不用全局变量名就不用,这里定义的是指针
struct mychar_dev *pgmydev = (struct mychar_dev *)filp->private_data;
int size;
int ret = 0;
if(count > pgmydev->curlen)
{
size = pgmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(pbuf,pgmydev->mydev_buff,size);
if(ret)
{
printk("my read copy_to_user failed\n");
return -1;
}
memcpy(pgmydev->mydev_buff,pgmydev->mydev_buff + size,pgmydev->curlen - size);
pgmydev->curlen = pgmydev->curlen - size;
return size;
}
ssize_t mywrite(struct file *filp,const char __user *pbuf, size_t count, loff_t *ppos)
{
//能避免不用全局变量名就不用,这里定义的是指针
struct mychar_dev *pgmydev = (struct mychar_dev *)filp->private_data;
int size;
int ret = 0;
if(count > BUF_LEN - pgmydev->curlen)
{
size = BUF_LEN - pgmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pgmydev->mydev_buff + pgmydev->curlen,pbuf,size);
if(ret)
{
printk("my read copy_from_user failed\n");
return -1;
}
//memcpy(mydev_buff + size,mydev_buff,size);
pgmydev->curlen = pgmydev->curlen + size;
return size;
}
long myioctl(struct file *filp,unsigned int cmd, unsigned long arg)
{
//能避免不用全局变量名就不用,这里定义的是指针
struct mychar_dev *pgmydev = (struct mychar_dev *)filp->private_data;
int ret = 0;
int __user * pret = (int *)arg;
int maxlen = BUF_LEN;
switch (cmd)
{
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret, &maxlen, sizeof(int));
if(ret)
{
printk("myioctl copy_to_user failed\n");
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
ret = copy_to_user(pret, &pgmydev->curlen, sizeof(int));
if(ret)
{
printk("myioctl copy_to_user failed\n");
}
break;
default:
printk("myioctl cmd is unknow\n");
return -1;
}
return 0;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = myopen,
.release = myclose,
.read = myread,
.write = mywrite,
.unlocked_ioctl = myioctl,
};
int __init imysy_22_test_init(void)
{
//在这个函数中没法避免不不使用全局变量名,那就只能用了
int ret = 0;
int i;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,num,"imysy_22_test");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,num,"imysy_22_test");
if(ret)
{
printk("devno failed.\n");
return -1;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
for(i = 0;i < DEV_NUM;i++)
{
devno = MKDEV(major,minor+i);
/*给mydev指定操作函数集*/
cdev_init(&gmydev_arr[i].mydev,&myops);
/*将mydev添加到内核对应的数据结构里*/
gmydev_arr[i].mydev.owner = THIS_MODULE;
cdev_add(&gmydev_arr[i].mydev,devno,1);
}
return 0;
}
void __exit imysy_22_test_exit(void)
{
int i;
//在这个函数中没法避免不不使用全局变量名,那就只能用了
dev_t devno = MKDEV(major,minor);
for(i = 0;i < DEV_NUM;i++)
{
cdev_del(&gmydev_arr[i].mydev);
}
unregister_chrdev_region(devno, num);
}
MODULE_LICENSE("GPL");
module_init(imysy_22_test_init);
module_exit(imysy_22_test_exit);
2、修改Makefile
增加:obj-m += multichardev.o
make编译
3、如图
4、结果:
边栏推荐
- 水平垂直居中方式
- There are several ways to jump to js source code, jump on the current page, jump on the blank page
- LeetCode_377_组合总和Ⅳ
- Introduction to Scala Basic Syntax (3) Various Operators in Scala
- 新特性解读 | MySQL 8.0 GIPK 不可见主键
- Cannot determine loading status from target frame detached when selenium chrome driver is running
- js秒表倒计时插件
- Automatically generate code generator recommendation-code-gen
- [b01lers2020]Welcome to Earth-1
- 麻烦问一下,对mysql 场景注入故障,是不是不是对mysql server 端注入故障,只是对ja
猜你喜欢
随机推荐
高效代码静态测试工具Klocwork 2022.2——Portal全新升级、支持RLM
国产 GPU 创业潮 喧嚣下的资本游戏
如何关闭开启硬件加速[通俗易懂]
Custom mvc framework review
js炫酷仪表盘插件
How to implement waterfall flow layout (what is waterfall flow layout)
Intouch Historian历史曲线配置导入导出
js stopwatch countdown plugin
Closures in JS
Interpretation of new features | MySQL 8.0 GIPK invisible primary key
scrapy框架初识1
How to use the database like tap water?|Tencent Cloud Database TDSQL-C
设置代理服务器(谷歌+IE)「建议收藏」
SQL Server 2014 installation tutorial (nanny-level graphic tutorial)
Intouch System Platform IDE-1
智能手表前景如何?
自定义mvc框架复习
Openlayers Quick Start Tutorial
Wireless vibrating wire acquisition instrument remote modification method
pytorch模型转tensorflow模型