九鼎创展论坛中文版English
登录 | 立即注册 设为首页收藏本站 切换到宽版
查看: 5629|回复: 1

x4412&ibox项目实战40-Linux设备驱动之异步通知实验

[复制链接]
发表于 2014-10-11 16:48:41 | 显示全部楼层 |阅读模式
阻塞和非阻塞,以有poll函数都是在进程访问驱动时,通过内核调度来完成。能否实现当内核准备好时,主动向应用发起通知呢?linux的异步通知机制能够实现这一功能。异步通知是指一旦设备准备就绪,则主动通知应用程序,应用程序无需再通过查询设备状态,等待等机制,它非常类似硬件中的中断,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O中使用poll()意味着查询设备是否可访问,而异步通知则意味着设备通知自身可访问,实现了异步I/O。由此可见,这几种方式I/O可以互为补充。阻塞、非阻塞I/O、异步通知本身没有优劣,应该根据不同的应用场景合理选择。
异步通知通过linux信号实现,信号全称为软中断信号,也有人称作软中断。从它的命名可以看出,它的实质和使用很象中断。所以,信号可以说是进程控制的一部分。 linux系统可用的信号如下表。
  
信号
  
含义
SIGHUP
1
挂起
SIGINT
2
终端中断
SIGQUIT
3
终端退出
SIGILL
4
无效命令
SIGTRAP
5
跟踪陷阱
SIGIOT
6
IOT陷阱
SIGBUS
7
BUS错误
SIGFPE
8
浮点异常
SIGKILL
9
强行终止(不能被捕获或忽略)
SIGUSR1
10
用户定义的信号1
SIGSEGV
11
无效的内存段处理
SIGUSR2
12
用户定义的信号2
SIGPIPE
13
半关闭管道发生写操作
SIGALRM
14
计时器到期
SIGTERM
15
终止
SIGSTKFLT
16
堆栈错误
SIGCHLD
17
子进程已经停止或退出
SIGCONT
18
如果停止了,继续执行
SIGSTOP
19
停止执行(不能被捕获或忽略)
SIGTSTP
20
终端停止信号
SIGTTIN
21
后台进程需要从终端读取输入
SIGTTOU
22
后台进程需要向从终端写出
SIGURG
23
紧急的套接字事件
SIGXCPU
24
超额使用CPU分配的时间
SIGXFSZ
25
文件尺寸超额
SIGVTALRM
26
虚拟时钟信号
SIGPROF
27
时钟信号描述
SIGWINCH
28
窗口尺寸变化
SIGIO
29
I/O
SIGPWR
30
断电重启
可以在进入linux系统后,在命令行通过kill–l指令查询,该命令可以列出系统支持的所有信号。注意,列出的信号都省略了“SIG”。
  1. [root@x4412 ~]# kill -l
  2. HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH IO PWR SYS
  3. [root@x4412 ~]#
复制代码
以丰信号除了SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他的全部信号。一个信号被捕获的意思是当一个信号到达时有相应的代码处理它。如果一个信号没有被这个进程所捕获,内核将采用默认行为处理。
在用户程序中,为了捕获信号,可以使用signal()函数来设置对应信号的处理函数。在驱动程序中,通过fasync()函数来释放信号。
将globalfifo驱动通过异步通知的方式告知应用,编写应用程序测试异步通知实现的效果。
在kernel/drivers/char/x4412目录下新建x4412-globalfifo-fasync.c文件,编辑内容如下:
  1. #include <linux/module.h>
  2. #include <linux/types.h>
  3. #include <linux/fs.h>
  4. #include <linux/errno.h>
  5. #include <linux/sched.h>
  6. #include <linux/init.h>
  7. #include <linux/cdev.h>
  8. #include <asm/uaccess.h>
  9. #include <linux/poll.h>
  10. #include <linux/slab.h>
  11. #include <linux/device.h>
  12. #define GLOBALFIFO_SIZE 0x1000 /*全局fifo最大4K字节*/
  13. #define FIFO_CLEAR 0x1  /*清0全局内存的长度*/
  14. DEFINE_SEMAPHORE(sem);//初始化并声明一个信号量
  15. DECLARE_WAIT_QUEUE_HEAD(r_wait);//初始化并声明一个等待队列r_wait
  16. DECLARE_WAIT_QUEUE_HEAD(w_wait);//初始化并声明一个等待队列w_wait
  17. static int globalfifo_major;
  18. static struct class *cdev_class;
  19. struct globalfifo_dev
  20. {
  21.          struct cdev cdev; /*cdev结构体*/
  22.          unsigned int current_len;    /*fifo有效数据长度*/
  23.          unsigned char mem[GLOBALFIFO_SIZE]; /*全局内存*/
  24.          struct fasync_struct *async_queue;/*异步结构体指针*/
  25. };
  26. struct globalfifo_dev *globalfifo_devp; /*设备结构体指针*/
  27. static int globalfifo_fasync(int fd,struct file *filp,int mode)
  28. {
  29.          struct globalfifo_dev *dev =filp->private_data;
  30.          return fasync_helper(fd,filp,mode,&dev->async_queue);
  31. }
  32. int globalfifo_open(struct inode *inode, struct file *filp)
  33. {
  34.          /*将设备结构体指针赋值给文件私有数据指针*/
  35.          filp->private_data = globalfifo_devp;
  36.          return 0;
  37. }
  38. int globalfifo_release(struct inode *inode, struct file *filp)
  39. {
  40.          globalfifo_fasync(-1,filp,0);/*将文件从异步通知列表里删除*/
  41.          return 0;
  42. }
  43. static long globalfifo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  44. {
  45.          struct globalfifo_dev *dev = filp->private_data;/*获得设备结构体指针*/
  46.          switch (cmd)
  47.          {
  48.                    case FIFO_CLEAR:
  49.                             down(&sem); //获得信号量   
  50.                             dev->current_len = 0;
  51.                             memset(dev->mem,0,GLOBALFIFO_SIZE);
  52.                             up(&sem); //释放信号量
  53.                             printk(KERN_INFO "globalfifo is set to zero\n");     
  54.                    break;
  55.                    default:
  56.                             return  - EINVAL;
  57.          }
  58.          return 0;
  59. }
  60. static unsigned int globalfifo_poll(struct file *filp, poll_table *wait)
  61. {
  62.          unsigned int mask = 0;
  63.          struct globalfifo_dev *dev = filp->private_data; /*获得设备结构体指针*/
  64.          down(&sem);
  65.          poll_wait(filp, &r_wait, wait);//添加读等待队列头
  66.          poll_wait(filp, &w_wait, wait);//添加写等待队列头
  67.          if(dev->current_len != 0)/*fifo非空*/
  68.          {
  69.                    mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/
  70.          }
  71.          if (dev->current_len != GLOBALFIFO_SIZE)/*fifo非满*/
  72.          {
  73.                    mask |= POLLOUT | POLLWRNORM; /*标示数据可写入*/
  74.          }
  75.          up(&sem);
  76.          return mask;
  77. }
  78. static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count,loff_t *ppos)
  79. {
  80.          int ret;
  81.          struct globalfifo_dev *dev = filp->private_data; //获得设备结构体指针
  82.          DECLARE_WAITQUEUE(wait, current); //定义等待队列
  83.          down(&sem); //获得信号量
  84.          add_wait_queue(&r_wait, &wait); //进入读等待队列头
  85.          if (dev->current_len == 0)/* 如果FIFO为空,则进程进入睡眠等待 */
  86.          {
  87.                    if (filp->f_flags &O_NONBLOCK)//非阻塞
  88.                    {
  89.                             ret =  - EAGAIN;
  90.                             goto out;
  91.                    }
  92.                    __set_current_state(TASK_INTERRUPTIBLE); //改变进程状态为睡眠
  93.                    up(&sem);
  94.                    schedule(); //调度其他进程执行
  95.                    if (signal_pending(current))//如果是因为信号唤醒
  96.                    {
  97.                             ret =  - ERESTARTSYS;
  98.                             goto out2;
  99.                    }
  100.                    down(&sem);
  101.          }
  102.          if (count > dev->current_len)/* 拷贝到用户空间 */
  103.                    count = dev->current_len;
  104.          if (copy_to_user(buf, dev->mem, count)) //从内核空间拷贝到用户空间
  105.          {
  106.                    ret =  - EFAULT;
  107.                    goto out;
  108.          }
  109.          else
  110.          {
  111.                    memcpy(dev->mem, dev->mem + count, dev->current_len - count);  //fifo(管道)数据前移
  112.                    dev->current_len -= count;     //有效数据长度减少
  113.                    printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count, dev->current_len);
  114.                    wake_up_interruptible(&w_wait); //唤醒写等待队列
  115.                    ret = count;
  116.          }
  117. out:
  118.          up(&sem); //释放信号量
  119. out2:
  120.          remove_wait_queue(&w_wait, &wait); //从附属的等待队列头移除
  121.          set_current_state(TASK_RUNNING);
  122.          return ret;
  123. }
  124. /*globalfifo写操作*/
  125. static ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
  126. {
  127.          struct globalfifo_dev *dev = filp->private_data; //获得设备结构体指针
  128.          int ret;
  129.          DECLARE_WAITQUEUE(wait, current); //定义等待队列
  130.          down(&sem); //获取信号量
  131.          add_wait_queue(&w_wait, &wait); //进入写等待队列头
  132.          if (dev->current_len == GLOBALFIFO_SIZE)/* 如果FIFO已满则进入睡眠等待 */
  133.          {
  134.                    if (filp->f_flags &O_NONBLOCK)//如果是非阻塞访问
  135.                    {
  136.                             ret =  - EAGAIN;
  137.                             goto out;
  138.                    }
  139.                    __set_current_state(TASK_INTERRUPTIBLE); //改变进程状态为睡眠
  140.                    up(&sem);
  141.                    schedule(); //调度其他进程执行
  142.                    if (signal_pending(current))//如果是因为信号唤醒
  143.                    {
  144.                             ret =  - ERESTARTSYS;
  145.                             goto out2;
  146.                    }
  147.                    down(&sem); //获得信号量
  148.          }
  149.          /*从用户空间拷贝到内核空间*/
  150.          if (count > GLOBALFIFO_SIZE - dev->current_len)
  151.                    count = GLOBALFIFO_SIZE - dev->current_len;
  152.          if (copy_from_user(dev->mem + dev->current_len, buf, count))
  153.          {
  154.                    ret =  - EFAULT;
  155.                    goto out;
  156.          }
  157.          else
  158.          {
  159.                    dev->current_len += count;
  160.                    printk(KERN_INFO "written %d bytes(s),current_len:%d\n", count, dev->current_len);
  161.                    wake_up_interruptible(&r_wait); //唤醒读等待队列
  162.                    ret = count;
  163.          }  
  164.     if(dev->async_queue)/*产生异步读信号*/
  165.            kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
  166. out:
  167.          up(&sem); //释放信号量
  168. out2:
  169.          remove_wait_queue(&w_wait, &wait); //从附属的等待队列头移除
  170.          set_current_state(TASK_RUNNING);
  171.          return ret;
  172. }
  173. /*文件操作结构体*/
  174. static const struct file_operations globalfifo_fops =
  175. {
  176.          .owner = THIS_MODULE,
  177.          .read = globalfifo_read,
  178.          .write = globalfifo_write,
  179.          .unlocked_ioctl = globalfifo_ioctl,
  180.          .poll = globalfifo_poll,
  181.          .fasync=globalfifo_fasync,
  182.          .open = globalfifo_open,
  183.          .release = globalfifo_release,
  184. };
  185. /*初始化并注册cdev*/
  186. static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
  187. {
  188.          int err, devno = MKDEV(globalfifo_major, index);
  189.          cdev_init(&dev->cdev, &globalfifo_fops);
  190.          dev->cdev.owner = THIS_MODULE;
  191.          dev->cdev.ops = &globalfifo_fops;
  192.          err = cdev_add(&dev->cdev, devno, 1);
  193.          if (err)
  194.                    printk(KERN_NOTICE "Error %d adding LED%d", err, index);
  195. }
  196. /*设备驱动模块加载函数*/
  197. int globalfifo_init(void)
  198. {
  199.          int ret;
  200.          dev_t devno;
  201.          ret = alloc_chrdev_region(&devno, 0, 1, "x4412-globalfifo-fasync");
  202.          globalfifo_major = MAJOR(devno);
  203.          if (ret < 0)
  204.                    return ret;
  205.          /* 动态申请设备结构体的内存*/
  206.          globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
  207.          if (!globalfifo_devp)    /*申请失败*/
  208.          {
  209.                    ret =  - ENOMEM;
  210.                    goto fail_malloc;
  211.          }
  212.          memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));
  213.          globalfifo_setup_cdev(globalfifo_devp, 0);
  214.          cdev_class = class_create(THIS_MODULE,"x4412-globalfifo-fasync");
  215.          if(IS_ERR(cdev_class))
  216.                    goto fail_class;
  217.          device_create(cdev_class,NULL,devno,NULL,"x4412-globalfifo-fasync");
  218.          return 0;
  219. fail_class:
  220.          class_destroy(cdev_class);
  221. fail_malloc:
  222.          unregister_chrdev_region(devno, 1);
  223.          return ret;
  224. }
  225. /*模块卸载函数*/
  226. void globalfifo_exit(void)
  227. {
  228.          device_destroy(cdev_class,MKDEV(globalfifo_major,0));
  229.          class_destroy(cdev_class);
  230.          cdev_del(&globalfifo_devp->cdev);   /*注销cdev*/
  231.          kfree(globalfifo_devp);     /*释放设备结构体内存*/
  232.          unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); /*释放设备号*/
  233. }
  234. MODULE_LICENSE("Dual BSD/GPL");
  235. MODULE_AUTHOR("www.9tripod.com");
  236. module_init(globalfifo_init);
  237. module_exit(globalfifo_exit);
复制代码
       和前面的x4412-globalfifo-poll.c文件对比,不然发现增加异步通知需要增加的内容。在file_operations结构体中添加了fasync成员:
  1. .fasync=globalfifo_fasync,
复制代码
       globalfifo_dev结构体中增加了异步结构体指针:
  1. struct fasync_struct *async_queue;
复制代码
       定义了globalfifo_fasync函数,供删除异步通知时用:
  1. static int globalfifo_fasync(int fd,struct file *filp,int mode)
  2. {
  3.          struct globalfifo_dev *dev =filp->private_data;
  4.          return fasync_helper(fd,filp,mode,&dev->async_queue);
  5. }
  6. int globalfifo_release(struct inode *inode, struct file *filp)
  7. {
  8.          globalfifo_fasync(-1,filp,0);/*将文件从异步通知列表里删除*/
  9.          return 0;
  10. }
复制代码
       程序需要实现的是当FIFO写有数据时,FIFO将具备可读属性,通过给上层发异步通知的消息,上层收到消息后马上执行读操作,因此在驱动中只需要在写完FIFO后释放消息即可:
if(dev->async_queue)/*产生异步读信号*/
  1.            kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
复制代码
       到此,驱动中的异步通知增加完毕。编辑kernel/drivers/char/x4412/Kconfig文件,增加如下语句:
  1. config X4412_GLOBALFIFO_FASYNC_DRIVER
  2.          tristate "x4412 globalfifo by fasync driver"
  3.          default m
  4.          help
  5.          compile for x4412 globalfifo by fasync driver,y for kernel,m for module.
复制代码
       编辑kernel/drivers/char/x4412/Makefile文件,增加如下语句:
  1. obj-$(CONFIG_X4412_GLOBALFIFO_FASYNC_DRIVER) += x4412-globalfifo-fasync.o
复制代码
       编译内核,将会在kernel/drivers/char/x4412目录下生成目标文件x4412-globalfifo-fasync.ko文件。
       ubuntu用户目录或samba目录下新建测试应用程序x4412-globalfifo-fasync-app.c,编辑内容如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <fcntl.h>
  5. #include <sys/ioctl.h>
  6. #include <signal.h>
  7. #define S_IRUSR 00400
  8. #define S_IWUSR 00200
  9. void input_handler(int signum)
  10. {
  11.     printf("signum is ;%d\n",signum);
  12. }
  13. int main()
  14. {
  15.     int fd,oflags;
  16.     fd=open("/dev/x4412-globalfifo-fasync",O_RDWR,S_IRUSR | S_IWUSR);
  17.     if(fd != -1) {
  18.         /*启动信号驱动机制*/
  19.         signal(SIGIO,input_handler);/*让input_handler 处理SIGIO信号*/
  20.         fcntl(fd,F_SETOWN,getpid());//设置本进程为打开的文件的拥有者
  21.         oflags=fcntl(fd,F_GETFL);
  22.         fcntl(fd,F_SETFL,oflags|FASYNC);//对设备设置fasync标志
  23.         while(1)
  24.             {
  25.                 sleep(100);
  26.             }
  27.     } else
  28.         {
  29.             printf("device open failed!\n");
  30.         }
  31.     return 0;
  32. }
复制代码
       在应用程序中,signal函数用于关联处理信号的函数input_handler,紧接着设置本进程为打开的文件的拥有者,并对设备设置fasync标志,之后进程直接进入循环等待状态。一旦进程收到驱动发送的异步通知,input_handler函数将会被触发并得以执行。
       加载驱动模块并运行测试程序后,通过echo命令每向驱动模块写入一次数据,异步通知将会发送一次,应用程序进程随之也将调用一次关联函数input_handle()
  1. [root@x4412 mnt]# insmod x4412-globalfifo-fasync.ko
  2. [root@x4412 mnt]# ./x4412-globalfifo-fasync-app &
  3. [1] 1186
  4. [root@x4412 mnt]# echo 'www.9tripod.com' > /dev/x4412-globalfifo-fasync
  5. [ 3935.793561] written 16 bytes(s),current_len:16
  6. signum is ;29
  7. [root@x4412 mnt]# echo 'www.9tripod.com' > /dev/x4412-globalfifo-fasync
  8. [ 3938.655007] written 16 bytes(s),current_len:32
  9. signum is ;29
  10. [root@x4412 mnt]#
复制代码
       上面的测试结果和我们的预期不谋而合,同时,不管我们输入多少次echosignum总是返回29。从前面的linux信号表格可以看出,29正好对应SIGIO。再编写一段应用程序,当使用ctrl+c中止进程时,将会发送SIGINT信号,当使用kill命令结束进程时,将会发送SIGTERM信号。通过查询信号表可知,这时对应的signum应该为215
       ubuntu用户目录或samba目录下新建应用程序x4412-signal-app.c文件,编辑内容如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <fcntl.h>
  5. #include <sys/ioctl.h>
  6. #include <signal.h>
  7. void input_handler(int signum)
  8. {
  9.     printf("signum is %d\n",signum);
  10. }
  11. int main()
  12. {
  13.         signal(SIGINT,input_handler);//让input_handler处理SIGINT信号
  14. signal(SIGTERM,input_handler);//让input_handler处理SIGTERM信号
  15.         while(1)
  16.         {
  17.                 sleep(100);
  18.         }
  19.         return 0;
  20. }
复制代码
       使用如下指令编译应用程序:
  1. arm-none-linux-gnueabi-gcc x4412-signal-app.c -o x4412-signal-app
复制代码
       在前台测试应用程序,不断按ctrl+c中断进程:
  1. [root@x4412 mnt]# ./x4412-signal-app
  2. signum is 2
  3. signum is 2
复制代码
       可见,ctrl+c触发了中断信号,打印信息不断提示2
       在后台测试应用程序,按ctrl+c将无法发送给应用了,因为系统并不知道需要将中断信号发送给谁。使用kill指令终止进程,将会给对应进程发送终止信号。
  1. [root@x4412 mnt]# ./x4412-signal-app &
  2. [1] 1179
  3. [root@x4412 mnt]#
  4. [root@x4412 mnt]# kill 1179
  5. [root@x4412 mnt]# signum is 15

  6. [root@x4412 mnt]#
复制代码
       再编写一个应用程序,通过signal(SIGIO,input_handler)对标准输入文件描述符STDIN_FILENO 启动信号机制。当用户在当前进程输入数据后,应用程序将接收到SIGIO信号,进而触发input_handle函数。
       ubuntu用户目录或samba目录下新建应用程序x4412-signal-stdin.c文件,编辑内容如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <fcntl.h>
  5. #include <sys/ioctl.h>
  6. #include <signal.h>

  7. void input_handler(int signum)
  8. {
  9.          char data[100];
  10.          int len;
  11.          //读取并输出STDIN_FILENO上的输入
  12.          len = read(STDIN_FILENO,&data,100);
  13.          data[len]=0;
  14.     printf("available input from x4412:%s\n",data);
  15. }

  16. int main()
  17. {
  18.          int oflags;
  19.      signal(SIGIO,input_handler);//让input_handler处理SIGIO信号
  20.          fcntl(STDIN_FILENO,F_SETOWN,getpid());
  21.          oflags = fcntl(STDIN_FILENO,F_GETFL);
  22.          fcntl(STDIN_FILENO,F_SETFL,oflags | FASYNC);
  23.         while(1)
  24.         {
  25.                 sleep(100);
  26.         }

  27.         return 0;   
  28. }
复制代码
       使用如下命令编译应用程序:
  1. arm-none-linux-gnueabi-gcc x4412-signal-stdin.c -o x4412-signal-stdin
复制代码
       测试效果如下:
  1. [root@x4412 mnt]# ./x4412-signal-stdin
  2. I love x4412 board.
  3. available input from x4412:I love x4412 board.

  4. I love ibox board too.
  5. available input from x4412:I love ibox board too.
复制代码
       可见,每当我们输入一串字符串,它都将触发input_handle函数执行一次,同时它会将输入的数据读取并打印出来。

回复

使用道具 举报

发表于 2017-1-23 17:53:00 | 显示全部楼层
这篇写得不错,嘻嘻
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳市九鼎创展科技官方论坛 ( 粤ICP备11028681号-2  

GMT+8, 2024-3-28 22:13 , Processed in 0.025429 second(s), 17 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表