驱动


机制而非策略

一、Linux设备驱动概述
1.设备驱动分类
1.字符设备(open,read,write,close,seek,ioctl,fcntl,mmap,select,aio_read),比如adc,uart,rtc,
2.块设备(open,...,mount,umount)比如 nandflash(uboot擦除必须以块为单位)不可能一个一个字节的删除,存储设备
3.网络接口设备(ifconfig,socket,bind,listen...)
2.设备驱动作用
应用层
C程序..
内核
VFS
Driver
硬件
3.设备文件
/dev
4.设备号
major,minor
5.地址空间
MMU--->虚拟内存
内核空间(1G)所有进程共享
用户空间(3G)每个进程独立(0-3G)

二、内核模块
1.模块编写
static int __init hello_init(void)
{
//...
return 0;
}
static void __exit hello_exit(void)
{
//...
}
module_init(hello_init);
module_exit(hello_exit);
2.操作相关指令
insmod
rmmod(后边加不加ko.都行,正常情况下,不加 ***注意此处的rmmod 和insmod 用的是**_driver,而不是什么设备名)
lsmod
modinfo
depmod
modprobe
3.参数
支持的类型:byte short ushort int uint long ulong charp bool invbool + 数组
module_param(name,type,perm);
4.导出符号
EXPORT_SYMBOL();
EXPORT_SYMBOL_GPL();
5.说明 modinfo
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
MODULE_DESCRIPTION("");
MODULE_VERSION("");
MODULE_SUPPORTED_DEVICE("");
MODULE_ALIAS("");
6.相关几个文件
/proc/modules ==> lsmod
用命令cat /proc/modules | grep led_driver
结果 led_driver 2284 0 - Live 0xf813d000
用命令 lsmod | grep led
结果 led_driver 2284 0
/proc/kallsyms ==> 内核导出符号表
/sys/module ==> 内核模块(我们添加以后会放到这个模块下)
用命令 ls -l /dev/ | grep led
结果 crw-rw---- 1 root root 250, 0 2014-09-13 09:48 myleds
如果调用register_chrdev_region这个函数,在cat /proc/devices 下就有这个设备
lsmod | head
三、搭建环境
1.板子跑起来
uboot + kernel + filesystem(rootfs.jffs2/rootfs_qt.jffs2)
2.准备和板子匹配的Linux内核源码树
linux-2.6.32.2
3.搭建tftp和nfs服务器
1.安装tftp和nfs
1.sudo apt-get install tftp
2.sudo apt-get install nfs //这里是有网的时候用的。
//====================================================nfs
1.启动服务:
service nfs-kernel-server restart
2.设置共享目录
vi /etc/exports
/home/.../ *(rw,sync,no_root_squash)
3.本机测试:
mount -t nfs -o nolock -o tcp 127.0.0.1:/home/... /mnt
ls /mnt
umount /mnt //卸载挂载
用完后直接卸载,要不你的WINDOS与虚拟机的共享桌面不能用,即使卸载也不影响你的远端测试。
4.远端测试(必须执行上面的卸载,才能执行下面的步骤)
mount -t nfs -o nolock -o tcp serverIp:/home/... /mnt
ls /mnt
umount /mnt
//===================================================tftp
tftp -gr hello.ko serverIp
静态IP
vim /etc/network/interfaces
加入4行 auto eth5
auto lo
inface lo inet loopback
auto eth5
iface eth5 inet static
address 192.168.12.95
netmask 255.255.255.0
重启 sudo /etc/init.d/networking restart
板子上静态ip设置。
vi /etc/net.conf
IPADDR=192.168.7.116
NETMASK=255.255.255.0
GATEWAY=192.168.7.115
MAC=10:23:45:67:89:ab
注意不同的文件系统可以对应的修改的地方不同。


错误分析
问题1 mount.nfs: DNS resolution failed for sh1slcs001: Name or service not known
答: IP问题
问题2 有人说mount:RPC:Remote system error - Connection refused是因为server的portmap没开,但是我确实开了,iptables都已经被我禁掉了

四、在开发板上运行hello.ko

字符设备驱动
一、设备编号
1.设备号的内部表示
dev_t
MKDEV(int major,int minor);
MAJOR(dev_t devno);
MINOR(dev_t devno);
2.设备号的分配和释放
静态分配:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
动态分配:
int alloc_chrdev_region(dev_t *first,int firstminor,unsigned int count, char *name);
释放:
unregister_chrdev_region(dev_t devno,unsigned int count);
二、重要数据结构
struct file_operations
{
struct module *owner; //THIS_MODULE
int (*open)(struct inode *,struct file *);
int (*release)(struct inode *,struct file *);
ssize_t (*read)(struct file *,char __user *buf,size_t,loff_t *);
ssize_t (*write)(struct file *,const char __user *buf,size_t,loff_t *);
loff_t (*llseek)(struct file *,loff_t,int);
int (*ioctl)(struct inode *,struct file *,unsigned int,unsigned long);
...
};
struct file
{
unsigned int f_flags; /*文件的打开标志,O_RDONLY,O_NONBLOCK*/
mode_t f_mode; /*文件的读写模式 FMODE_READ,FMODE_WRITE*/
atomic_t f_count; /*文件的引用计数*/
loff_t f_pos; /*当前读写位置*/
void *private_data; /*私有数据*/
//私有数据存放自己的数据
struct file_operations *f_op; /*文件的操作函数*/
...
};
//每一次的open都会创建一个struct file的结构体。
struct inode
{
//...文件的属性字段 i_mode,i_uid,i_gid,...
dev_t i_rdev; /*如果是设备文件,代表设备号*/
struct cdev *i_cdev; /*指向当前设备cdev的指针*/
};

三、字符设备
1.Linux内核使用struct cdev代表一个字符设备
struct cdev
{
struct module *owner;
struct file_operations *ops;
...
};
2.将cdev通知内核(将cdev与设备号关联,并且添加到内核驱动列表)
int cdev_add(struct cdev *, dev_t, unsigned);
3.将cdev从内核删除
void cdev_del(struct cdev *);


与硬件通讯
一、IO端口与IO内存
1.X86
独立编址 IO端口
2.ARM、Mips、PowerPC
统一编址 IO内存
二、IO内存的操作
1.申请
request_mem_region();
2.映射
ioremap();
3.使用
ioread8(); ioread8_rep();
ioread16(); ioread16_rep();
ioread32(); ioread32_rep();
iowrite8(); iowrite8_rep();
iowrite16(); iowrite16_rep();
iowrite32(); iowrite32_rep();
4.解除映射
iounmap();
5.释放
release_mem_region();

任务1:修改led_driver.c
任务2:beep_driver.c
任务3:serial0_driver.c

内核中的并发与竟态 && 阻塞

一、中断屏蔽 ( 进程器调度的前提是中端,定时器中端)
local_irq_disable(); //禁用所有中断
local_irq_enable(); //使能所有中断

local_irq_save(flags); //禁用所有中断并且保存中断状态
local_irq_restore(flags); //使能中断并且恢复状态为flags

local_bh_disable(); //禁用中断底半部
local_bh_enable(); //使能中断底半部

二、原子操作(不可打断的操作)
1.整型原子操作
1.初始化
atomic_t v = ATOMIC_INIT(0);
2.读写
atomic_set(atomic_t *v,int val);
int atomic_read(atomic_t *v);
3.操作
void atomic_add(int val,atomic_t *v); //v += val
void atomic_sub(int val,atomic_t *v); //v -= val
void atomic_inc(atomic_t *v); //v++
void atomic_dec(atomic_t *v); //v--

int atomic_inc_and_test(atomic_t *v); //if(++v==0)
int atomic_dec_and_test(atomic_t *v); //if(--v==0)
int atomic_sub_and_test(int val,atomic_t *v); //if((v-=val)==0)

int atomic_add_return(int val,atomic_t *v); //v += val
int atomic_sub_return(int val,atomic_t *v); //v -= val
int atomic_inc_return(atomic_t *v); //v++
int atomic_dec_return(atomic_t *v); //v--
2.位原子操作
void set_bit(nr,void *addr); //*addr |= 1<<nr
void clear_bit(nr,void *addr); //*addr *= ~(1<<nr)
void change_bit(nr,void *addr); //*addr ^= 1<<nr
test_bit(nr,void *addr); //if((*addr >> nr) & 0x1)

int test_and_set_bit(nr,void *addr); //先执行test,后执行set_bit
int test_and_clear_bit(nr,void *addr);
int test_and_change_bit(nr,void *addr);

三、自旋锁机制
1.定义自旋锁
spinlock_t spinlock;
2.初始化
spin_lock_init(&spinlock);
3.加锁(获取锁)
spin_lock(&lock);
spin_trylock(&lock);
4.解锁(释放锁)
spin_unlock(&lock);
========================
spin_lock_irq() = spin_lock() + local_irq_disable();
spin_unlock_irq() = spin_unlock() + local_irq_enable();
spin_lock_irqsave() = spin_lock + local_irq_save();
spin_unlock_irqrestore() = spin_unlock + local_irq_restore();
spin_lock_bh() = spin_lock() + local_bh_disable();
spin_unlock_bh() = spin_unlock() + local_bh_enable();

5.何为自旋锁
1.自旋锁的核心是一个原子变量(内部仅读循环)
2.自旋锁加锁过程 相当于 “测试并设置”(test-and-set,原子操作)该原子变量
空闲:则可以获取锁
否则:程序将在一个小的循环内重复该“测试并设置”操作
6.自旋锁使用注意事项:
1.自选锁实际上是一个忙等待,适用于“占用锁时间极短的情况”
2.自旋锁可能导致死锁
3.自旋锁锁定期间,不允许出现可能引起进程调度的行为,否则可能会导致死锁:
1.中断
2.一些可能导致阻塞/睡眠的函数:copy_form_user,copy_to_user,kmalloc,sleep系列函数

读写自旋锁:
允许同时加多个读锁(读写互斥,写写互斥,读读允许)
1.定义读写锁
rwlock_t my_rwlock;
2.初始化读写锁
rwlock_init(&my_rwlock);
3.加锁
void read_lock(rwlock_t *rwlock);
void read_lock_irq(rwlock_t *rwlock);
void read_lock_irqsave(rwlock_t *rwlock,int flags);
void read_lock_bh(rwlock_t *lock);

void write_lock(rwlock_t *rwlock);
void write_lock_irq(rwlock_t *rwlock);
void write_lock_irqsave(rwlock_t *rwlock,int flags);
void write_lock_bh(rwlock_t *lock);
4.解锁
void read_unlock(rwlock_t *rwlock);
void read_unlock_irq(rwlock_t *rwlock);
void read_unlock_irqsave(rwlock_t *rwlock,int flags);
void read_unlock_bh(rwlock_t *lock);

void write_unlock(rwlock_t *rwlock);
void write_unlock_irq(rwlock_t *rwlock);
void write_unlock_irqsave(rwlock_t *rwlock,int flags);
void write_unlock_bh(rwlock_t *lock);
顺序自旋锁:
写写仍互斥,读操作时,写操作完成了,必须重新写。



四、信号量
1.定义信号量:
struct semaphore sema;

2.信号量初始化:
void sema_init(struct semaphore *sema,int val);
init_MUTEX(struct semaphore *sema); //初始化为1
init_MUTEX_LOCKED(struct semaphore *sema); //初始化为0
3.P函数 +1
void down(struct semaphore *sema);
int down_interruptible(struct semaphore *sema);
///// int down_interruptible(struct semaphore *sem)
这个函数的功能就是获得信号量,如果得不到信号量就睡眠,此时没有信号打断,那么进入睡眠。但是在睡眠过程中可能被信号打断,打断之后返回-EINTR,主要用来进程间的互斥同步。
int down_trylock(struct semaphore *sema);
4.V函数 -1
void up(struct semaphore *sema);
信号量值不能为负
4.1 信号量实现互斥
init_MUTEX(sema);
ssize_t xxx_read(...)
{
down(&sema);
//...
up(&sema);
}
ssize_t xxx_write(...)
{
down(&sema);
//...
up(&sema);
}
4.2 信号量实现同步
init_MUTEX_LOCKED(sema);

进程A:
dosth1();
down(&sema);
dosth2();
进程B:
dosth3();
up(&sema);

五、完成量(Completions)
5.1 完成量实现同步
1.定义完成量:
struct competion comp;
2.初始化
init_completion(&comp);
3.等待完成
wait_for_completion(&comp);
4.通知完成
complete(&comp);
complete_all(&comp);

进程A:
dosth1();
wait_for_completion(&comp);
dosth2();
进程B:
dosth3();
complete(&comp);/complete_all(&comp);


05 进程睡眠
一、基本API
1.定义一个等待队列头:
wait_queue_head_t myqueue;
2.初始化等待队列头:
init_waitqueue_head(&myqueue);
DECLARE_WAIT_QUEUE_HEAD(name);
3.睡眠
wait_event(myqueue, condition)
wait_event_interruptible(myqueue, condition)//响应任何信号,但是等待需要一个条件,这个条件
wait_event_timeout(myqueue, condition, timeout)
wait_event_interruptible_timeout(myqueue, condition, timeout)
4.唤醒
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);

二、相关函数
1.定义等待队列
wait_queue_t queue;
init_wait(&my_wait);
DEFINE_WAIT(my_wait);
2.添加等待队列到等到队列头
__add_wait_queue(q, wait);
3.设置进程状态
void set_current_state(int new_state);
TASK_RUNNING 运行态/就绪态
TASK_INTERRUPTIBLE 可打断休眠(信号)
TASK_UNINTERRUPTIBLE 不可打断休眠
current->state = TASK_INTERRUPTIBLE;
4.让出CPU
schedule();

prepare_to_wait()//完成上面2,3步操作
finish_wait() //取消上面2,3步操作
1.设置进程状态为TASK_RUNNING
set_current_state(TASK_RUNNING);
2.将等待队列从等待队列头移除
remove_wait_queue(q,wait);




Linux 五种IO模型
一、seek与ioctl的实现
1.seek
2.ioctl
1.命令的组成:
type 魔数,每个设备有唯一的魔数 8bit
number 命令的序号
direction 命令的方向
size 传递参数的字节数
2.如何定义命令
_IO(type,nr)
_IOR(type, nr, datatype)
_IOW(type, nr, datatype)
_IOWR(type,nr,datatype)
3.如何解析命令
_IOC_DIR(cmd)
_IOC_TYPE(cmd)
_IOC_NR(cmd)
_IOC_SIZE(cmd)

二、五种IO模型概述
1.阻塞IO
2.非阻塞IO(O_NONBLOCK)
3.IO多路复用(select,O_NONBLOCK)
4.信号驱动IO(SIGIO)
5.异步IO(aio_read,aio_write,...)
三、五种IO模型在驱动中的实现
1.阻塞IO的实现
1.当条件不满足就睡眠
(wait_queue_head_t,wait_event_interruptible,wake_up_interruptible)
2.非阻塞IO的实现
1.当条件不满足
1.判断filp->f_flags 是否有O_NONBLOCK标志,如果有则返回-EAGAIN
3.IO多路复用的实现
unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll函数实现两步骤操作:
1.将当前设备中所有可能引起阻塞的 等待队列头(wait_queue_head_t)添加到poll_table
void poll_wait (struct file *, wait_queue_head_t *, poll_table *);
2.返回一个表示当前是否可读写的状态掩码
POLLIN
如果设备可被不阻塞地读, 这个位必须设置.
POLLRDNORM
这个位必须设置, 如果"正常"数据可用来读. 一个可读的设备返回( POLLIN|POLLRDNORM ).
POLLRDBAND
这个位指示带外数据可用来从设备中读取. 当前只用在 Linux 内核的一个地方( DECnet 代码 )并且通常对设备驱动不可用.
POLLPRI
高优先级数据(带外)可不阻塞地读取. 这个位使 select 报告在文件上遇到一个异常情况, 因为 selct 报告带外数据作为一个异常情况.
POLLHUP
当读这个设备的进程见到文件尾, 驱动必须设置 POLLUP(hang-up). 一个调用 select 的进程被告知设备是可读的, 如同 selcet 功能所规定的.
POLLERR
一个错误情况已在设备上发生. 当调用 poll, 设备被报告位可读可写, 因为读写都返回一个错误码而不阻塞.
POLLOUT
这个位在返回值中设置, 如果设备可被写入而不阻塞.
POLLWRNORM
这个位和 POLLOUT 有相同的含义, 并且有时它确实是相同的数. 一个可写的设备返回( POLLOUT|POLLWRNORM).
POLLWRBAND
如同 POLLRDBAND , 这个位意思是带有零优先级的数据可写入设备. 只有 poll 的数据报实现使用这个位, 因为一个数据报看传送带外数据.
4.信号驱动IO的实现

5.异步IO

==============================================
07 Linux内核中断
arm的中断分为5部,但是内核的中断分为2步,在操作系统下的编程。
一、申请中断
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev);
irq:中断编号 mach/irqs.h
IRQ_EINT0
IRQ_TIMER0
handler: 中断处理函数
typedef irqreturn_t (*irq_handler_t)(int, void *);
irqreturn_t的取值:
IRQ_NONE //没有处理
IRQ_HANDLED //已经处理
IRQ_WAKE_THREAD //线程唤醒处理方式
flags: 中断触发方式 && 处理方式
IRQF_TRIGGER_NONE
IRQF_TRIGGER_RISING
IRQF_TRIGGER_FALLING
IRQF_TRIGGER_HIGH
IRQF_TRIGGER_LOW
------------------
IRQF_DISABLED 快速中断,中断处理期间,屏蔽其他所有中断
IRQF_SHARED 中断共享
一旦进入中断处理函数,flag就废了。
name: 设备名 cat /proc/interrupts
dev: 中断共享时使用,会作为参数被传递给中断处理函数

二、释放中断
void free_irq(unsigned int, void *);

三、中断使能/禁用
void disable_irq(int irqno); //禁用中断,等待已经发生的中断处理完毕
void disable_irq_nosync(int irqno); //禁用中断,立刻返回
void enable_irq(int irqno); //使能中断

void local_irq_disable(); //禁用所有中断
#define local_irq_save(flags); //禁用所有中断,并且保存中断状态到flags
#define local_irq_restore(flags); //使能所有中断,并且状态设置为flags
void local_irq_enable(); //使能所有中断

四、中断顶半部和底半部
1.进程上下文 && 中断上下文
进程上下文允许调度
中断上下文不允许调度
2.中断处理程序处于中断上下文,不允许调度
1.为了追求更高的系统吞吐率(及时响应能力),要求中断处理程序必须尽可能短
2.通常情况下,中断要进行较大量的耗时处理
3.基于1,2的矛盾,产生了中断处理架构:顶半部 + 底半部
3.底半部机制:
1.tasklet
void mytasklet_func(unsigned long);
DECLARE_TASKLET(name, mytasklet_func, data)
tasklet_schedule(name);

注意:tasklet机制仍然处于软中断上下文,而不是进程上下文,所有tasklet中仍然不能睡眠
2.workqueue
struct work_struct my_wq;
void my_wq_func(unsigned long );
INIT_WORK(&my_wq,my_wq_func,_data)
schedule_work(&my_wq);

注意:workqueue处于进程上下文,允许睡眠

五、中断共享
1.如何设置中断共享
IRQF_SHARED
2.中断共享的意义
允许多个设备注册同一个中断号/中断线
3.中断共享的问题
多个设备给同一个中断号注册了多个中断处理函数,多个中断处理函数以链表的形式存在,
当该中断产生时,会依次调用链表中的中断处理函数,直到某个函数返回 IRQ_HANDLED

4.如何正确调用设备的中断处理函数
typedef irqreturn_t (*irq_handler_t)(int, void *dev_id);
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev_id);

在没有中断共享的情况下:
dev_id ---> NULL
如果设置了中断共享,dev_id通常用于区分每个设备
dev_id ---> 当前设备结构指针

原文地址:https://www.cnblogs.com/coding4/p/5894637.html