Linux 内核信号 SIGIO 使用实例讲解

前言

进程间通信(IPC)底层原理都是需要借助内核来完成的。而且大部分都需要用户空间主动发起,内核仅作为一个中转。
那么内核主动向用户空间发送信息的方式有哪些呢?
Netlink 是一种,在 WiFi 子系统中,WiFi 驱动通过 Netlink 跟用户空间的 hostapd/wpa_supplicant 通信,可以主动向上层传递 WiFi 底层状态信息。
那除了 Netlink,还有别的方式吗?
信号(Signal)也是一种。

信号的基本概念

信号(Signal)是在软件层次上对中断机制的一种模拟。在原理上,一个进程收到一个信号与处理器收到一个中断请求是类似的。信号是异步的,进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。

信号的功能介绍

所谓同步,就是“你慢我等你”。
那么异步就是:你慢那你就自己玩,我做自己的事去了,你有情况再通知我。
所谓异步通知,就是 APP 可以忙自己的事,当驱动程序有数据时它会主动给 APP 发信号,这会导致 APP 执行信号处理函数。

仔细想想“发信号”,这只有 3 个字,却可以引发很多问题:
① 谁发:驱动程序发
② 发什么:信号
③ 发什么信号:SIGIO
④ 怎么发:内核里提供有函数
⑤ 发给谁:APP,APP 要把自己告诉驱动
⑥ APP 收到后做什么:执行信号处理函数
⑦ 信号处理函数和信号,之间怎么挂钩:APP 注册信号处理函数

小孩通知妈妈的事情有很多:饿了、渴了、想找人玩。
Linux 系统中也有很多信号,在 Linux 内核源文件 include\uapi\asm-generic\signal.h 中,有很多信号的宏定义:

#define SIGHUP		 1
#define SIGINT		 2
#define SIGQUIT		 3
#define SIGILL		 4
#define SIGTRAP		 5
#define SIGABRT		 6
#define SIGIOT		 6
#define SIGBUS		 7
#define SIGFPE		 8
#define SIGKILL		 9
#define SIGUSR1		10
#define SIGSEGV		11
#define SIGUSR2		12
#define SIGPIPE		13
#define SIGALRM		14
#define SIGTERM		15
#define SIGSTKFLT	16
#define SIGCHLD		17
#define SIGCONT		18
#define SIGSTOP		19
#define SIGTSTP		20
#define SIGTTIN		21
#define SIGTTOU		22
#define SIGURG		23
#define SIGXCPU		24
#define SIGXFSZ		25
#define SIGVTALRM	26
#define SIGPROF		27
#define SIGWINCH	28
#define SIGIO		29 // 驱动常用信号,表示有 I/O 事件
#define SIGPWR		30
#define SIGSYS		31

信号的处理方式

信号可以被以下几种方式处理:

  • 忽略:接收到信号后不做任何反应。
  • 捕捉:为信号定义一个处理函数,当信号发生时,执行该处理函数。
  • 默认操作:执行系统默认的操作,如终止进程或产生core dump。

SIGIO 的使用

驱动程序通知 APP 时,它会发出“SIGIO”这个信号,表示有“I/O 事件”要处理。

APP 代码流程

就 APP 而言,你想处理 SIGIO 信息,那么需要提供信号处理函数,并且要跟 SIGIO 挂钩。这可以通过一个 signal 函数来“给某个信号注册处理函数”,用法如下:
在这里插入图片描述

除了注册 SIGIO 的处理函数,APP 还要做什么事?想想这几个问题:

① 内核里有那么多驱动,你想让哪一个驱动给你发 SIGIO 信号?
APP 要打开驱动程序的设备节点。

② 驱动程序怎么知道要发信号给你而不是别人?
APP 要把自己的进程 ID 告诉驱动程序。

③ APP 有时候想收到信号,有时候又不想收到信号:
应该可以把 APP 的意愿告诉驱动:设置 Flag 里面的 FASYNC 位为 1,使能“异步通知”。

驱动代码流程

驱动程序要做什么?发信号。
① APP 设置进程 ID 时,驱动程序要记录下进程 ID ;
② APP 还要使能驱动程序的异步通知功能,驱动中有对应的函数:
③ APP 打开驱动程序时,内核会创建对应的 file 结构体, file 中有 f_flags ; f_flags 中有一个 FASYNC 位,它被设置为 1 时表示使能异步通知功能。 当 f_flags 中的 FASYNC 位发生变化时,驱动程序的 fasync 函数被调用。
④ 发生中断有数据时,驱动调用内核辅助函数发信号。 这个辅助函数名为 kill_fasync。
综上所述,使用异步通知,也就是使用信号的流程如下图所示:
在这里插入图片描述
① 打开驱动程序对应的设备节点文件,使用 fd 文件描述符记录
② 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 :
① 提供对应的 drv_fasync 函数;
② 并在合适的时机发信号。
drv_fasync 函数很简单,调用 fasync_helper 函数就可以,如下:

static struct fasync_struct *button_async;
static int drv_fasync (int fd, struct file *filp, int on)
{
    return fasync_helper (fd, filp, on, &button_async);
}

fasync_helper 函数会分配 、构造一个 fasync_struct 结构体 button_async:

  • 驱动文件的 flag 被设置为 FAYNC 时:
    button_async->fa_file = filp; // filp 表示驱动程序文件,里面含有之前设置的 PID
  • 驱动文件被设置为非 FASYNC 时:
    button_async->fa_file = NULL;
    以后想发送信号时,使用 button_async 作为参数就可以,它里面“可能” 含有 PID 。
    什么时候发信号呢?在本例中,在 GPIO 中断服务程序中发信号。
    怎么发信号呢? kill_fasync (&button_async, SIGIO, POLL_IN);
    ① 第 1 个参数: button_async->fa_file 非空时,可以从中得到 PID ,表示发给哪一个 APP ;
    ② 第 2 个参数表示发什么信号: SIGIO ;
    ③ 第 3 个参数表示为什么发信号: POLL_IN ,有数据可以读了。 (APP 用不到这个参数)

APP 编程

① 编写信号处理函数:

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);

④ 把进程 ID 告诉驱动(实际上只会到达内核层次,不会到达驱动层次):

fcntl(fd, F_SETOWN, getpid());

⑤ 使能驱动的 FASYNC 功能:

flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);

SIGIO 实例

源码目录结构

nvidia@nvidia-desktop:~/sigio$ tree
.
├── driver
│   ├── hello.c
│   └── Makefile
├── Makefile
├── test.c
└── write.c

1 directory, 5 files

驱动程序

hello.c

#include <asm/signal.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kdev_t.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/uaccess.h>

static dev_t dev_num;
struct device *dev;
struct class *cls;
struct fasync_struct *hello_fasync;
struct cdev hello_cdev;
struct class hello_class;

static int hello_open(struct inode *inode, struct file *filep)
{
	printk("hello_open()\n");

	return 0;
}

static int hello_release(struct inode *inode, struct file *filep)
{
	printk("hello_release()\n");

	return 0;
}

#define KMAX_LEN 32
char kbuf[KMAX_LEN] = "\0";
static ssize_t hello_read(struct file *filep, char __user *buf, size_t size, loff_t *pos)
{
	int error;

	size = (size <= KMAX_LEN) ? size : KMAX_LEN;

	if (copy_to_user(buf, kbuf, size)) {
		error = -EFAULT;
		return error;
	}

	return size;
}

static ssize_t hello_write(struct file *filep, const char __user *buf, size_t size, loff_t *pos)
{
	int error;

	size = (size <= KMAX_LEN) ? size : KMAX_LEN;

	memset(kbuf, 0, sizeof(kbuf));
	if (copy_from_user(kbuf, buf, size)) {
		error = -EFAULT;
		return error;
	}

	printk("%s\n", kbuf);
	kill_fasync(&hello_fasync, SIGIO, POLLIN);

	return size;
}

int hello_fasync_func(int fd, struct file *filep, int on)
{
	printk("hello_fasync \n");
	return fasync_helper(fd, filep, on, &hello_fasync);
}

static struct file_operations hello_ops = {
	.open = hello_open,
	.release = hello_release,
	.read = hello_read,
	.write = hello_write,
	.fasync = hello_fasync_func,
};

static int hello_init(void)
{
	int result = -1;

	printk("hello_init \n");

	if (alloc_chrdev_region(&dev_num, 0, 1, "hellodev")) {
		printk("%s: can not get device number.", __func__);

		result = -EPERM;
		goto dev_reg_error;
	}

	cdev_init(&hello_cdev, &hello_ops);
	if ((result = cdev_add(&hello_cdev, dev_num, 1)) != 0) {
		goto cdev_add_error;
	}

	cls = class_create(THIS_MODULE, "hellocls");
	if (IS_ERR(cls)) {
		printk(KERN_ERR "class_create() failed for cls\n");

		result = PTR_ERR(cls);
		goto class_create_error;
	}

	dev = device_create(cls, NULL, dev_num, NULL, "hellodev");
	if (IS_ERR(dev)) {
		printk(KERN_ERR "device_create failed\n");

		result = PTR_ERR(dev);
		goto device_create_error;
	}

	return 0;

device_create_error:
	class_destroy(cls);
class_create_error:
	cdev_del(&hello_cdev);
cdev_add_error:
	unregister_chrdev_region(dev_num, 1);
dev_reg_error:
	return result;
}

static void hello_exit(void)
{
	printk("hello_exit \n");
	device_destroy(cls, dev_num);
	class_destroy(cls);
	cdev_del(&hello_cdev);
	unregister_chrdev_region(dev_num, 1);

	return;
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m = hello.o

all:
	make -C /lib/modules/$(shell uname -r)/build/ M=${PWD} modules
clean:
	make -C /lib/modules/$(shell uname -r)/build/ M=${PWD} clean

APP

test.c

#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

char buff[64] = {0};
int fd;

void func(int signo)
{
	printf("signo= %d\n", signo);
	read(fd, buff, sizeof(buff));
	printf("buff=%s\n", buff);

	return;
}

int main(int argc, char *argv[])
{
	int flage;

	fd = open("/dev/hellodev", O_RDWR);
	if (fd < 0) {
		perror("open fail \n");

		return -1;
	}

	fcntl(fd, F_SETOWN, getpid());
	flage = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flage | FASYNC);
	signal(SIGIO, func);

	while (1)
		;

	close(fd);

	return 0;
}

write.c

#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
	int fd;
	int len;

	if (argc < 2) {
		printf("usage:%s str\n", argv[0]);
		return 0;
	}

	fd = open("/dev/hellodev", O_RDWR);
	if (fd < 0) {
		perror("open fail \n");
		return -1;
	}

	printf("before write\n");
	len = write(fd, argv[1], strlen(argv[1]) + 1);
	printf("after write\n");

	printf("len = %d\n", len);

	close(fd);

	return 0;
}

Makefile

all:
	gcc test.c -o test
	gcc write.c -o write

clean:
	rm test write

编译

驱动

nvidia@nvidia-desktop:~/sigio/driver$ make
make -C /lib/modules/5.10.120-tegra/build/ M=/home/nvidia/sigio/driver modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.120-tegra-ubuntu20.04_aarch64/kernel-5.10'
  CC [M]  /home/nvidia/sigio/driver/hello.o
  MODPOST /home/nvidia/sigio/driver/Module.symvers
  CC [M]  /home/nvidia/sigio/driver/hello.mod.o
  LD [M]  /home/nvidia/sigio/driver/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.120-tegra-ubuntu20.04_aarch64/kernel-5.10'

APP

nvidia@nvidia-desktop:~/sigio$ make
gcc test.c -o test
gcc write.c -o write

运行

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

参考

嵌入式Linux异步通知方式
驱动程序基石:异步通知
Linux内核信号SIGIO使用实例讲解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Li-Yongjun

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

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

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

打赏作者

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

抵扣说明:

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

余额充值