论文网站开发,淘宝购物,镇江个人网站建设,如何查一个公司的营业执照一、数据传输 1.1 APP和驱动
APP和驱动之间的数据访问是不能通过直接访问对方的内存地址来操作的#xff0c;这里涉及Linux系统中的MMU#xff08;内存管理单元#xff09;。在驱动程序中通过这两个函数来获得APP和传给APP数据#xff1a;
copy_to_usercopy_from_user
…一、数据传输 1.1 APP和驱动
APP和驱动之间的数据访问是不能通过直接访问对方的内存地址来操作的这里涉及Linux系统中的MMU内存管理单元。在驱动程序中通过这两个函数来获得APP和传给APP数据
copy_to_usercopy_from_user
简单来讲应用程序与内核/驱动程序在物理空间上是隔离开的应用程序和驱动程序是不可能互相访问到的。驱动程序里的copy_from_user得到应用层传来的数据驱动程序可以使用copy_to_user把数据发给应用程序即应用程序和驱动程序通过这两个函数交换数据。
1.2 驱动和硬件
各个子系统函数通过ioremap映射寄存器地址后直接访问寄存器
驱动程序操作硬件可以通过子系统的方式调用函数来操作硬件或者用最原始的办法ioremap映射寄存器的地址不是直接操作寄存器地址这样在驱动程序里就可以访问寄存器了。
二、APP使用驱动的4种方式
驱动程序提供能力不提供策略(驱动程序提供各种作用的函数供应用程序抉择并使用)。
2.1 非阻塞查询
如果在应用程序里open这个argv[1]设备节点时指定了非阻塞表示读数据时如果没有数据并且这个文件的flag是非阻塞则立刻返回一个错误。APP指定了非阻塞方式驱动程序是否判断它的flag完全由用户决定。
//应用程序
//O_RDWR可读可写O_NONBLOCK非阻塞方式
fd open(argv[1], O_RDWR | O_NONBLOCK);//驱动程序的read
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;int key;if (is_key_buf_empty() (file-f_flags O_NONBLOCK))return -EAGAIN;wait_event_interruptible(gpio_wait, !is_key_buf_empty());key get_key();err copy_to_user(buf, key, 4); return 4;
}
2.2 阻塞休眠唤醒
如果一开始buf里没有数据APP调用读函数驱动程序读函数会进入wait_event_interruptible里休眠放弃运行不是死等等待被唤醒。所以我们经常看到read函数很久没有返回是因为在驱动程序里休眠了。该事件会记录在gpio_wait队列中。
//应用程序
//O_RDWR可读可写不设置非阻塞
fd open(argv[1], O_RDWR);//驱动程序的read
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;int key;if (is_key_buf_empty() (file-f_flags O_NONBLOCK))return -EAGAIN;wait_event_interruptible(gpio_wait, !is_key_buf_empty());key get_key();err copy_to_user(buf, key, 4); return 4;
}
通常配合中断定时器的方式来唤醒该队列里面等待唤醒的进程/线程。
//中断函数
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_desc *gpio_desc dev_id;printk(gpio_key_isr key %d irq happened\n, gpio_desc-gpio);//定时器 用来消除抖动//修改定时器的超时时间 jiffies(当前时间) 赫兹/5mod_timer(gpio_desc-key_timer, jiffies HZ/5);return IRQ_HANDLED;//成功处理
}//定时器超时函数
static void key_timer_expire(unsigned long data)
{struct gpio_desc *gpio_desc (struct gpio_desc *)data;int val;int key;val gpio_get_value(gpio_desc-gpio);key (gpio_desc-key) | (val8);put_key(key);//按键值放入环形缓冲区//唤醒队列中的进程/线程wake_up_interruptible(gpio_wait);kill_fasync(button_fasync, SIGIO, POLL_IN);
}
2.3 POLL休眠唤醒超时时间
2.3.1 POLL机制流程
使用休眠-唤醒的方式等待某个事件发生时有一个缺点 等待的时间可能很久。我们可以加上一个超时时间这时就可以使用 poll 机制。poll机制流程如下6步
①APP不知道驱动程序中是否有数据可以先调用poll函数查询一下poll函数可以传入超时时间
②APP进入内核态调用到驱动程序的poll函数如果有数据的话立刻返回
③如果发现没有数据时就休眠一段时间
④当有数据时比如当按下按键时驱动程序的中断服务程序和定时器超时函数被调用它会记录数据、唤醒APP
⑤当超时时间到了之后内核也会唤醒APP
⑥APP根据poll函数的返回值就可以知道是否有数据如果有数据就调用read得到数据。
2.3.2 POLL执行流程 图1 poll机制
函数执行流程如上图①⑧所示重点从③开始看。假设一开始无按键数据
③APP调用poll之后进入内核态
④在循环中执行程序致驱动程序的drv_poll被调用注意drv_poll要把自己这个线程挂入等待队列 wq 中并没有休眠且无数据返回0有数据返回POLLIN
⑤当前没有数据则在内核态中休眠一会等待超时内核唤醒或中断定时器唤醒
中断定时器唤醒情况
⑥过程中按下了按键发生了中断定时器超时函数在定时器超时函数里记录了按键值并且从gpio_wait队列中把线程唤醒了
⑦从休眠中被唤醒继续执行 for 循环再次调用 drv_poll在drv_poll中返回数据状态POLLIN
⑧有数据返回到内核态内核态返回到应用态
⑨APP调用read函数读数据。
超时内核唤醒情况接着上面的⑤
⑥在休眠过程中一直没有按下了按键超时时间到内核把这个线程唤醒
⑦线程从休眠中被唤醒继续执行 for 循环再次调用 drv_polldrv_poll返回数据状态
⑧还是没有数据但是超时时间到了那从内核态返回到应用态
⑨APP不能调用 read 函数读数据。
需要注意一下几点 drv_poll 要把线程挂入队列gpio_wait但是并不是在 drv_poll 中进入休眠而是在调用 drv_poll 之后休眠drv_poll 要返回数据状态APP 调用一次 poll有可能会导致 drv_poll 被调用 2 次线程被唤醒的原因有 2个中断定时器发生了去队列gpio_wait中把它唤醒超时时间到了内核把它唤醒APP 要判断 poll 返回的原因有数据还是超时。有数据时再去调用read函数 2.3.3 POLL应用和驱动编程
驱动程序中的poll代码
static unsigned int gpio_drv_poll(struct file *fp, poll_table * wait)
{poll_wait(fp, gpio_wait, wait);return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
} 驱动程序中的中断触发函数按键消抖修改了定时器超时时间
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_desc *gpio_desc dev_id;printk(gpio_key_isr key %d irq happened\n, gpio_desc-gpio);//定时器 用来消除抖动mod_timer(gpio_desc-key_timer, jiffies HZ/5);//修改定时器的超时时间 jiffies(当前时间) 赫兹/5return IRQ_HANDLED;//成功处理
} 定时器超时函数获取按键值储存按键值唤醒线程
static void key_timer_expire(unsigned long data)
{struct gpio_desc *gpio_desc (struct gpio_desc *)data;int val;int key;val gpio_get_value(gpio_desc-gpio);key (gpio_desc-key) | (val8);put_key(key);//按键值放入环形缓冲区wake_up_interruptible(gpio_wait);//唤醒队列里的线程kill_fasync(button_fasync, SIGIO, POLL_IN);
}应用程序代码
struct pollfd fds[1]
int timeout_ms 5000;
int ret;
int fd;
fds[0].fd fd; //查询fd这个文件
fds[0].events POLLIN; //POLLIN表示查询这个文件有没有数据让我读进来 fd open(argv[1], O_RDWR);
if(fd -1)
{printf(can not open file %s\n, argv[1]);
}while(1)
{ret poll(fds, 1, timeout_ms);//ret为1表示fds结构体中有文件满足返回条件且返回的事件是这个文件有数据让我读进来POLLINif((ret 1) (fds[0].revents POLLIN)){read(fd, val, 4);printf(get button : 0x%x\n, val);}else{printf(timeout\n);}} 2.4 异步通知
2.4.1 异步通知流程
使用休眠-唤醒、POLL机制时都需要休眠等待某个事件发生时它们的差别在于后者可以指定休眠的时长。如果APP不想休眠怎么办也有类似的方法驱动程序有数据时主动通知APPAPP收到信号后执行信息处理函数这就是异步通知。 图2 异步通知的信号流程
重点从②开始 ② APP 给 SIGIO 这个信号注册信号处理函数 func以后 APP 收到 SIGIO信号时这个函数会被自动调用 ③ 把 APP 的 PID(进程 ID)告诉驱动程序这个调用不涉及驱动程序在内核的文件系统层次记录 PID ④ 读取驱动程序文件 Flag ⑤ 设置 Flag 里面的 FASYNC 位为 1当 FASYNC 位发生变化时会导致驱动程序的 fasync 被调用 ⑥⑦ 调 用 faync_helper 它会根据FAYSNC的值决定是否设置button_async-fa_file驱动文件 filp驱动文件 filp 结构体里面含有之前设置的 PID。 ⑧ APP 可以做其他事 ⑨⑩ 按下按键发生中断驱动程序的中断服务程序被调用里面调用kill_fasync 发信号 ⑪⑫⑬ APP 收到信号后它的信号处理函数被自动调用可以在里面调用read 函数读取按键。
2.4.1 异步通知应用和驱动编程
应用程序信号处理函数注册信号处理函数打开驱动把进程ID告诉驱动使能驱动的FASYNC功能
static void sig_func(int sig)
{int val;read(fd, val, 4);printf(get button : 0x%x\n, val);
}signal(SIGIO, sig_func);fd open(argv[1], O_RDWR);
if(fd -1)
{printf(can not open file %s\n, argv[1]);
}fcntl(fd, F_SETOWN, getpid()); //告诉驱动程序要给谁发信号
flags fcntl(fd, F_GETFL); //获得之前的flags
fcntl(fd, F_SETFL, flags | FASYNC); //这是新的flags并使能驱动的FASYNC功能使能异步通知
驱动程序中的fasync被调用使能异步通知后会调用这个辅助函数来构造结构体结构体里存放进程id
//构造button_fasync结构体结构体里存放进程id
static int gpio_drv_fasync(int fd, struct file *file, int on)
{if (fasync_helper(fd, file, on, button_fasync) 0)return 0;elsereturn -EIO;
} 驱动程序中的中断触发函数按键消抖修改了定时器超时时间
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_desc *gpio_desc dev_id;printk(gpio_key_isr key %d irq happened\n, gpio_desc-gpio);//定时器 用来消除抖动mod_timer(gpio_desc-key_timer, jiffies HZ/5);//修改定时器的超时时间 jiffies(当前时间) 赫兹/5return IRQ_HANDLED;//成功处理
} 定时器超时函数最后一行发送信号SIGIO给进程button_fasync结构体中有进程信息发送信号后应用程序中收到信号会打断while循环并先执行对应的信号处理函数再回到while循环。
static void key_timer_expire(unsigned long data)
{struct gpio_desc *gpio_desc (struct gpio_desc *)data;int val;int key;val gpio_get_value(gpio_desc-gpio);key (gpio_desc-key) | (val8);put_key(key);//按键值放入环形缓冲区wake_up_interruptible(gpio_wait);//唤醒队列里的线程kill_fasync(button_fasync, SIGIO, POLL_IN);
}