用信号驱动I/O降低CPU使用率

信号类似于硬件上使用的"中断",只不过信号是软件层次上的,可以理解为软件层次上对中断的一种模拟,驱动通过主动向应用程序发送可访问的信号,应用程序获取到信号后即可从驱动设备中读取或写入数据了

1. 驱动和应用中的信号处理

驱动程序中需要定义fasync_struct结构体指针变量,一般在设备结构体中定义该变量,fasync_struct结构体原型如下:

struct fasync_struct {
 spinlock_t fa_lock;
 int magic;
 int fa_fd;
 struct fasync_struct *fa_next;
 struct file *fa_file;
 struct rcu_head fa_rcu;
};

⏩ fasync函数:file_operations操作集中要实现的函数

int (*fasync) (int fd, struct file *filp, int on)

⏩ fasync_helper函数:fasync函数里通过调用该函数来初始化前面定义的 fasync_struct结构体指针

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
//参数fapp:要初始化的fasync_struct 结构体指针变量

⏩ kill_fasync函数:当设备可访问时,驱动程序利用该函数向应用程序发出信号

void kill_fasync(struct fasync_struct **fp, int sig, int band)
//fp:要操作的 fasync_struct
//sig:要发送的信号
//band:可读时设置为 POLL_IN,可写时设置为 POLL_OUT

驱动中fasync函数使用示例,如下示:

//驱动中fasync函数示例
struct xxx_dev {
 ......
 struct fasync_struct *async_queue; /* 异步相关结构体 */
};

static int xxx_fasync(int fd, struct file *filp, int on) {
 struct xxx_dev *dev = (xxx_dev)filp->private_data;
 if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
  return -EIO;
 return 0;
}

static int xxx_release(struct inode *inode, struct file *filp) {
 return xxx_fasync(-1, filp, 0);  /* 删除异步通知 */
} //关闭驱动文件时需要释放fasync_struct

static struct file_operations xxx_ops = {
 ......
 .fasync = xxx_fasync,
 .release = xxx_release,
 ......
};

应用程序中对信号的处理包括以下三步:

⏩ 注册信号处理函数:使用signal函数来设置信号的处理函数

void (int) * signal(int sig, void (int) * func)

⏩ 将本应用程序的进程号告诉给内核

//以下函数用于将本应用程序的进程号告诉给内核
fcntl(fd, F_SETOWN, getpid())

⏩ 开启异步通知:当应用程序通过fcntl()函数改变fasync标记时,驱动程序file_operations操作集中的fasync函数就会执行

flags = fcntl(fd, F_GETFL);          /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC);  /* 开启当前进程异步通知功能 */

2. 信号驱动I/O程序编写

非阻塞IO实验代码基础上进行改编,设备树文件无需修改,只需修改应用程序和驱动程序里的部分代码即可。当按键按下后驱动程序向应用程序发送SIGIO信号,应用程序获取到SIGIO信号以后读取并且打印出按键值

⏩ 驱动程序中需要修改的部分

/* imx6uirq 设备结构体 */
struct imx6uirq_dev{
 ......
 struct fasync_struct *async_queue;  /* 异步相关结构体 */
};
......
......
void timer_function(unsigned long arg) {
 ......
 ......
 if(atomic_read(&dev->releasekey)) {  /* 一次完整的按键过程 */
  if(dev->async_queue)
   kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
 }
#if 0
 /* 唤醒进程 */
 if(atomic_read(&dev->releasekey)) {  /* 完成一次按键过程 */
  /* wake_up(&dev->r_wait); */
  wake_up_interruptible(&dev->r_wait);
 }
#endif
}
......
......
static int imx6uirq_fasync(int fd, struct file *filp, int on) {
 struct imx6uirq_dev *dev = (struct imx6uirq_dev *) filp->private_data;
 return fasync_helper(fd, filp, on, &dev->async_queue);
}

static int imx6uirq_release(struct inode *inode, struct file *filp) {
 return imx6uirq_fasync(-1, filp, 0);
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
 ......
 .fasync = imx6uirq_fasync,
 .release = imx6uirq_release,
};
......

⏩ 应用程序:获取到SIGIO信号后读取并且打印出按键值

/* SIGIO信号处理函数 */
static void sigio_signal_func(int signum) {
 int err = 0;
 unsigned int keyvalue = 0;

 err = read(fd, &keyvalue, sizeof(keyvalue));
 if(err < 0) {
  /* 读取错误 */
 } else {
  printf("sigio signal! key value=%d\r\n", keyvalue);
 }
}
/* 主程序 */
int main(int argc, char *argv[]) {
 int flags = 0;
 char *filename;

 if (argc != 2) {
  printf("Error Usage!\r\n");
  return -1;
 }

 filename = argv[1];
 fd = open(filename, O_RDWR);
 if (fd < 0) {
  printf("Can't open file %s\r\n", filename);
  return -1;
 }

 /* 设置信号 SIGIO 的处理函数 */
 signal(SIGIO, sigio_signal_func);
 fcntl(fd, F_SETOWN, getpid()); /* 将当前进程的进程号告诉给内核 */
 flags = fcntl(fd, F_GETFD); /* 获取当前的进程状态 */
 fcntl(fd, F_SETFL, flags | FASYNC);/* 设置进程启用异步通知功能 */

 while(1) {
  sleep(2);
 }

 close(fd);
 return 0;
}

3. 编译测试

⏩ 编译驱动程序:当前目录下创建Makefile文件,并make编译

KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := asyncnoti.o

build: kernel_modules

kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

⏩ 编译测试程序:无需内核参与,直接编译即可

arm-linux-gnueabihf-gcc asyncnotiApp.c -o asyncnotiApp

⏩ 将驱动文件和测试文件拷贝至rootfs/lib/modules/4.1.15后加载驱动

depmod  #第一次加载驱动时,需使用“depmod”命令
modprobe asyncnoti.ko

⏩ 使用./asyncnotiApp /dev/asyncnoti &命令,以后台模式运行应用程序,此时按下按键,应用程序会打印出按键值
在这里插入图片描述

⏩ 使用top命令查看asyncnotiApp的CPU使用率:可见使用信号驱动I/O方式处理后,CPU的使用率也非常低
在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安迪西嵌入式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值