(一)多路复用-----poll()
应用空间:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
//参数1---一般为数组
//参数2---监控的路数
//参数3---阻塞的时间 -1
struct pollfd {
int fd; /* file descriptor */ 文件描述符 fd1
short events; /* requested events */ 你要监控的事件 POLLIN
short revents; /* returned events */ 这个事件是否发生 POLLIN
};
//利用poll监控键盘是否有数据可读,并且监控开发板上是否有按键输入
struct pollfd fds[2];
fds[0].fd =0; //标准输入的文件描述符
fds[0].events =POLLIN; //监控是否有数据输入
fds[1].fd =fd; //我们自己写的按键的文件描述符
fds[1].events =POLLIN;//监控是否有数据输入
poll();
if(fds[0].revents&POLLIN){
fgets
//从键盘获取数据
}
if(fds[1].revents&POLLIN){
//读取数据
}
内核驱动:
unsigned int drv_key_poll (struct file *, struct poll_table_struct *)
{
int mask=0;
1,将等待队列头注册到VFS中
// 将复用处理的等待队列追加内核管理的进程 poll_table上去
poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table * p)
2,如果触发了中断----有数据可读,返回POLLIN 如果没有数据返回0
if(have_data==1){
mask |=POLLIN;
}
return mask
}
unsigned int drv_key_poll(struct file *filp, struct poll_table_struct *ptbs)
{
unsigned int Mask=0;
printk("------------%s--------------\n",__FUNCTION__);
// 1,将等待队列头注册到VFS中
poll_wait(filp, &drv_key->wq,ptbs);
// 2,如果触发了中断----有数据可读,返回POLLIN 如果没有数据返回0
if(drv_key->have_data){
Mask |=POLLIN;
}
return Mask;
}
二,MMAP的实现
当在应用空间中需要使用物理内存时,就可以通过mmap实现映射物理内存
1,实现映射物理内存的好处
1》是一个文件IO接口,可以在驱动中实现
2》可以在应用空间和内存空间之间快速的传递数据,效率更高
3》使用非常方便,可以在应用空间调用mmap实现映射
应用空间:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
//参数1:指定要映射到用户空间的哪个地址,一般NULL
//参数2:要映射的空间长度 PAGE_SIZE
//参数3: 对内存的操作权限 PROT_READ PROT_WRITE
//参数4:是否允许其他的进程映射该内存 MAP_SHARED 允许 MAP_PRIVATE 建立一个私有的映射
//参数5:文件描述符
//参数6:从物理内存起始位置多少开始映射
//返回值:成功:返回映射到用户空间的地址,失败:NULL
内核空间:
// 1,申请一块4k的虚拟空间------一般写在加载函数中
返回值:申请的虚拟空间的地址=kzalloc(PAGE_SIZE,GFP_KERNEL);
int (*mmap) (struct file *filp, struct vm_area_struct *vma(映射空间的信息))
{
// 2,根据申请到的虚拟空间找到一块物理空间
static __inline__ unsigned long virt_to_phys(volatile void *address)
//参数: 虚拟空间地址
//返回值:找到的物理空间的地址
// 3,将物理空间映射给用户
int io_remap_pfn_range(struct vm_area_struct *vma, unsigned long from,
unsigned long pfn, unsigned long size, pgprot_t prot)
//参数1:表示映射空间的相关信息
//参数2:是映射的应用空间的起始地址 ----vma->vm_start
//参数3:要映射的物理内存的页地址 -----addr(物理地址) >> PAGE_SHIFT
//参数4:要映射的空间的大小 ----PAGE_SIZE (4k)
//参数5:映射空间的权限
//返回值: =0成功,不为0,失败
}
mmap编程步骤:
内核空间:-----实现映射
1,申请一块4k的虚拟空间------一般写在加载函数中
drv_key->virt=kzalloc(PAGE_SIZE,GFP_KERNEL);
if(IS_ERR(drv_key->virt)){
printk("4k virt kzalloc is error\n");
ret=PTR_ERR(drv_key->virt);
goto device_err;
}
2,实现内核的操作接口函数mmap
2.1 根据申请到的虚拟空间找到一块对应物理空间
phys_addr=virt_to_phys(drv_key->virt);
2.2 将对应的物理空间映射到用户空间
if (io_remap_pfn_range(vma, vma->vm_start,phys_addr>> PAGE_SHIFT,
PAGE_SIZE, vma->vm_page_prot)) {
printk(KERN_ERR "%s: io_remap_pfn_range failed\n",
__func__);
return -EAGAIN;
}
内核空间:----验证映射是否准确(将虚拟空间中的数据通过ioctl发送到应用层)
3,实现ioctl的函数接口
3.1 定义一个结构体,和一个结构体变量----用来存放虚拟空间的数据
struct mmap_data{
char buf[128];
};
struct mmap_data mamp_data;
3.2 定义命令这个命令_IOR来定义(用_IOR来定义决定ioctl数据传输方向是内核到应用)
#define ioctl_mmap_data _IOR('K',0x2321,struct mmap_data)
3.3 在ioctl的函数中用switch语句
switch(cmd){
case ioctl_mmap_data://命令就是3.2定义的命令
memset(mamp_data.buf,0,sizeof(mamp_data.buf)); //清除buf结构体变量
//将虚拟空间的数据复制到buf结构体变量
memcpy(mamp_data.buf,drv_key->virt,sizeof(mamp_data.buf));
//将buf中的数据转化成应用空间的数据
ret=copy_to_user(gprs,&mamp_data,sizeof(mamp_data));
if(ret!=0){
printk("ioctl_mmap copy_to_user is error\n ");
return ret;
}
应用空间:
1,定义一个指针,指向映射号的空间
char*mmap_buf
2,实现mmap内存映射
mmap_buf=mmap(NULL,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(mmap_buf==NULL){
perror("mmap");
exit(1);
}
3,在键盘上获取数据,并且放入实现了mmap内存映射的mmap_buf地址中
fgets(buf,sizeof(buf),stdin); //从键盘上获取数据
memcpy(mmap_buf,buf,sizeof(buf));//将键盘上获取的数据放入mmap_buf地址
4,延时1s
5,通过ioctl将drv_key->virt的数据读出来,读出以后打印
ioctl(fd,ioctl_mmap_data,&mamp_data.buf);
printf("app:ioctl_mmap_data=%s\n",mamp_data.buf);
三,中断下半部
实现中断下半部方式:
1》tasklet
2》工作队列
3》软中断
1>tasklet的实现
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
1,定义一个tasklet对象 ,初始化对象-------加载函数硬件初始化的位置初始化
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)
//参数1:tasklet对象的地址
//参数2:中断下半部的执行函数
//参数3:传给中断下半部的执行函数的参数
2,中断下半部的执行函数
void (*func)(unsigned long)
{
//中断下半部执行代码
}
3,启动中断下半部------//在中断处理函数中启动中断下半部
static inline void tasklet_schedule(struct tasklet_struct *t)
经验:
1>任务对时间敏感,放在上半部
2>任务和硬件有直接关系,放在上半部
3>需要保证任务不被其他任务打断,放在上半部
其他全部下半部
2》工作队列
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
1>定义下半部的结构体对象
struct work_struct work;
2>初始化工作队列-------加载函数硬件初始化的位置初始化
INIT_WORK(_work, _func)
//参数一:定义的工作队列对象
//参数二:工作队列的函数名
3>启动下半部------//在中断处理函数中启动中断下半部
schedule_work(struct work_struct * work)
中断下半部的执行函数
typedef void (*work_func_t)(struct work_struct *work);