一、字符设备注册的本质及注册注销步骤
字符设备驱动注册的本质
只要某个信息存在于操作系统上,在操作系统中一定存在一个描述这个信息的对象,字符设备驱动注册进内核,在内核中一定会存在一个字符设备驱动对象保存当前的字符设备驱动的信息。
字符设备驱动对象结构体分析 struct cdev
struct dev{
struct kobject kobj; //内核基类的对象
struct module *owner; //指向这个字符设备驱动对象的指针 THIS_MODULE
const struct file_operations *ops; //操作方法结构体
dev_t dev; //设备号的起始值
unsigned int countl; //设备数量
};
字符设备驱动注册、注销的过程
/********************注册过程***************/
1.申请一个字符设备驱动对象
2.为字符设备驱动对象初始化赋值
3.向内核申请使用的设备号和指定数量的设备资源
4.将字符设备驱动对象注册进内核
/********************注销过程*****************/1.注销字符设备对象
2.释放申请的设备号3.释放字符设备驱动对象空间
字符设备驱动注册、注销相关API
*******************注册过程**************
#include <linux/cdev.h>
1.申请字符设备驱动对象
a:struct cdev cdev;
b:struct cdev *cdev = cdev_alloc();
/*
struct cdev *cdev_alloc(void)
功能:向内核申请一个struct cdev对象
返回值:成功返回申请到的对象空间首地址,失败返回NULL
2.字符设备驱动对象的初始化
void cdev_init(struct cdev *cdev, const struct file_operation *fops)
功能:实现字符设备驱动对象成员的部分初始化参数: cdev :字符设备对象指针
fops:操作方法指针
3.向内核申请使用的设备号和指定数量的设备资源3.1 静态申请设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name)参数:from:要申请的设备号的起始值
count:要申请的设备资源数量
name:驱动名
3.2 动态申请设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminod, unsigned count, const char *name)
参数:dev:保存申请到的设备号的空间首地址
baseminor:要申请的次设备号的起始值
count:申请的设备资源的数量
name:驱动名返回值:成功返回0, 失败返回错误码
4.注册字符设备驱动对象
int cdev_add(struct cdev *p, dev_t dev, unsigned cout)参数:p :字符设备驱动对象指针
dev:申请的设备号起始值
count:申请的设备资源的数量
/*******************************注销过程******************************/1.注销字符设备驱动对象
void cdev_del(struct cdev *p)
参数:字符设备驱动对象指针
2.释放申请的设备号
void unregister_chrdev_region(dev_t from, unsigned count)参数:from:设备号的起始值
count:设备资源的数量
3.释放字符设备驱动对象空间void kfree(struct cdev *p)
参数:p:字符设备驱动对象指针
字符设备驱动对象分布注册注销示例
驱动代码
#include <linux/init.h>
#include <linux/module.h>
static int __init mycdev_init(void)
{
//1.申请一个对象空间cdev_alloc
//2.初始化对象cdev_init
//3.申请设备号 register_chrdev_region()/alloc_chrdev_region()
//4.注册驱动对象 cdev_add
//5.向上提交目录 class_create
//6.向上提交设备节点信息 device_create
return 0;
}
static void __exit mycdev_exit(void)
{
//1.销毁设备节点信息
//2.销毁目录
//3.注销字符设备驱动对象
//4.释放设备号
//5.释放申请到的字符设备驱动对象空间
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
应用层代码
#include <linux/init.h>
#include <linux/module.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/cdev.h>
#include<linux/slab.h>
struct cdev *cdev;
unsigned int major=0;
unsigned int minor=0;
dev_t devno;
struct class *cls;
struct device *dev;
int mycdev_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
// 定义操作方法结构体变量并赋值
struct file_operations fops = {
.open = mycdev_open,
.unlocked_ioctl = mycdev_ioctl,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
//1.申请一个对象空间cdev_alloc
int ret;
cdev= cdev_alloc();
if(cdev==NULL)
{
printk("申请字符设备驱动对象失败\n");
ret=-EFAULT;
goto out1;
}
printk("字符设备驱动对象申请成功\n");
//2.初始化对象cdev_init
cdev_init(cdev,&fops);
//3.申请设备号 register_chrdev_region()/alloc_chrdev_region()
if(major==0)//动态申请
{
ret=alloc_chrdev_region(&devno,minor,3,"mychrdev");
if(ret)
{
printk("动态申请设备号失败\n");
goto out2;
}
major=MAJOR(devno);//根据设备号获取主设备号
minor=MINOR(devno);//根据设备号获取次设备号
}
else
{
ret=register_chrdev_region(MKDEV(major,minor),3,"mychrdev");
if(ret)
{
printk("静态指定设备号失败\n");
goto out2;
}
}
printk("设备号申请成功\n");
//4.注册驱动对象 cdev_add
ret=cdev_add(cdev,MKDEV(major,minor),3);
if(ret)
{
printk("注册字符设备驱动对象失败\n");
goto out3;
}
printk("注册字符设备驱动对象成功\n");
//5.向上提交目录 class_create
cls=class_create(THIS_MODULE,"mychrdev");
if(IS_ERR(cls))
{
printk("向上提交目录失败\n");
goto out4;
}
printk("向上提交目录成功\n");
//6.向上提交设备节点信息 device_create
int i;
for(i=0;i<3;i++)
{
dev=device_create(cls,NULL,MKDEV(major,i),NULL,"mycdev%d",i);
if(IS_ERR(dev))
{
printk("向上提交设备节点失败\n");
goto out5;
}
}
printk("向上提交设备节点信息成功\n");
return 0;
out5:
//将提交成功的节点信息释放
for(--i;i>=0;i--)
{
device_destroy(cls,MKDEV(major,i));
}
//销毁目录
class_destroy(cls);
out4:
cdev_del(cdev);
out3:
unregister_chrdev_region(MKDEV(major,minor),3);
out2:
kfree(cdev);
out1:
return ret;
}
static void __exit mycdev_exit(void)
{
//1.销毁设备节点信息
int i;
for(i=0;i<3;i++)
{
device_destroy(cls,MKDEV(major,i));
}
//2.销毁目录
class_destroy(cls);
//3.注销字符设备驱动对象
cdev_del(cdev);
//4.释放设备号
unregister_chrdev_region(MKDEV(major,minor),3);
//5.释放申请到的字符设备驱动对象空间
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
二、struct inode/file结构体的作用
struct inode结构体的作用
1.inode的作用
只要文件存放在操作系统中,操作系统内核中就一定会有一个struct inode结构体对象,保存当前文件的相关信息,每一个文件都有一个自己特定的标识,叫做 inod号,inode号同时也是文件对应的inode结构体的索引号。
struct inode{
umode_t i_mode; //文件的权限
unsigned short i_opflags;
kuid_t i_uid; //文件的用户ID
kuid_t i_gid; //组ID
unsigned int i_flags;
dev_t i_rdev; //设备号
union{
struct block_device *i_bdev; //块设备
struct cdeb *i_cdv; //字符设备
};
void *i_private; //当前文件的私有数据,可以用于函数数值传递
};
2.open函数如何根据文件路径调到驱动的操作方法
struct file结构体的作用
1.文件描述符的作用
当我们在一个进程中打开一个文件时,open函数打开成功会返回一个非负的整数,这个整数就是文件描述符。在一个进程中最多可以分配1024个文件描述符(文件描述符本质是数组下标0-1023)。每一个进程都会有自己独立的一套文件描述符,文件描述符依赖于进程存在。所以想要知道文件描述符的作用,就要知道文件描述符在进程中的位置。
只要进程存在操作系统中,在操作系统内核一定会存在一个struct task_struct对象,用来保存进程的相关信息
struct task_struct {
volatile long state; //进程状态
int on_cpu; //标识进程在哪个cpu上z执行
int prio; //进程优先级
pid_t pid; //进程号
struct task_struct __rcl *real_parent; //父进程
struct files_struct *files; //打开的文件相关结构体指针
};
在struct task_struct对象中,有一个打开的文件相关结构体指针
struct files_struct {
struct file __rcu *fd_array[NR_OPEN_DEFULT]; //结构体指针数组,存放的是struct file对象的地址(struct file对象存放的是打开的文件的相关信息)
};//文件描述符就是这个数组的下标
struct file结构体功能
当我们在进程中打开一个文件,在内核中会申请一个struct file类型的结构体空间,这个空间保存的打开的文件信息。这个struct file对象的地址会保存到内核中的一个数组fd_array中,而文件描述符就是这个数组对应位置的下标
struct file {
struct path f_path; //文件路径
struct inode *f_inode;
const struct file_operations *f_op; //操作方法结构体
unsigned int f_flags; //open函数的第二个参数赋值给f_flags
fmode_t f_mode; /打开的文件的权限
void *private_date; //私有数据,可以实现函数间数据的传递
};
系统调用函数如何通过文件描述符找到驱动中对应的操作方法
fd文件描述符申请和生成struct file结构体的过程
在驱动程序中完成设备文件和具体设备的绑定
方法1:
1.属于同一个类型的设备,不同设备之间次设备号不同,可以将不同设备文件与具体设备绑定
2.在open方法中参数是struct inode*和struct file*结构体对象指针,
3.在struct inode对象中有一个成员设备号 i_rdev, 可以通过int min = MINOR(inode->i_rdev获取到次设备号。
4.将获取到的次设备号min 赋值给 struct file对象的私有数据成员(void *private,可以用来作为函数间的数据传递), file->private = (void *)min
5.在ioctl/read等操作方法中也有一个struct file*对象指针,int min = (int)(file->private); 通过判断私有数据成员,来进行判断设备文件对应哪个具体设备。
方法2:在ioctl/read等操作方法中有一个struct file*对象指针,struct file对象中有struct inode*对象指针,在struct inode对象中有一个成员设备号 i_rdev, 可以通过int min = MINOR(inode->i_rdev获取到次设备号。
int min = MINOR(file->inode->i_rdev);
代码示例
int mycdev_open(struct inode *inode, struct file *file)
{
int min=MINOR(inode->i_rdev);//获取打开的的文件的次设备号
file->private_data= (void *)min;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int min=(int)file->private_data;//获取到文件的次设备号
switch(min)
{
case 0://操作LED1
switch(cmd)
{
case LED_ON://开灯
break;
case LED_OFF:
//关灯
break;
}
break;
case 1://操作LED2
switch(cmd)
{
case LED_ON://开灯
break;
case LED_OFF:
//关灯
break;
}
break;
case 2://操作LED3
switch(cmd)
{
case LED_ON://开灯
break;
case LED_OFF:
//关灯
break;
}
break;
}
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}