驱动学习-日常笔记day5

【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)	
功能:唤醒休眠的进程
参数:
	@等待队列头	

猜你喜欢

转载自blog.csdn.net/weixin_48430195/article/details/108671809