first_drv/second_drv/third_drv.c/forth_drv.c/fifth_drv.c

本文深入探讨了驱动程序如何与操作系统交互,包括查询法、中断法、poll机制及按键读取方法,同时介绍了等待队列、信号量、阻塞与非阻塞等关键概念在驱动开发中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(1)first_drv.c
(2)second_drv.c    //使用查询法
  在第二个驱动程序中,使用查询法:
  1.内核的 key_vals[i]=gpfdat 进行读值
  2.copy_to_user(buf,key_vals,sizeof(key_vals));将key_vals赋给buf(内核层传递数据到用户层)
  3.read(fd,key_vals,sizeof(key_vals));将内核空间中的数据读出到key_vals接收。


(3)third_drv.c
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断事件标志, 中断服务程序将它置1,third_drv_read将它清0 */
static volatile int ev_press = 0;
static irqreturn_t buttons_irq(int irq, void *dev_id)
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};
ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
->wait_event_interruptible(button_waitq, ev_press);
在应用层中使用read(fd,&key_val,1),从内核中读取数值到key_val,


什么是等待队列?


在软件开发中任务经常由于某种条件没有得到满足而不得不进入睡眠状态,然后等待条件得到满足的时候再继续运行,进入运行状态。这种需求需要等待队列机制的支持。Linux中提供了等待队列的机制,该机制在内核中应用很广泛。


在Linux内核中使用等待队列的过程很简单,首先定义一个wait_queue_head,然后如果一个task想等待某种事件,那么调用wait_event(等待队列,事件)就可以了。
首先创建一个等待队列头button_waitq


static DECLARE_WAIT_QUEUE_HEAD(button_waitq);


App中调用read函数,驱动中调用s3c24xx_buttons_read函数,


static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
  unsigned long err;


  if (!ev_press) 
  {
  if (filp->f_flags & O_NONBLOCK)


  return -EAGAIN;


  else
  wait_event_interruptible(button_waitq, ev_press); // ev_press是等待条件
  }
  ev_press = 0;
  err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count));
  return err ? -EFAULT : min(sizeof(key_values), count);


}


    在s3c24xx_buttons_read中添加一个事件到等待队列button_waitq中,其中ev_press是等待条件,那么App中的read函数调用驱动中的s3c24xx_buttons_read函数,此函数会阻塞在wait_event_interruptible处等待中断。然后,如果产  生了中断,中断服务函数中的:


  key_values[button_irqs->number] = '0' + down;


  ev_press = 1;


  wake_up_interruptible(&button_waitq);


  会唤醒等待队列button_waitq中的可以被中断的任务,同时设置ev_press为1


  这样,s3c24xx_buttons_read中的阻塞函数将得以执行下去,对等待条件复位,复制键值。


pinval = s3c2410_gpio_getpin(pindesc->pin);
作用:


(4)forth_drv.c
  static unsigned forth_drv_poll(struct file *file, poll_table *wait)
  {
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); // do_poll(nfds,head,&table,timeout)
     // for(;;) {       //          if(do_pollfd(pfd,pt))
//             mask=file->f_op->poll(file,pwait);return mask
//                在此处调用驱动中的.poll = forth_drv_poll
//                     ->poll_wait();将button_waitq
//     放入等待队列
//
if (ev_press)
mask |= POLLIN | POLLRDNORM;


return mask;
  }
  static struct file_operations sencod_drv_fops = {
....
.poll    =  forth_drv_poll,
  }

POLL机制:
应用程序:poll
内核:sys_poll
          do_sys_poll(.....,struct timespec *end_time)
              poll_initwait(&table);
                   init_poll_funcptr(&pwq->pt, __pollwait);>(table->__pollwait = __pollwait)pt->qproc = __pollwait;
                do_poll(nfds, head, &table, end_time);
                    for (;;)
                     {
                           for (; pfd != pfd_end; pfd++) { 
                               if (do_pollfd(pfd, pt)) {  >mask = file->f_op->poll(file, pwait);
                                                                 //驱动poll
                                                                   mask = poll_wait(file, &button_waitq, wait)
poll_table *p ;
  p->__pollwait(filp,wait_address,p);
  __pollwait(filp,&button_waitq,p)
//把当前进程挂到button_waitq队列中
                                                                              即pt->__pollwait,上面定义的。
                                                                                                                             
                                   count++;  
  pt = NULL;
}
      //2.如果驱动的poll返回0值(按键未按下,中断未触发,ev_press为0),
      //  即poll_wait()中的return mask=0;那么count++不会执行
      //  则进入休眠状态。
      //3.如果应用程序中poll调用,进入驱动程序的poll_wait后,有中断发生ev_press
      //  不等于0时,mask |= POLLIN | POLLRDNORM; 使得mask非0,return mask;
      //  那么下面将会break,进入应用程序中返回ret非0,读取键值
                             
                              //break条件count非0,timed_out非0
                              if (count || !*timed_out || signal_pend(current))
                                      break;
                               }
                               }
 
     //假设上面条件不成立,休眠,然后继续for循环
     //假如上面条件成立,则会返回应用程序。  当休眠结束timeout就会减为0.
       __timeout = schedule_timeout(__timeout);
                      }
*************************************
总结读取按键的方法:
1.查询法:非常耗费资源
2.中断法:应用程序中while中一直read(),如果没有按键按下则在驱动中的read()中休眠,不会返回到应用程序中。
3.poll:   指定超时时间,超时则放回。
以上三种方法都是程序主动的read(),现在另一种方法:
   驱动程序中当发现有按键按下后
*************************************


(5)fifth_drv.c
驱动层:
static struct fasync_struct *button_async;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
....
kill_fasync (&button_async, SIGIO, POLL_IN);
//调用函数kill_fasync发送信号SIGIO给内核
//当按键按下,产生中断,进入buttons_irq函数,获取到键值,存放到key_val中。
  //这时候可以使用kill_fasync()函数发送信号告诉内核,POLL_IN表示设备可读。
}

static int fifth_drv_fasync (int fd, struct file *filp, int on)
{
printk("driver: fifth_drv_fasync\n");
return fasync_helper (fd, filp, on, &button_async);
->fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
}  //函数fasync_helper将fd,filp和定义的结构体传给内核。
static struct file_operations sencod_drv_fops = {
 ....
          .fasync =  fifth_drv_fasync,    
};
//当应用程序中调用fcntl(fd,F_SETFL,oflags|FASYNC);就会
//调用驱动程序中fifth_drv_fasync函数。fifth_drv_fasync(fd,filp,on)
//中打印,关键将struct button_async进行初始化,并注册到内核中。

应用层:
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd,&key_val,1);
printf("key_val:0x%x\n",key_val);
}


void main(int argc,char **argv)
{
....
signal(SIGIO,my_signal_fun); //定义为IO口信号处理函数.处理函数为my_signal_fun.
....
fcntl(fd,F_SETDOWN,getpid());   //获取
Oflags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,Oflags | FASYNC);改变fasync标记,最终调用到fifth_drv_fasync。


while(1)
{
sleep(1000);
}
}
总结:a.先通过signal()函数注册,信号发送时的处理函数。
      b.当执行fcntl(fd,F_SETFL,Oflags | FASYNC)来调用驱动层的.fcntl=fifth_drv_fasync的
fifth_drv_fasync()函数将button_async结构体注册到内核。
      c.当按键按下,执行中断函数button_irq,在其中会调用kill_fasync(&button_async,SIGIO,POLL_IN)
        发送信号,那么这将调用应用层的my_signal_fun()函数。my_signal_fun中将调用read读取按键值。


谁发?c解决了谁发信号的问题:驱动程序发,之前是利用kill -USR1 733命令发送。
发给谁?fcntl(fd,F_SETDOWN,getpid())告诉内核,解决了发给谁的问题:发给获取的pid


(6)sixth_drv.c
驱动层:
static atomic
static int sixth_drv_open(struct inode *inode, struct file *file)
{
#if 0     //stomic_dec_and_test()函数作用:自减法后判断是否为0
if (!atomic_dec_and_test(&canopen))  //原子变量canopen初始值为1,当有一个app1打开后--后为0,atomic_dec_and_test
{     //判断为true,!true=false,执行分支程序。此时加入有app2打开,自减后为-1,然后
atomic_inc(&canopen);     //判断为false,!false=true,执行atomic_inc,return -EBUSY;
return -EBUSY;
}
#endif

if (file->f_flags & O_NONBLOCK)
{
if (down_trylock(&button_lock))
return -EBUSY;
}
else
{
/* 获取信号量 */
down(&button_lock);
}
......
}
ssize sixth_drv_read(struct file *file,char __user *buf, size_t size, loff_t *ppos)
{

/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
}
防止两个app同时打开一个文件的方法:
总结:原子操作方法
//stomic_dec_and_test()函数作用:自减法后判断是否为0
//原子变量canopen初始值为1,当有一个app1打开后--后为0,atomic_dec_and_test
//判断为true,!true=false,执行分支程序。此时加入有app2打开,自减后为-1,然后
//判断为false,!false=true,执行atomic_inc,return -EBUSY;




static int sixth_drv_open(struct inode *inode, struct file *file)
{
#if 0    
if (!atomic_dec_and_test(&canopen))  
{     
atomic_inc(&canopen);    
return -EBUSY;
}
#endif

/* 获取信号量 */
down(&button_lock);
......
}
int sixth_drv_close(struct inode *inode, struct file *file)
{
....
up(&button_lock);
....
}
总结:信号量方法
      如果app1在调用open后,调用down($button_lock),可以执行程序,如果app2此时
      调用open后,则在down处睡眠,ps命令可以看到app1为S(运行状态),app2其为D(僵死状态)
      在app1被kill后,app2马上唤醒,进入S状态。       

static int sixth_drv_open(struct inode *inode, struct file *file)
{
.....
if (file->f_flags & O_NONBLOCK) //判断是否为非阻塞方式打开
{
if (down_trylock(&button_lock))  //非阻塞方式打开,down_trylock()获取信号量
return -EBUSY; //如果不成功则返回错误。(这是app2在app1已打开造成)
}
else
{
/* 获取信号量 */
down(&button_lock); //阻塞方式打开,获取信号量,如果不成功就休眠
}
......
}
ssize sixth_drv_read(struct file *file,char __user *buf, size_t size, loff_t *ppos)
{
if (file->f_flags & O_NONBLOCK)
{
if (!ev_press) //非阻塞方式,如果文件时非阻塞打开,按键未按下,
return -EAGAIN; //则返回错误(这是中断未发生ev_press=0,返回错误)
}
else
{
/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
}
.....
}
int sixth_drv_close(struct inode *inode, struct file *file)
{
....
up(&button_lock);
....
}
int main(int argc, char **argv)
{
....
fd = open("/dev/buttons", O_RDWR );      //在用阻塞的方式打开
//fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
}
总结:阻塞和非阻塞
  阻塞:在读取按键时,没有值则等待
  非阻塞:在读取按键时,没有值则返回错误。
  要实现,必须在驱动中的open函数中进行代码的写入。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值