linux 驱动 并发、(非)阻塞、时钟中断
来源:互联网 发布:贵金属看盘软件 编辑:程序博客网 时间:2024/05/16 01:36
并发情况下防止竞争的措施:
(1)中断屏蔽
(2)原子操作
(3)自旋锁
(4)读写自旋锁(防写不防读)
(5)顺序锁(seqlock)
(6)RCU(Read-Copy-Update)
(7)信号量(信号量其实和自旋锁是一样的,就是有一点不同:当获取不到信号量时,进程不会原地打转而是进入休眠等待状态)
(8)完成量(completion),它用于一个执行单元等待另一个执行单元执行完某事
(9)读写信号量
(10)互斥体(mutex)
经验:定义带有设备并发控制方案的结构体(诸如信号量,自旋锁等)
(1)在Linux设备驱动中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒.等待队列能够用于实现内核中的异步事件通知机制。
(2)使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问,这两个系统调用最终又会引发设备驱动中的poll()函数被执行,设备驱动中的poll()函数原型:
unsigned int (*poll)(struct file *filp, struct poll_table *wait);
void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait);常量说明POLLIN普通或优先级带数据可读POLLRDNORM普通数据可读POLLRDBAND优先级带数据可读POLLPRI高优先级数据可读POLLOUT普通数据可写POLLWRNORM普通数据可写POLLWRBAND优先级带数据可写POLLERR发生错误POLLHUP发生挂起POLLNVAL描述字不是一个打开的文件驱动程序中的异步通知:
1)设备申请中断
2)释放中断
3)使能和屏蔽中断
Linux系统中中断是分为顶半部和底半部的,低半部通过tasklet,工作队列,软中断实现。
向量中断就是入口地址不同,进不同的地址做不同的事。那非向量中断则是进同一地址,至于区分就放在了进去后用条件判断。
tasklet使用模版:
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(XXX_tasklet, xxx_do_tasklet, 0);
void xxx_do_tasklet(unsigned long) //中断处理底半部
{
.....
}
irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) //中断处理顶半部
{
...
tasklet_schedule(&xxx_tasklet);
}
int __init xxx_init(void) //设备驱动模块加载函数
{
..
result= request_irq(xxx_irq, xxx_interrupt, SA_INTERRUPT, "XXX",NULL); //申请中断
...
}
void __exit xxx_exit(void) //设备驱动卸载模块
{
..
free_irq(xxx_irq, xxx_interrupt); //释放中断
..
}
工作队列模版:
struct work_struct xxx_wq;
void xxx_do_work(unsigned long);
void xxx_do_work(unsigned long) //中断处理底半部
{
.....
}
irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) //中断处理顶半部
{
...
schedule_work(&xxx_wq);
}
int xxx_init(void) //设备驱动模块加载函数
{
..
result= request_irq(xxx_irq, xxx_interrupt, SA_INTERRUPT, "XXX",NULL); //申请中断
...
INIT_WORK(&xxx_wq, (void (*)(void *))xxx_do_work, NULL);
...
}
void __exit xxx_exit(void) //设备驱动卸载模块
{
..
free_irq(xxx_irq, xxx_interrupt); //释放中断
..
}
中断共享模版:
irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) //中断处理顶半部
{
...
int status = read_int_status(); //获取终端源
if(!is_myint(dev_id, status)) //判断是否是本设备的中断
{
return IRQ_NONE://立即返回
}
..
return IRQ_HANDLED;
}
int __init xxx_init(void) //设备驱动模块加载函数
{
..
result= request_irq(xxx_irq, xxx_interrupt, SA_SHIRQ, "XXX",xxx_dev); //申请共享中断
...
}
void __exit xxx_exit(void) //设备驱动卸载模块
{
..
free_irq(xxx_irq, xxx_interrupt); //释放中断
..
}
内核在时钟中断发生后检测各定时器是否到期,到期后的定时器处理函数将作为软中断在底半部执行。实质上,时钟中断处理程序执行update_process_timers函数,该函数调用run_local_timers函数,这个函数处理TIMER_SOFTIRQ软中断,运行当前处理上到期的所有定时器。
定时器的模版:
struct xxx_dev /*second设备结构体*/
{
struct cdev cdev; /*cdev结构体*/
...
struct timer_list xxx_timer; /*设备要使用的定时器*/
};
int xxx_func1(...) //xxx驱动中某函数
{
struct xxx_dev *dev = filp->private_data;
...
/*初始化定时器*/
init_timer(&dev->xxx_timer);
dev->xxx_timer.function = &xxx_do_handle;
dev->xxx_timer.data = (unsigned long)dev;
dev->xxx_timer.expires = jiffies + delay;
add_timer(&dev->xxx_timer); /*添加(注册)定时器*/
...
return 0;
}
int xxx_func2(...) //驱动中某函数
{
...
del_timer(&second_devp->s_timer);
...
}
static void xxx_do_timer(unsigned long arg) //定时器处理函数
{
struct xxx_device *dev = (struct xxx_device *)(arg);
...
//调度定时器再执行
dev->xxx_timer.expires = jiffies + delay;
add_timer(&dev->xxx_timer);
}
内核延迟函数:
1)短延迟
2)长延迟
3)睡着延迟
IO内存、IO端口读写,申请,映射。
http://www.cnblogs.com/hanyan225/archive/2010/10/26/1861431.html
一般情况下,用户空间是不可能也不应该直接访问设备的,但是设备驱动程序可实现mmap()函数,这个函数可使得用户空间能直接访问设备的物理地址。实际上,mmap()实现了这样的一个映射过程,它将用户空间的一段内存与设备内存关联,当用户访问用户空间的这段地址范围时,实际上会转化为对设备的访问。
mmp()必须以PAGE_SIZE为单位进行映射,实际上,内存只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行页对齐,强行以PAGE_SIZE的倍数大小进行映射。驱动中mmp()函数原型如下:int (*mmp)(struct file *, struct vm_area_struct *);它实现的机制是建立页表,并填充VMA结构体中 vm_operations_struct指针,vm_area_struct用于描述一个虚拟内存区域:
Linux内核中,关于虚存管理的最基本的管理单元应该是struct vm_area_struct了,它描述的是一段连续的、具有相同访问属性的虚存空间,该虚存空间的大小为物理内存页面的整数倍。
struct vm_area_struct { struct mm_struct * vm_mm; //所处的地址空间 unsigned long vm_start;//开始虚拟地址 unsigned long vm_end;//结束虚拟地址 struct vm_area_struct *vm_next; pgprot_t vm_page_prot; //访问权限 unsigned long vm_flags; //标志 ... struct vm_operations_struct * vm_ops; //操作VMA的函数集指针 unsigned long vm_pgoff; //偏移(页帧号) struct file * vm_file; void * vm_private_data; ...};vm_area_struct结构所描述的虚存空间以vm_start、vm_end成员表示,它们分别保存了该虚存空间的首地址和末地址后第一个字节的地址,以字节为单位,所以虚存空间范围可以用[vm_start, vm_end)表示。
假如该vm_area_struct描述的是一个文件映射的虚存空间,成员vm_file便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面。
进程建立vm_area_struct结构后,只是说明进程可以访问这个虚存空间,但有可能还没有分配相应的物理页面并建立好页面映射。在这种情况下,若是进程执行中有指令需要访问该虚存空间中的内存,便会产生一次缺页异常。这时候,就需要通过vm_area_struct结构里面的vm_ops->nopage所指向的函数来将产生缺页异常的地址对应的文件数据读取出来。
进程虚拟地址示意图
mmap系统调用(功能)
void * mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset)
参数:
addr
指定映射的起始地址(通常不指定)通常为NULL,由系统指定
length
映射到内存的文件长度
prot
映射区的保护方式:
PROT_EXEC:映射区可被执行
PROT_READ:映射区可被读取
PROTWRITE:映射区可被写入
flags
映射区的特性:
MAP_SHARED:写入映射区的内容最后要写入文件
MAP_PRIVATE:最后不会写入文件
fd
由open返回的文件描述符,代表要映射的文件
offset
以文件开始处的偏移量,必须是分布大小的整数倍,通常为0,表示从文件头开始映射
返回
会返回起始地址,本来mmap是指向内存地址的指针
内存映射函数mmap,负责把文件内容映射到进程的虚拟内存空间,通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read、write等操作。直接用指针操作文件的内容。
图中左边的是进程的虚拟空间,右边的是文件
mmap设备操作
映射一个设备是指 把用户空间的一段地址关联到设备内存上
当程序读写这段用户空间的地址时,它实际上是在访问设备。
步骤
1)找到用户空间的地址(内核自动帮你做好)
2)找到设备的物理地址(查看芯片手册)
3)关联 (通过页式管理)
mmap设备方法所需要做的就是建立虚拟地址到物理地址的页表
int (*mmap)(struct file * , struct vm_area-strcut *)
↓
内核帮我找的
mmap如何完成页表的建立?
方法有二:
1)使用remap_pfn_range一次建立所有页表
2)使用nopage VMA方法每次建立一个页表
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn,unsigned long size, pgprot_t prot)
vma
虚拟内存区域指针
virt_addr
虚拟地址的起始值
pfn
要映射的物理地址所在的物理页帧号(物理地址的序列号),可将物理地址>>PGE_SHIFT得到,即右移12位,相当于除以4k(2^12)
prot
VMA的保护属性
// 内核模块加载函数int __init kmalloc_map_init(void){ ../申请设备号,添加cedv结构体 buffer = kmalloc(BUF_SIZE, GFP_KERNEL); //申请buffer for(page = virt_to_page(buffer); page< virt_to_page(buffer+BUF_SIZE); page++) { mem_map_reserve(page); //置业为保留 }}//mmap()函数static int kmalloc_map_mmap(struct file *filp, struct vm_area_struct *vma){ unsigned long page, pos; unsigned long start = (unsigned long)vma->start; unsigned long size = (unsigned long)(vma->end - vma->start); printk(KERN_INFO, "mmaptest_mmap called\n"); if(size > BUF_SIZE) //用户要映射的区域太大 return - EINVAL; pos = (unsigned long)buffer; while(size > 0) //映射buffer中的所有页 { page = virt_to_phys((void *)pos); if(remap_page_range(start, page, PAGE_SIZE, PAGE_SHARRED)) return -EAGAIN; start += PAGE_SIZE; pos +=PAGE_SIZE; size -= PAGE_SIZE; } return 0;}
另外通常,IO内存被映射时需要是nocache的,这个时候应该对vma->vm_page_prot设置nocache标志。如下:
static int xxx_nocache_mmap(struct file *filp, struct vm_area_struct *vma){ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); //赋nocache标志 vma->vm_pgoff = ((u32)map_start >> PAGE_SHIFT); if(rempa_pfn_range(vma, vma->vm_start, vma->vm_pgoff, vma->vm_end - vm_start, vma->vm_page_prot)); return - EAGGIN; return 0;}
这段代码中的pgprot_noncached()是一个宏,它实际上禁止了相关页的cache和写缓冲(write buffer),另外一个稍微少的一些限制的宏是:
#define pgprot_writecombine(prot) __pgprot(pgprot_val (prot) & –L_PTE_CACHEABLE); 它则没有禁止写缓冲
而除了rempa_pfn_range()外,在驱动程序中实现VMA的nopage()函数通常可以为设备提供更加灵活的内存映射途径。当发生缺页时,nopage()会被内核自动调用,。这是因为,当发生缺页异常时,系统会经过如下处理过程:
1)找到缺页的虚拟地址所在的VMA 2)如果必要,分配中间页目录表和页表
3)如果页表项对应的物理页表不存在,则调用这个VMA的nopage()方法,它返回物理页面的页描述符。
4)将物理页面的地址填充到页表中。
实现nopage后,用户空间可以通过mremap()系统调用重新绑定映射区所绑定的地址,下面给出一个在设备驱动中使用nopage()的典型范例:
static int xxx_mmap(struct file *filp, struct vm_area_struct *vma);{ unsigned long offset = vma->vm_pgoff << PAGE_OFFSET; if(offset >= _ _pa(high_memory) || (filp->flags &O_SYNC)) vma->vm_flags |=VM_IO; vma->vm_ops = &xxx_nopage_vm_ops; xxx_vma_open(vma); return 0;}struct page *xxx_vma_nopage(struct vm_area_struct *vma, unsigned long address, int *type){ struct page *pageptr; unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; unsigned long physaddr = address - vma->vm_start + offset; //物理内存 unsigned long pageframe = physaddr >> PAGE_SHIFT; //页帧号 if(!pfn_valid(pageframe)) //页帧号有效 return NOPAGE_SIGBUS; pageptr = pfn_to_page(pageframe); //页帧号->页描述符 get_page(pageptr); //获得页,增加页的使用计数 if(type) *type = VM_FAULT_MINOR; return pageptr; //返回页描述符
}
上述函数对常规内存进行映射,返回一个页描述符,可用于扩大或缩小映射的内存区域,由此可见,nopage()和remap_pfn_range()一个较大的区别在于remap_pfn
_range()一般用于设备内存映射,而nopage()还可以用于RAM映射。
- linux 驱动 并发、(非)阻塞、时钟中断
- linux驱动的并发控制和阻塞和非阻塞
- linux设备驱动中的阻塞与非阻塞(一)
- linux设备驱动中的阻塞与非阻塞(二)
- linux设备驱动中的阻塞与非阻塞(一)
- linux设备驱动中的阻塞与非阻塞(二)
- Linux设备驱动中的阻塞与非阻塞IO与并发控制
- linux驱动的阻塞与非阻塞
- Linux驱动之阻塞与非阻塞
- 嵌入式linux:阻塞与非阻塞驱动
- Linux驱动开发(五)——中断和时钟
- linux设备驱动中的中断与时钟
- 【Linux驱动】阻塞型I/O(二+并发控制)
- Linux设备驱动第九天(非阻塞、内存管理)
- Linux设备驱动编程之阻塞与非阻塞(转)
- Linux驱动学习9(同步/异步与阻塞/非阻塞的区别 )
- Linux 设备驱动编程之阻塞与非阻塞
- Linux设备驱动编程之阻塞与非阻塞
- css 颜色渐变
- 常见的关系数据库
- Linux下crontab命令的用法
- seo是什么意思、网站为什么要做seo优化
- Linux GDB调试
- linux 驱动 并发、(非)阻塞、时钟中断
- hdu 2234 无题I IDA*
- HDU 2000 - ASCII码排序
- python的string的操作函数
- python 列表函数
- Apache POI库简化,仅保留hwpf部分(word文档处理,可用于android)
- Python的字典操作
- 数据库事物隔离之通俗理解
- Universal ONVIF Viewer