增加了异步通知功能的虚拟设备驱动程序(fasync_helper)

本文介绍了一个基于Linux的管道驱动程序实现细节,包括设备注册、内存分配、读写操作及信号处理等内容。通过信号量和等待队列实现了多进程间的同步与通信。

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

#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#define __NO__VERSION__
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#ifndef vmf_MAJOR
#define vmf_MAJOR 244//默认主设备号
#endif

#define SHMDATASIZE 1024
#ifndef vmf_BUFFER
#define vmf_BUFFER SHMDATASIZE//默认管道缓冲区大小
#endif
int vmf_major=vmf_MAJOR;
int vmf_buffer=vmf_BUFFER;
MODULE_PARM(vmf_major,"i");//MODULE_PARM(var,type)模块参量的类型代码: b=byte h=short i=int l=long s=string
MODULE_PARM(vmf_buffer,"i");
MODULE_AUTHOR("lanhai");
//MODULE_LICENSE("GPL");

typedef struct vmf_Dev
{
 wait_queue_head_t rdq,wrq;// rdq表示堵塞读的等待队列,wrq是堵塞写的等待队列
 char *base; //所分配缓冲区的起始地址
 unsigned int buffersize; //是缓冲区的大小
 unsigned int len; //表示管道中已有数据块的长度
 unsigned int start; //当前应该读取的缓冲区位置相对于base的偏移量
 unsigned int readers,writers;//表示vmf设备当前的读者个数和写者个数
 struct semaphore sem; //用于互斥的访问的信号量
 struct fasync_struct *async_queue;
 
}vmf_Dev;

struct vmf_Dev *vmf_devices;


static int vmf_fasync(int fd, struct file *filp, int mode)
{
 struct vmf_Dev *dev = filp->private_data;
 return fasync_helper(fd, filp, mode, &dev->async_queue);
}

static int vmf_open(struct inode *inode,struct file *filp)//一个管道对应两个文件(读和写),两个节点。
{

 vmf_Dev *dev;

    //int num=MINOR(inode->i_rdev);//i_rdev--Device major and minor numbers;minor()取次设备号,用major(dev)取主设备号,用mkdev()将主、次设备号和在一起作为设备号(dev)
 //printk("<1>MINOR(inode->i_rdev)=%d/n",num);
 if(!filp->private_data)//void *private_data;私有空间;系统在调用驱动程序的open方法前将这个指针置为NULL。驱动程序可以将这个字段用于任意目的,也可以忽略这个字段。驱动程序可以用这个字段指向已分配的数据,但是一定要在内核释放file结构前的release方法中清除它。
    {
  dev=vmf_devices;//vmf_Dev vmf_devices设备赋值给局部变量dev
  
  filp->private_data=dev;//将其保存到文件私有空间中

 }
 else
 {

  dev=filp->private_data;//如果私有空间已经使用,说明管道已经打开直接把此私有空间赋值给dev

 }

 if (down_interruptible(&dev->sem))//一个管道一个互斥锁,由此到up(&dev->sem)是临界区。返回0表示成功

  return -ERESTARTSYS;

 if(!dev->base)
 {

        dev->base=kmalloc(vmf_buffer,GFP_KERNEL);

  if(!dev->base)
  {

   up(&dev->sem);

   return -ENOMEM;

  }

  dev->buffersize=vmf_buffer;

  dev->len=dev->start=0;//初始化时已写入数据长度和偏移量为0

 }

 if(filp->f_mode&FMODE_READ)

  dev->readers++;

 if(filp->f_mode&FMODE_WRITE)
  
  dev->writers++;

 filp->private_data=dev;

 MOD_INC_USE_COUNT;

 up(&dev->sem);

 return 0;
}

 

static int vmf_release(struct inode *inode,struct file *filp)

{

 vmf_Dev *dev=filp->private_data;

 down(&dev->sem);

 if(filp->f_mode&FMODE_READ)

  dev->readers--;

 if(filp->f_mode&FMODE_WRITE)
 {

  dev->writers--;

  wake_up_interruptible(&dev->sem.wait);
  //Linux进程可能会在事件发生之前一直等待下去。例如,一个进程可能会在运行中等待一些写入硬件设备的信息的到来。其中一种方式是进程可以使用sleep()和wakeup()这两个系统调用,进程先使自己处于睡眠状态,等待事件的到来,
         //一旦事件发生,进程即可被唤醒。举个例子来说:interruptible_sleep_on(&dev_wait_queue)函数使进程睡眠并且将此进程的进程号加到进程睡眠列表dev_wait_queue中,一旦设备准备好后,设备发出一个中断,从而导致设备
         //驱动程序中相应的例程被调用,这个驱动程序例程处理完一些设备要求的事宜后会发出一个唤醒进程的信号,通常使用wake_up_interruptible(&dev_wait_queue)函数,它可以唤醒dev_wait_queue所示列表中的所有进程。
 }

 if((dev->readers+dev->writers==0)&&(dev->len==0))
 {//没有读者、写者并且缓冲区数据为0,释放缓冲区

  kfree(dev->base);

 
  dev->base=NULL;

 }

 up(&dev->sem);

 vmf_fasync( -1,filp,0);

 MOD_DEC_USE_COUNT;

 return 0;
}


static ssize_t vmf_read(struct file *filp,char *buf,size_t count,loff_t *f_pos)

{

 vmf_Dev *dev=filp->private_data;//取当前设备文件保的相关数据

 ssize_t read=0;

 if(f_pos!=&filp->f_pos)

  return -ESPIPE;

 if(down_interruptible(&dev->sem))

  return -ERESTARTSYS;

do_more_read:

 while(dev->len==0)
 {

  if(!dev->writers)
  {

   up(&dev->sem);

   return -EAGAIN;

  }

  up(&dev->sem);

  if(filp->f_flags&O_NONBLOCK)

   return -EAGAIN;

  printk("<1>%s reading:going to sleep/n",current->comm);

  if(wait_event_interruptible(dev->rdq,(dev->len>0)))//wait_event使当前进程在等待队列rdq中等待直至dev->len>0为真

   return -ERESTARTSYS;

  printk("<1>%s has been waken up/n",current->comm);

  if(down_interruptible(&dev->sem))

   return -ERESTARTSYS;
 }


 while(count>0&&dev->len)
 {

  char *pipebuf=dev->base+dev->start;//pipebuf指向当前读取数据

  ssize_t chars=dev->buffersize-dev->start;//chars表示一次性度取的最大数据量

  if(chars>count) chars=count;

  if(chars>dev->len) chars=dev->len;//dev->len表示设备中已有的数据块长度

  if(copy_to_user(buf,pipebuf,chars))
  {//copy_to_user(to,from,n),将pipebuf指向的内核区数据复制到由buf指向的用户区

   up(&dev->sem);

   return -EFAULT;

  }

  read+=chars;

  dev->start+=chars;

  dev->start%=dev->buffersize;

  dev->len-=chars;

  count-=chars;

  buf+=chars;

 }

 if (!dev->len)
  
  dev->start=0;

 if(count&&dev->writers&&!(filp->f_flags&O_NONBLOCK))
 {//count大于0说明设备中没有可读数据,但是还要继续读,所以唤醒写等待队列

  up(&dev->sem);

  wake_up_interruptible(&dev->wrq);

  if(down_interruptible(&dev->sem))

   return -ERESTARTSYS;

  goto do_more_read;

 }

 up(&dev->sem);

 wake_up_interruptible(&dev->wrq);//唤醒由于wait_event_interruptible而进入写等待队列的进程

 //printk("<1>%s did read %1i bytes/n",current->comm,(long)read);

 return read;//已经读出的数据块大小
}

 

static ssize_t vmf_write(struct file *filp,const char *buf,size_t count,loff_t *f_pos)
{

 vmf_Dev *dev=filp->private_data;

 ssize_t written=0;

 if(f_pos!=&filp->f_pos||count==0)

  return -ESPIPE;

 if(down_interruptible(&dev->sem))

  return -ERESTARTSYS;

do_more_write:

 while(dev->len==dev->buffersize)
 {

  up(&dev->sem);

  if(filp->f_flags&O_NONBLOCK)

   return -EAGAIN;

  printk("%s writting:going to sleep/n",current->comm);


  if(wait_event_interruptible(dev->wrq,(dev->len<dev->buffersize)))//:wait_event使当前进程在等待队列wrq中等待直至dev->lenbuffersize为真

   return -ERESTARTSYS;

  printk("%s has been waken up/n",current->comm);

  if(down_interruptible(&dev->sem))

   return -ERESTARTSYS;

 }

 while(count>0)
 {//写数据

  char *pipebuf=dev->base+(dev->len+dev->start)%dev->buffersize;//pipebuf指向当前要写入数据的地址,在未读出的数据块好骟写入

  ssize_t chars=dev->buffersize-(dev->len+dev->start);//这两行计算可以一次性写入的最大数据量

  if(chars<0) chars+=dev->start;

  if(chars!=0)
  {//chars==0表示缓冲区满

   if (chars>count) chars=count;

   if (copy_from_user(pipebuf,buf,chars))
   {//copy_from_user(to,from,n)

    up(&dev->sem);

    return -EFAULT;

   }

   written+=chars;

   dev->len+=chars;

   count-=chars;

   buf+=chars;

   if (dev->async_queue)

    kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

  }

 }

 if (count&&!(filp->f_flags&O_NONBLOCK))
 {//count大于0说明写空间已满,但是还要继续写,唤醒读等待进程

  up(&dev->sem);

  wake_up_interruptible(&dev->rdq);

  if (down_interruptible(&dev->sem))

   return -ERESTARTSYS;

  goto do_more_write;

 }

 up(&dev->sem);

 wake_up_interruptible(&dev->rdq);

 //printk("%s did write %1i bytes/n",current->comm,(long)written);

 return written;
}


unsigned int vmf_poll(struct file *filp,poll_table *wait)//poll(struct file *,poll_table *)让一个进程决定它
             //是否能无阻塞地从一个或多个打开的文件中读取数据,
             //或者向这些文件中写数据。还可以用来实现在无阻塞
             //情况下的不同源输入的多路复用
{

 vmf_Dev *dev=filp->private_data;

 unsigned int mask=0;

 poll_wait(filp,&dev->rdq,wait);//将当前进程进入等待队列排队

 poll_wait(filp,&dev->wrq,wait);

 if (dev->len>0)
  
  mask|=POLLIN|POLLRDNORM;//管道能无阻塞可读

 if (dev->len!=dev->buffersize)
  
  mask|=POLLOUT|POLLWRNORM;//管道能无阻塞可写

 return mask;
}


static struct file_operations vmf_fops=
{
read: vmf_read,
write: vmf_write,
poll: vmf_poll,
open: vmf_open,
release: vmf_release,
fasync: vmf_fasync,
};


static int __init vmf_init_module(void)//__init是一个段描叙符号  对于任何有这个修饰符号的数据或者函数,都将放在init代码段或者数据段里面。当内核启动完毕后该段的所有空间将被回收
{

 int result;

 SET_MODULE_OWNER(&vmf_fops);//设置struct module *owner字断,相当于在vmf_fops中设置"owner:THIS_MODULE,"


 result=register_chrdev(vmf_major,"vmf",&vmf_fops);

 if (result<0)

 {

  printk(KERN_WARNING "vmf:can't get major %d/n",vmf_major);

  return result;

 }

 if (vmf_major==0)

  vmf_major=result;//自动分配的major号

 vmf_devices=kmalloc(sizeof(vmf_Dev),GFP_KERNEL);//划分vmf_Dev管道设备的全局空间,本例中11个

 if (!vmf_devices)

 {

  return -ENOMEM;

 }

 memset(vmf_devices,0,sizeof(vmf_Dev));


  init_waitqueue_head(&vmf_devices->rdq);//读等待队列头

  init_waitqueue_head(&vmf_devices->wrq);//写等待队列头

  sema_init(&vmf_devices->sem,1);//用1来初始化信号量vmfo_devices.sem


 return 0;
}

static void __exit vmf_cleanup_module(void)

{


 unregister_chrdev(vmf_major,"vmf");


 if (vmf_devices)

 {

 

      if (vmf_devices->base)

   kfree(vmf_devices->base);//释放由kmalloc分配的区域


  kfree(vmf_devices);//最终释放由kmalloc分配的区域

 

 }
}


module_init(vmf_init_module);

module_exit(vmf_cleanup_module);

 

 

/////////////////////////////////////////////////////////////////////////////////////////////

MAKEFILE和以前的相同

 

应用程序如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>


void input_handler(int signum)
{
  printf("receive a signal from globalfifo,signalnum:%d/n",signum);
}

main()
{
  int fd, oflags;
  fd = open("/shm_a", O_RDWR, S_IRUSR | S_IWUSR);
  if (fd !=  - 1)
  {
    //启动信号驱动机制
    signal(SIGIO, input_handler); //让input_handler()处理SIGIO信号
    fcntl(fd, F_SETOWN, getpid());
    oflags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, oflags | FASYNC);
    while(1)
    {
     sleep(100);
    }
  }
  else
  {
    printf("device open failure/n");
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值