【1】复习
1.字符设备驱动的框架
inode结构体:只要一个文件在linux系统中存在
就会有一个inode结构体和这个文件对应。
这个inode结构体时描述这个文件的各种
属性信息,驱动 ,设备号等
file结构体:只要通过open函数打开文件就会产生
file,这个file结构体就会保存在fd_array
数组中,这个数组下标就是fd,file结构体
是用来描述打开文件时候的各种信息。
2.字符设备驱动实现
1.分配cdev的对象
struct cdev*cdev = cdev_alloc();
2.cdev结构体的初始化
cdev_init(cdev,&fops);
3.申请设备号
静态申请设备号:
register_chrdev_region();
动态申请设备号:
alloc_chrdev_region();
4.字符设备驱动的注册
cdev_add();
----------------------------
5.字符设备驱动的注销
cdev_del();
6.注销设备号
unregister_chrdev_region();
7.释放内存
kfree(cdev);
练习:
1.练习ADC字符设备驱动(15分钟)
【2】Linux内核并发和竞态的解决方法
竞态:应用层多个进程可以同时访问同一个驱动同一
个资源(临界资源),这时候就会产生竞态。
竞态产生的根本原因:
1.对于单cpu的内核支持抢占
2.多核cpu,核与核之间就会有竞态
3.进程和中断也会产生竞态
4.中断和中断间竞态?
解决竞态的方式:
1.顺序执行
2.互斥执行
竞态的解决方法(互斥执行):
1.中断屏蔽(了解)
中断屏蔽只适合用在单核cpu上,原因和进程和
进程间的抢占以及中断和进程间的竞态的产生
都是由中断来完成的。所以将中断屏蔽了,就可
解决竞态。
中断屏蔽的时间要尽可能的短,如果中断屏蔽的
时间很长,它可能导致内核的崩溃或用户数据的
丢失等。
使用的方法如下:
local_irq_disable();
//临界资源
local_irq_enable();
2.自旋锁(重点掌握)(中断中保护临界资源使用)
特性:当一个进程获取到自旋锁之后,另外一个
进程也想或者这把锁,此时它(第二个进程)处
于自旋状态(原地打转)。
自旋锁使用的注意事项:
1.自旋锁是针对多核cpu设计的
2.自旋锁会消耗cpu
3.自旋锁上锁的时间尽可能的短,临界资源
执行时间要尽可能的短。如果临界资源占用
时间很长可能会导致内核的崩溃等,在临界区
内不能使用延时,耗时,copy_to/from_user等函数。
4.自旋锁可能导致死锁 ,在同一个进程内多次
获取同一把未释放的锁。(锁死的是cpu)
5.自旋锁在上锁的时候会关闭抢占
6.自旋锁工作在中断上下文
API:
1.定义锁
spinlock_t lock;
2.初始化锁
spin_lock_init(&lock);
3.上锁
void spin_lock(spinlock_t *lock)
4.解锁
void spin_unlock(spinlock_t *lock)
3.信号量(重点掌握)(进程中保护临界资源使用)
特点:当一个进程获取到锁之后,另外一个进程也
向或者这把锁,此时后面这个进程就处于休眠模式。
注意事项:
1.信号量工作在进程上下文
2.信号量保护的临界区可以很大,甚至里面可以有
延时或者耗时操作。
3.信号量在获取不到锁的时候,不会消耗cpu资源。
API:
1.定义信号量
struct semaphore sem;
2.初始化信号量
sema_init(&sem, 1);
3.上锁
down(struct semaphore *sem);
int down_trylock(struct semaphore *sem);
功能:尝试获取锁
返回值:成功返回0 ,失败返回1
4.解锁
up(struct semaphore *sem);
4.互斥体(掌握)
API:
1.定义互斥体
struct mutex lock;
2.互斥体的初始化
mutex_init(&lock);
3.上锁
mutex_lock(struct mutex *lock)
int mutex_trylock(struct mutex *lock)
功能:尝试获取锁
返回值:成功返回1 ,失败返回0
4.解锁
mutex_unlock(struct mutex *lock)
5.原子操作(掌握)
原子操作内部其实就是一个变量,只不过这个变量
在被操作的时候它是一个整体,不能被打断。因为
它和cpu架构相关,通过内联汇编来完成值的改变。
1.定义原子操作
atomic_t v;
2.赋初值
v = ATOMIC_INIT(1);
v = ATOMIC_INIT(-1);
3.上锁
atomic_inc_and_test(&v);
//将原子变量的值加1 和0比较,如果结果为0 ,返回真,
//表示获取锁成功过了
atomic_dec_and_test(&v)
//将原子变量的值减1 和0比较,如果结果为0 ,返回真,
//表示获取锁成功过了
4.解锁
atomic_inc(&v) //加1
atomic_dec(&v) //减1
练习:
1.linux内核竞态的解决办法的实例
【3】IO模型
非阻塞:不管硬件中的数据是否准备好,应用层
的read函数都会理解返回。
open("设备文件",O_RDWR|O_NONBLOCK);
read(fd,buf,sizeof(buf));
-------------------------------------------
driver_read()
{
if(file->f_flags & O_NONBLOCK){
将数据立即拷贝到用户空间即可
copy_to_user即可
}
}
阻塞:当硬件的数据没有准备好的时候read函数不会
返回,此时这个进程就会进入休眠状态,当硬件的数据
准备好的时候会产生一个中断,在中断处理函数中唤醒
休眠的进程,休眠的进程就会从硬件中将数据读取到内核
空间,然后将这个数据在拷贝到用户空间。
open("设备文件",O_RDWR);
read(fd,buf,sizeof(buf));
-------------------------------------------
driver_read()
{
if(file->f_flags & O_NONBLOCK){
//报错返回
return -错误码;
}else{
//休眠
ret = wait_event_interruptible(wq,condition);
}
将数据拷贝到用户空间即可。
}
wait_queue_head_t wq;
//定义等待队列头
init_waitqueue_head(&wq);
//初始化等待队列头
wait_event(wq, condition) //不可中断的等待态
wait_event_interruptible(wq, condition) //可中断的等待态
功能:让当前的进程休眠
参数:
@wq :等待队列头
(休眠的进程在内核中会被放到一个
队列中,这里的wq就是队列头)
@condition:条件为假,要休眠
为真,不需要休眠(数据准备好了)
返回值:成功返回0,失败返回错误码
wake_up(&wq)
wake_up_interruptible(&wq)
功能:唤醒休眠的进程
参数:
@等待队列头