字符驱动程序 (国嵌笔记)

linux驱动程序概述
1.学习方法
驱动程序设计模式40%+内核相关知识30%+硬件相关知识30%
2.早期设计模式
1.每一个应用程序都要写一遍驱动程序。复用性差
2.将驱动程序提取出来,多个应用程序调用一个驱动程序。前期设计问题导致的驱动程序改变,全部应用程序都需要变动
3.分类
字符设备驱动、网络设备驱动、块设备驱动
字符设备:以字节为最小单位来访问的设备
块设备:访问数据是以块(512或更大的2次幂的数)为单位来访问设备。而LINUX允许块设备传送任意数目的字节,因此LINUX块和字符的区别仅仅是驱动与内核的接口不同。还有一点是访问顺序不同,一个可以随机访问一个只能按顺序访问.字符按顺序,块设备可以不按顺序。
网络接口:负责收发数据报文
4.驱动程序安装
1.insmod rmmod
2.直接编译进内核,通过修改Kconfig和Makefile
具体操作过程:
1.Kconfig中添加 config HELLO_WORLD
      bool "helloworld"
2.Makefile中添加 obj-$(CONFIG_HELLO_WORLD) +=hello_world.o
3.重新编译下载启动
为什么会加载相应的模块执行呢?因为使用__init把函数放到初始化代码段中,内核启动时会依次调用内核代码段中的函数。
5.用户如何使用驱动程序
字符设备:应用设备->设备文件->字符设备驱动
块设备:应用程序->文件系统->块设备文件->块设备驱动
网络设备:应用程序->套接字->协议栈->网络设备驱动
设备文件在/dev目录下
字符驱动程序
1.设备号、创建设备文件、设备注册、重要数据结构、设备操作
1.设备号

1.主设备号用来标识与设备文件相连的驱动程序。次设备号被驱动程序用来辨别操作的那个设备。
主设备号:用来反映设备类型
次设备号:用来区分同类型的设备
比如说:两个网卡,多个串口等
2.设备号dev_t在内核中实质为一个32位(unsigned int 32)的整数,其中高12位为主设备号,低20位为次设备号
主设备号:MAJOR(dev_t dev)
次设备号:MINOR(dev_t dev)
3.分配设备号
1.静态申请
使用函数register_chrdev_region来注册设备号
优点:简单(Documentation/devices.txt中描述了已经使用的设备号)。缺点:设备号不容易确定,易引起冲突
int register_chrdev_region(dev_t from,unsigned count,const char* name)
功能:申请使用从from开始的count个设备号(主设备号不变,次设备号增加)
from:申请的设备号
count:申请设备数目
name:设备名
2.动态分配
使用alloc_chrdev_region()
优点:简单易于推广。缺点:无法在安装驱动前创建设备文件(因为安装前还没有分配到主设备号),可以从/proc/devices中查询设备号
int alloc_chrdev_region(dev_t *dev,unsigned baseminior,unsigned count,const char* name)
功能:请求内核动态分配count个设备号而且次设备号从baseminor开始
dev:分配到的设备号(不是用来传值的,是用来获取值的)
baseminor:起始次设备号
count:申请设备号数目
name:设备名
3.注销设备号
设备号是一种挺宝贵的资源
void unregister_chrdev_region(dev_t from,unsigned count)
功能:释放从from开始的count个设备号
2.创建设备文件
1.mknod命令手工创建
2.自动创建
3.重要的数据结构
struct file
struct inode
struct file_operations
1.struct file 代表一个打开的文件。系统中每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建,在文件关闭后释放。
每打开一次都创建一个
重要成员:
loff_t f_pos; /*文件读写位置*/
struct file_operations *f_op;
2.struct inode文件的物理信息,比如:文件的存放位置,设备号等信息。因此一个文件只对应一个inode
3.struct file_operations.是一个函数指针的集合,定义能在设备上进行的操作。结构中的成员指向驱动程序中的函数,这些函数实现一个特别的操作,对于不支持的操作保留为NULL
   struct file_operations mem_fops={
.owner=THIS_MODULE,
.read=mem_read,
}
就相当于一个对应关系表
4.设备注册
字符设备使用struct cdev来描述
可以分为三个步骤
1.分配cdev
2.初始化cdev
3.添加cdev
1.分配
struct cdev* cdev_alloc(void)
2.初始化
void cdev_init(struct cdev *cdev,const struct file_operations *fops)
cdev:待初始化的cdev结构
fops:设备对应的操作函数集
3.添加注册
int cdev_add(struct cdev* p,dev_t dev,unsigned count)
p:待添加到内核的字符设备结构
dev:设备号->对应到相应的设备
count:添加的设备个数
4.实现struct file_operations中的函数
open    如果该项为NULL,设备的打开操作永远成功
release->  close
read
write
poll->select
等等
上面的读和写 ssize_t xxx_read/xxx_write(struct file *flip,char __user *buff,size_t count,loff_t * offp)
filp:文件指针。count:请求传输的数据量。buff:指向数据缓存。offp:文件当前的访问位置
buff是用户空间指针。因此,它不能被内核代码直接引用,
用户空间指针在内核空间中可能根本是无效的,没有相应的地址映射。用户不能直接访问内核空间
使用专门的函数访问:copy_from_user和copy_to_user
  5.设备注销
int cdev_del(struct cdev *p)

dev_t devno=MKDEV(xxxx,xxx)
驱动调试技术
1.打印调试、调试器调试、查询调试
printk 要合理使用,加的不对还会降低系统性能

#if 0
#endif

#ifdef PDEBUG
#define PLOG(fmt,args···) printk(KERN_DEBUG"scull:"fmt,##args)
#else
#define PLOG(fmt,args···)
#endif

在Makefile中使用 -DPDEBUG     :-D在makefile中使用宏定义


并发与竞态


1.信号量
内核和应用中的信号量是不同的,不能混淆使用
内核中他是一种睡眠锁

1.定义信号量
struct semaphore sem

2.初始化信号量:
void sema_init(struct semaphore *sem,int val)
该函数用于设置信号量的初值,他设置信号量的sem的值为val

互斥锁的值只能是0和1
void init_MUTEX(struct semaphore *sem)
该函数初始化一个互斥锁,即把信号量sem的值设置为1
void init_MUTEX_LOCKED(struct semaphore *sem)
该函数初始化一个互斥锁,把信号量sem设置为0,即一开始就处在已锁状态

3.定义和初始化一步到位
DECLARE_MUTEX(name) //定义一个信号量name,并初始化为1
DECLARE_MUTEX_LOCKED(name) //定义一个信号量name,并把初始值设为0,即在创建时就处在已锁状态

4.获取信号量
void down(struct semaphore *sem)
获取信号量sem,可能会导致进程睡眠,因此不能在中断上下文中使用,该函数将sem的值减1,如果sem的值非负,就返回,否则挂起,直到别的任务释放该信号量才能继续运行。
void down_interruptible(struct semaphore*sem)
如果信号量不可用进程将被设置为TASK_INTERRUPYIBLE类型的睡眠状态。该函数由返回值来区分是正常返回还是被信号中断返回。正常返回0,否则返回-EINTR
down_illable(···)
获取信号量sem,如果信号量不可用,进程将被置为TASK_KILLABLE类型的睡眠状态

down函数现在已经不建议使用,建议使用down_killable或down_interruptible

5.释放信号量
void up(···)
把sem加1
2.自旋锁
最多只能被一个可执行单元持有,不会引起调用者睡眠,如果一个执行线程试图获得一个已经被持有的自旋锁,那么线程就会一直进行忙循环,一直等待下去。查看是否已经释放了锁。自旋就是这个意思
最大的特点盲等(盲目等待)
1.spin_lock_init(x)
初始化
2.spin_lock(lock)
获取自旋锁lock,成功立即获得锁,并马上返回。否则一直等待。
3.spin_trylock(lock)
获取自旋锁lock,能就获取返回真,不能返回假,不会一直盲等
4.spin_unlock(lock)
释放自旋锁,他与spin_trylock或spin_lock配对使用




信号量PK自旋锁
1.信号量可能允许有多个持有者,而自旋锁任何时候只能允许有一个持有者。当然也有互斥信号量(只允许一个持有者),允许多个持有者的信号量叫做计数信号量
2.信号量适合于保持时间较长的情况;而自旋锁适合于保持时间非常短的情况,在实际应用中自旋锁控制代码只有几行,而持有自旋锁的时间一般也不会超过两次上下文切换时间,因为线程一旦要进行切换,就至少花费切出切入两次,自旋锁的占用时间如果远远长于两次上下文切换,我们就应该选择信号量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值