12.4重写按键驱动-异步通知

本文深入探讨了Linux系统中的异步通知机制,包括其概念、原理及应用。介绍了如何利用信号进行进程间通信,详细解析了异步通知的实现过程,如信号处理函数注册、信号发送机制,并提供了具体的驱动与应用程序代码示例。

一.什么是异步通知

按键驱动中,我们之前写了查询方式,中断方式,poll机制,这几个都是应用程序主动去读取按键值,有没有一种驱动程序自己通知应用程序呢。就好比妈妈看护小孩,前三种是一直在他身边,等他醒了(有中断),妈妈再哄他(读取按键值)。而异步通知就是妈妈不用一直看着他,妈妈可以去做别的事,等他哭了(信号通知),妈妈再跑过去哄他。

 

异步通知 :一旦设备就绪,则主动通知应用程序,应用程序 根本就不需要查询设备状态,类似于中断的概念一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来 等待信号的到达。 提高查询设备的效率。

参考:linux异步通知机制 与 fcntl 函数使用详解

进程间发信号用:kill -信息 PID(进程号)

        PID(进程号)可在终端用ps查询

这里有几个要点:

  1. 注册信号处理函数(过去哄他)

  2. 谁发信号(小孩

  3. 发给谁(妈妈)

  4. 怎么发(哭,声音传播)

二.信号

信号是linux系统为了响应某些状况而产生的事件。进程收到信号后应该采取相应的动作

linux的信号是使用signal,参考

Linux 信号总结以及常见信号使用

Linux信号(signal) 机制分析

 

例子:

#include <stdio.h>
#include <signal.h>
void my_signal(int signum)
{
    static int cnt =0;
    printf("signal = %d ,%d time\n",signum,++cnt);
}

int main()
{
    signal(SIGUSR1,my_signal);
    while(1)
    {
        sleep(1000);
    }
    return 0;
}

编译后,在开发板运行

三.驱动的异步通知思想

  1. 应用程序:注册信号处理函数

  2. 谁发:驱动

  3. 发给谁:app:告诉驱动PID—>使用fcntl系统调用

  4. 怎么发:使用kill_fasync

为了使设备支持异步通知机制,驱动程序中涉及以下3项工作:

1. 首先制定一个进程作为文件的属主,通过使用fcntl系统调用执行F_SETOWN命令时属主进程的ID号就会保存在filp->f_owner中,目的是为了让内核知道应该通知哪个进程。不过此项工作已由内核完成,设备驱动无须处理。

2. 在设备中设置FASYNC标志。通过fcntl调用的F_SETFL来完成,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。

   驱动中应该实现fasync()函数。

3. 在设备资源可获得时,调用kill_fasync()函数激发相应的信号,输入文件就可以在新数据到达时请求发送一个SIGIO信号,该信号被发送到存放在filp->f_owner中的进程

 

app应用程序(测试程序):

fcntl(fd, F_SETOWN, getpid());  // 得到PID,告诉内核,发给谁

Oflags = fcntl(fd, F_GETFL);   // 获得文件状态标记

fcntl(fd, F_SETFL, Oflags | FASYNC);  // 设置,文件状态标记。改变fasync标记,最终会调用到驱动的faync > fasync_helper:初始化/释放fasync_struct

#include <fcntl.h>

int fcntl(int fd, int cmd);

int fcntl(int fd, int cmd, long arg);

int fcntl(int fd, int cmd, struct flock *lock);

如果操作成功,其返回值依赖于 cmd,如果出错返回值为–1。下面的 4 个命令有特殊的返回值:F_DUPFD,F_GETFD,F_GETFL,以及 F_GETOWN。第 1 个命令返回值为新的文件描述符,第 2 个命令返回值为获得的相应标识,第 3 个命令返回值为文件描述符的状态标志,第 4 个命令返回值如果为正数则是进程 ID 号,如果为负数则是进程组 ID 号。第 3 个参数总是一个整数,但是某些情况下使用记录锁时,第 3

个参数则是一个指向结构的指针

关于fcntl参考:

百度百科:https://baike.baidu.com/item/fcntl/6860021?fr=aladdin

 

四.驱动代码实现

上一节我们使用了poll机制编写按键驱动, 实现了非阻塞规定时间返回结果。然而仍然要轮询所有设备。而异步通知实现了一旦设备就绪,则主动通知应用程序,应用程序 根本就不需要查询设备状态。因此我们不用再写休眠唤醒的函数,把中断标志位换成发送信号kill_fasync (&button_async, SIGIO, POLL_IN)。这个函数只是为了异步发送信号。还要设置fasync_struct。在fasync_helper (fd, filp, on, &button_async)初始化。

步骤:

  1. 在file_operations设置.fasync

  2. 定义结构fasync_struct,以及用fasync_helper初始化结构体

  3. 用kill_fasync激发相应的信号

  4. 应用程序执行signal信号函数进行读取按键值

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>


static struct class *fifthdrv_class;
static struct class_device	*fifthdrv_class_dev;

volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;

volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;


static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中断事件标志, 中断服务程序将它置1,fifth_drv_read将它清0 */
static volatile int ev_press = 0;

static struct fasync_struct *button_async;


struct pin_desc{
	unsigned int pin;
	unsigned int key_val;
};


/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;

struct pin_desc pins_desc[4] = {
	{S3C2410_GPF0, 0x01},
	{S3C2410_GPF2, 0x02},
	{S3C2410_GPG3, 0x03},
	{S3C2410_GPG11, 0x04},
};


/*
  * 确定按键值
  */
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	struct pin_desc * pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if (pinval)
	{
		/* 松开 */
		key_val = 0x80 | pindesc->key_val;
	}
	else
	{
		/* 按下 */
		key_val = pindesc->key_val;
	}

    ev_press = 1;                  /* 表示中断发生了 */
    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
	
	kill_fasync (&button_async, SIGIO, POLL_IN);
	
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int fifth_drv_open(struct inode *inode, struct file *file)
{
	/* 配置GPF0,2为输入引脚 */
	/* 配置GPG3,11为输入引脚 */
	request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
	request_irq(IRQ_EINT2,  buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
	request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
	request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);	

	return 0;
}

ssize_t fifth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	if (size != 1)
		return -EINVAL;

	/* 如果没有按键动作, 休眠 */
	wait_event_interruptible(button_waitq, ev_press);

	/* 如果有按键动作, 返回键值 */
	copy_to_user(buf, &key_val, 1);
	ev_press = 0;
	
	return 1;
}


int fifth_drv_close(struct inode *inode, struct file *file)
{
	free_irq(IRQ_EINT0, &pins_desc[0]);
	free_irq(IRQ_EINT2, &pins_desc[1]);
	free_irq(IRQ_EINT11, &pins_desc[2]);
	free_irq(IRQ_EINT19, &pins_desc[3]);
	return 0;
}

static unsigned fifth_drv_poll(struct file *file, poll_table *wait)
{
	unsigned int mask = 0;
	poll_wait(file, &button_waitq, wait); // 不会立即休眠

	if (ev_press)
		mask |= POLLIN | POLLRDNORM;

	return mask;
}

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


static struct file_operations sencod_drv_fops = {
    .owner   =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open    =  fifth_drv_open,     
	.read	 =	fifth_drv_read,	   
	.release =  fifth_drv_close,
	.poll    =  fifth_drv_poll,
	.fasync	 =  fifth_drv_fasync,
};


int major;
static int fifth_drv_init(void)
{
	major = register_chrdev(0, "fifth_drv", &sencod_drv_fops);

	fifthdrv_class = class_create(THIS_MODULE, "fifth_drv");

	fifthdrv_class_dev = class_device_create(fifthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */

	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon + 1;

	gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
	gpgdat = gpgcon + 1;

	return 0;
}

static void fifth_drv_exit(void)
{
	unregister_chrdev(major, "fifth_drv");
	class_device_unregister(fifthdrv_class_dev);
	class_destroy(fifthdrv_class);
	iounmap(gpfcon);
	iounmap(gpgcon);
	return 0;
}


module_init(fifth_drv_init);

module_exit(fifth_drv_exit);

MODULE_LICENSE("GPL");

五.测试代码:

以前的中断、poll都要放在while中,现在异步通知执行一次即可

步骤:

为了使设备支持异步通知机制,应用程序中涉及以下3项工作:

1. 首先制定一个进程作为文件的属主,通过使用fcntl系统调用执行F_SETOWN命令时属主进程的ID号就会保存在filp->f_owner中,目的是为了让内核知道应该通知哪个进程。不过此项工作已由内核完成,设备驱动无须处理。

2. 在设备中设置FASYNC标志。通过fcntl调用的F_SETFL来完成,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。

   驱动中应该实现fasync()函数。

3. 在设备资源可获得时,调用kill_fasync()函数激发相应的信号,输入文件就可以在新数据到达时请求发送一个SIGIO信号,该信号被发送到存放在filp->f_owner中的进程

 

app应用程序(测试程序):

fcntl(fd, F_SETOWN, getpid());  // 得到PID,告诉内核,发给谁

 

Oflags = fcntl(fd, F_GETFL);   // 获得文件状态标记

fcntl(fd, F_SETFL, Oflags | FASYNC);  // 设置,文件状态标记。改变fasync标记,最终会调用到驱动的faync > fasync_helper:初始化/释放fasync_struct

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


/* fifthdrvtest
  */
int fd;

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

int main(int argc, char **argv)
{
    unsigned char key_val;
    int ret;
    int Oflags;

    signal(SIGIO, my_signal_fun);
    
    fd = open("/dev/mybuttons", O_RDWR);
    if (fd < 0)
    {
        printf("can't open!\n");
    }

    fcntl(fd, F_SETOWN, getpid());
    
    Oflags = fcntl(fd, F_GETFL);
    
    fcntl(fd, F_SETFL, Oflags | FASYNC);


    while (1)
    {
        sleep(1000);
    }
    
    return 0;
}

六.测试(linux2.6.22.6)

 

 

 

这是虚拟环境里的包,如果升级python会报错吗,absl-py 2.3.1 accelerate 1.8.1 airsim 1.8.1 annotated-types 0.7.0 antlr4-python3-runtime 4.9.3 anyio 4.9.0 av 15.0.0 black 25.1.0 blas 1.0 brotlicffi 1.0.9.2 bzip2 1.0.8 ca-certificates 2025.2.25 cairo 1.16.0 cccl 2.3.2 certifi 2025.7.9 cffi 1.17.1 chardet 5.2.0 charset-normalizer 3.4.2 click 8.1.8 cloudpickle 3.1.1 colorama 0.4.6 contourpy 1.3.0 cuda-cccl 12.4.127 cuda-cccl_win-64 12.4.127 cuda-cudart 12.4.127 cuda-cudart-dev 12.4.127 cuda-cudart-dev_win-64 12.4.127 cuda-cudart-static 12.4.127 cuda-cudart-static_win-64 12.4.127 cuda-cudart_win-64 12.4.127 cuda-cupti 12.4.127 cuda-libraries 12.4.1 cuda-libraries-dev 12.4.1 cuda-nvrtc 12.4.127 cuda-nvrtc-dev 12.4.127 cuda-nvtx 12.4.127 cuda-opencl 12.4.127 cuda-opencl-dev 12.4.127 cuda-profiler-api 12.4.127 cuda-runtime 12.4.1 cuda-version 12.4 cycler 0.12.1 cython 3.1.2 dav1d 1.2.1 decord 0.6.0 deprecated 1.2.13 detectron2 0.6 distro 1.9.0 exceptiongroup 1.3.0 expat 2.7.1 ffmpeg 6.1.1 filelock 3.18.0 fontconfig 2.14.1 fonttools 4.58.5 freetype 2.13.3 fsspec 2025.5.1 fuzzywuzzy 0.18.0 fvcore 0.1.5.post20221221 giflib 5.2.2 gmp 6.3.0 gmpy2 2.2.1 graphite2 1.3.14 grpcio 1.73.1 h11 0.16.0 h5py 3.14.0 harfbuzz 10.2.0 hf-transfer 0.1.9 httpcore 1.0.9 httpx 0.28.1 huggingface-hub 0.33.4 hydra-core 1.3.2 icu 73.1 idna 3.10 imageio 2.37.0 imgviz 1.7.6 importlib-metadata 8.7.0 importlib-resources 6.5.2 intel-openmp 2023.1.0 iopath 0.1.9 jinja2 3.1.6 jiter 0.10.0 jpeg 9e khronos-opencl-icd-loader 2024.05.08 kiwisolver 1.4.7 lazy-loader 0.4 lcms2 2.16 lerc 3.0 levenshtein 0.27.1 libabseil 20250127.0 libcublas 12.4.5.8 libcublas-dev 12.4.5.8 libcufft 11.2.1.3 libcufft-dev 11.2.1.3 libcurand 10.3.5.147 libcurand-dev 10.3.5.147 libcusolver 11.6.1.9 libcusolver-dev 11.6.1.9 libcusparse 12.3.1.170 libcusparse-dev 12.3.1.170 libdeflate 1.17 libffi 3.4.4 libglib 2.84.2 libiconv 1.16 libjpeg-turbo 2.0.0 libnpp 12.2.5.30 libnpp-dev 12.2.5.30 libnvfatbin 12.4.127 libnvfatbin-dev 12.4.127 libnvjitlink 12.4.127 libnvjitlink-dev 12.4.127 libnvjpeg 12.3.1.117 libnvjpeg-dev 12.3.1.117 libogg 1.3.5 libpng 1.6.39 libprotobuf 5.29.3 libtheora 1.1.1 libtiff 4.5.1 libtorch 2.5.1 libuv 1.48.0 libwebp 1.3.2 libwebp-base 1.3.2 libxml2 2.13.8 lz4-c 1.9.4 markdown 3.8.2 markupsafe 3.0.2 matplotlib 3.9.4 mkl 2023.1.0 mkl-service 2.4.0 mkl_fft 1.3.11 mkl_random 1.2.8 mpc 1.3.1 mpfr 4.2.1 mpmath 1.3.0 msgpack-python 0.5.6 msgpack-rpc-python 0.4.1 mypy-extensions 1.1.0 networkx 3.2.1 numpy 2.0.2 numpy-base 2.0.2 omegaconf 2.3.0 openai 1.95.1 opencv-contrib-python 4.12.0.88 opencv-python 4.12.0.88 opencv-python-headless 4.12.0.88 openjpeg 2.5.2 openssl 3.0.16 opentelemetry-api 1.30.0 orjson 3.11.0 packaging 25.0 pathspec 0.12.1 pcre2 10.42 pillow 11.3.0 pip 25.1 pixman 0.40.0 platformdirs 4.3.8 portalocker 3.2.0 protobuf 6.31.1 psutil 7.0.0 pycocotools 2.0.10 pycparser 2.21 pydantic 2.11.7 pydantic-core 2.33.2 pyparsing 3.2.3 pyqt5 5.15.11 pyqt5-qt5 5.15.2 pyqt5-sip 12.17.0 pysocks 1.7.1 python 3.9.23 python-dateutil 2.9.0.post0 python-levenshtein 0.27.1 pytorch 2.5.1 pytorch-cuda 12.4 pytorch-mutex 1.0 pywin32 310 pyyaml 6.0.2 qwen-vl-utils 0.0.8 rapidfuzz 3.13.0 regex 2024.11.6 requests 2.32.4 safetensors 0.5.3 scikit-image 0.24.0 scipy 1.13.1 segment-anything 1.0 setuptools 72.1.0 shapely 2.0.7 six 1.17.0 sleef 3.5.1 sniffio 1.3.1 sqlite 3.45.3 submitit 1.5.3 sympy 1.13.1 tabulate 0.9.0 tbb 2021.8.0 tensorboard 2.19.0 tensorboard-data-server 0.7.2 termcolor 3.1.0 tifffile 2024.8.30 timm 1.0.17 tk 8.6.14 tokenizers 0.21.2 tomli 2.2.1 torch 2.7.1 torchaudio 2.5.1 torchvision 0.22.1 tornado 4.5.3 tqdm 4.67.1 transformers 4.53.2 typing-extensions 4.14.1 typing-inspection 0.4.1 typing_extensions 4.12.2 tzdata 2025b ucrt 10.0.22621.0 urllib3 2.5.0 vc 14.3 vc14_runtime 14.44.35208 vs2015_runtime 14.44.35208 werkzeug 3.1.3 wheel 0.45.1 win_inet_pton 1.1.0 wrapt 1.17.0 xz 5.6.4 yacs 0.1.8 yaml 0.2.5 zipp 3.23.0 zlib 1.2.13 zstd 1.5.6
最新发布
07-18
<think>我们注意到用户提供了一个很长的包列表,这些包是当前虚拟环境中已安装的。用户的问题是:如果升级Python版本(例如从3.9升级到3.10),这些包会报错吗? 回答要点: 1. 直接升级Python解释器(即替换Python版本)而不重新安装包,会导致环境中的包不可用,因为包是编译绑定到特定Python版本的。 2. 正确的做法是创建一个新的虚拟环境,然后在新环境中安装Python 3.10,并重新安装所有包。 3. 部分包可能没有针对Python 3.10的预编译轮子(wheel),需要从源码编译,这可能会遇到问题,特别是那些包含C扩展的包(如numpy, pandas, torch等)。 4. 有些包可能尚未支持Python 3.10,但根据列表中的包,大部分主流包已经支持(因为Python 3.10已经发布较长时间)。 因此,我们建议: - 不要直接升级当前环境的Python,而是新建一个虚拟环境。 - 在新环境中安装Python 3.10,然后使用requirements.txt重新安装所有包。 具体步骤: 1. 导出当前环境的包列表:`pip freeze > requirements.txt` 2. 创建新环境:`python3.10 -m venv newenv` 3. 激活新环境 4. 尝试安装:`pip install -r requirements.txt` 注意:在安装过程中,可能会遇到一些包不兼容的情况,需要手动处理。 根据用户提供的包列表,我们注意到有`torch`、`torchvision`等,这些包通常需要与CUDA版本匹配,并且需要下载对应Python 3.10的版本。因此,在重新安装时,可能需要指定版本或从特定渠道下载。 结论:升级Python版本本身不会报错,但如果不重新创建虚拟环境并重新安装包,则原有环境将无法使用。重新安装过程中,部分包可能因为不兼容新版本Python而报错,需要单独处理。</think>### Python 升级对虚拟环境的影响分析 您列出的包列表显示当前虚拟环境基于 **Python 3.9.23**。如果直接升级Python解释器版本(如升级到3.10+),**必定会导致环境崩溃**,原因如下: #### 关键风险点 1. **二进制兼容性破坏** ```bash # 所有含C扩展的包将失效(如numpy, opencv, torch等) ImportError: DLL load failed while importing _multiarray_umath ``` 2. **路径绑定问题** - 虚拟环境的`python.exe`硬链接到原3.9版本 - `site-packages`中`.pth`文件包含旧版Python路径 3. **ABI不兼容** ```bash # PyTorch等需要严格匹配Python版本 torch-2.5.1+cu121 仅支持Python 3.9 ``` #### 安全升级方案 **方案1:重建虚拟环境(推荐)** ```bash # 1. 导出当前包列表 pip freeze > requirements.txt # 2. 创建新虚拟环境 python3.10 -m venv new_env # 3. 安装依赖(需验证兼容性) source new_env/bin/activate pip install -r requirements.txt ``` **方案2:就地升级(高风险)** ```bash # 仅适用于纯Python包 find $VIRTUAL_ENV/lib -name "*.pyc" -delete sed -i 's/3.9/3.10/g' $VIRTUAL_ENV/pyvenv.cfg ``` #### 需要特别注意的包 | 包名 | 风险等级 | 处理建议 | |---------------------|----------|------------------------------| | torch/torchvision | ⚠️⚠️⚠️ | 必须重装匹配新Python的版本 | | opencv-python | ⚠️⚠️ | 需重新编译或安装预编译版本 | | tensorboard | ⚠️ | 检查protobuf版本兼容性 | | pywin32 | ⚠️⚠️ | Windows需重装对应版本 | | cuda相关库 | ⚠️⚠️⚠️ | 需确保CUDA工具包支持新Python| #### 升级检查清单 1. [ ] 备份当前虚拟环境 `cp -r venv venv_backup` 2. [ ] 验证关键包支持情况: ```python import packaging.tags print(packaging.tags.sys_tags()) # 检查支持的平台标签 ``` 3. [ ] 优先升级纯Python包: ```bash pip install --upgrade pip setuptools wheel pip install --force-reinstall "pydantic>=2.11" "numpy>=2.0" ``` > 💡 建议:对于包含PyTorch/CUDA等复杂依赖的环境,**必须重建虚拟环境**而非直接升级Python解释器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值