对register_chrdev内部的实现过程分析,注册字符驱动的过程有以下几步
1、分配struct cdev对象空间
2、初始化struct cdev对象
3、注册cdev对象
以上三步完成了字符设备驱动的注册
只要有一个驱动存在于系统内核中,就会存在一个struct cdev对象,对象中是关于当前驱动的相关驱动的相关描述信息
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;//设备资源数量
};
1.分配 字符设备驱动对象
a.struct cdev cdev;
b.struct cdev *cdev = cdev_alloc();
/*
struct cdev *cdev_alloc(void)
功能:申请一个字符设备驱动对象空间
参数:无
返回值:成功返回申请的空间首地址
失败返回NULL
*/
2.字符设备驱动对象初始化
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
功能:实现字符设备驱动的部分初始化
参数:
cdev:字符设备驱动对象指针
fops:操作方法结构体指针
返回值:无
3.设备号的申请
3.1 静态指定设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:静态申请设备号并注册一定数量的设备资源
参数:
from:静态指定的设备号(第一个设备的设备号)
count:申请的设备数量
name:设备名或者驱动名
返回值:成功返回0,失败返回错误码
3.2 动态申请设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
功能:动态申请设备号并注册一定数量的设备资源
参数:
dev:存放申请的到的设备号的空间首地址
baseminor:次设备号的起始值
count:申请的设备资源数量
name:设备名或者驱动名
返回值:成功返回0,失败返回错误码
4.根据申请的设备号和驱动对象注册驱动
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
功能:注册字符设备驱动对象
参数:
cdev:字符设备驱动对象指针
dev:申请的设备号的第一个值
count:申请的设备资源的数量
返回值:成功返回0,失败返回错误码
***********注销过程*****************
1.注销驱动对象
void cdev_del(struct cdev *p)
参数:
p:要注销的对象空间指针
返回值:无
2.释放申请的设备号和设备资源
void unregister_chrdev_region(dev_t from, unsigned count)
参数:
from:申请的第一个设备号
count:申请的设备资源的数量
返回值:无
释放字符设备驱动对象空间
3.void kfree(void *addr)
功能:释放申请的内核空间
参数:要释放的空间首地址
返回值:无
需要注意的是,每个步骤失败后销毁每个步骤前上一个步骤申请成功的资源
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;
只要文件存在于操作系统上,那么在系统内核中就一定会存在一个struct inode结构体对象用来描述当前文件的相关信息
4.1inode结构体内容
struct inode {
umode_t i_mode;//文件的权限
unsigned short i_opflags;
kuid_t i_uid;//文件的用户ID
kgid_t i_gid;//组ID
unsigned int i_flags;
dev_t i_rdev;//设备号
union {
struct block_device *i_bdev;//块设备
struct cdev *i_cdev;//字符设备
char *i_link;
unsigned i_dir_seq;
};
open函数的第一个参数是文件路径,可以进而找到inode对象,从而回调到驱动的方法,但是read()、write()这些函数操作对象不是文件路径,而是文件描述符,使用如何通过文件描述符回调到驱动的操作方法了
文件描述符是一个进程里面打开文件时得到的一个非负整数,一个进程最多可以有1024个文件描述符,不同进程的文件描述符是独立的,文件描述符依赖于进程存在,要明白文件描述符在进程中的作用
5.2、struct task_struct结构体
只要一个进程存在于操作系统上,系统内核中一定会存在一个struct task_struct结构体对应保存进程的相关信息
struct task_struct {
volatile long state;//进程状态
int on_cpu;//表示进程在哪个CPU上执行
int prio;//进程优先级
pid_t pid;//进程号
struct task_struct __rcu *real_parent;//父进程
struct files_struct *files;//打开的文件相关结构体
};
struct files_struct {
struct file __rcu * fd_array[NR_OPEN_DEFAULT];//结构体指针数组
};
fd_array是一个指针数组,数组中每一个成员都指向一个struct file类型的对象,而数组的下标就是我们常说的文件描述符
struct file {
struct path f_path;//文件路径
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;//操作方法结构体
unsigned int f_flags;//open函数的第二个参数赋值给f_flags
fmode_t f_mode;//打开的文件的权限
void *private_data;//私有数据,可以实现函数件数据的传递
};
多个进程同时访问同一个驱动资源,就会出现对资源争抢的情况
单核处理器:如果支持资源抢占,就会出现竞态
多核处理器:核与核之间权限一样,本身就会出现资源抢占问题
中断和进程,会出现竞态
中断和中断:如果中断控制器支持中断嵌套,则会出现竞态,否则不会。ARM芯片使用的中断控制器是GIC,不支持中断嵌套
中断屏蔽是针对于单核处理器实现的竞态解决方案,如果进程想要访问临界资源,可以在访问资源之前先将中断屏蔽掉,当进程访问临界资源结束后在恢复中断的使能。一般屏蔽中断的时间要尽可能短,长时间屏蔽中断可能会导致用户数据的丢失甚至内核的崩溃。一般中断屏蔽仅仅留给内核开发者测试使用。
local_irq_disable()//中断屏蔽
临界资源
local_irq_enable()//取消中断屏蔽
自旋锁:一个进程想要访问临界资源,首先要获取自选锁,如果获取自旋锁成功,就访问临界资源,如果失败,进程就会进入自旋状态,自旋锁又称为盲等锁
自旋状态下进程处于运行状态,会耗费CPU的资源
自旋锁保护的临界资源尽可能的小,临界区中不能有延时、耗时、休眠等操作,也不能有copy_to_user和copy_from_user
自旋锁会出现死锁状态
自旋锁可以用于进程的上下文,也可以用于中断的上下文
自旋锁使用时会关闭抢占 ,尽量保证上锁的时间短
1.定义自旋锁
spinlock_t lock;
2.初始化自旋锁
spin_lock_init(&lock);
3.上锁(获取锁)
void spin_lock(spinlock_t *lock)
4.解锁(释放锁)
void spin_unlock(spinlock_t *lock)
信号量概述:一个进程想要访问临界资源,先要获取信号量,如果没有获取到,进程进入休眠状态
休眠状态下的进程不会消耗CPU的资源,进程状态的切换需要消耗CPU的资源
信号量的临界区可以很大,也可以很小,可以有延时,耗时,休眠等操作
信号量不会出现死锁
信号量只能用于进程的上下文切换
信号量不会关闭抢占
1.定义一个信号量
struct semaphore sema;
2.初始化信号量
void sema_init(struct semaphore *sem, int val)
参数:
sem:信号量指针
val:给信号量的初始值
3.获取信号量(上锁)
void down(struct semaphore *sem)//信号量数值-1
4.释放信号量(解锁)
void up(struct semaphore *sem);
一个进程想要访问临界资源需要先获取互斥体,如果获取不到,系统会切换到休眠状态
1.定义互斥体
struct mutex mutex;
2.初始化互斥体
mutex_init(&mutex);
3.上锁
void mutex_lock(struct mutex *lock)
4.解锁
void mutex_unlock(struct mutex *lock)