【正点原子Linux连载】第十七章 异步通知实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban

第十七章 异步通知实验

在前面使用阻塞或者非阻塞的方式来读取驱动中按键值都是应用程序主动读取的,对于非阻塞方式来说还需要应用程序通过poll函数不断的轮询。最好的方式就是驱动程序能主动向应用程序发出通知,报告自己可以访问,然后应用程序再从驱动程序中读取或写入数据,类似于中断。Linux提供了异步通知这个机制来完成此功能,本章我们就来学习一下异步通知以及如何在驱动中添加异步通知相关处理代码。

17.1 异步通知
17.1.1 异步通知简介
我们首先来回顾一下“中断”,中断是处理器提供的一种异步机制,我们配置好中断以后就可以让处理器去处理其他的事情了,当中断发生以后会触发我们事先设置好的中断服务函数,在中断服务函数中做具体的处理。比如我们以前学习单片机的时候用到的GPIO按键中断,我们通过按键去开关蜂鸣器,采用中断以后处理器就不需要时刻的去查看按键有没有被按下,因为按键按下以后会自动触发中断。同样的,Linux应用程序可以通过阻塞或者非阻塞这两种方式来访问驱动设备,通过阻塞方式访问的话应用程序会处于休眠态,等待驱动设备可以使用。非阻塞方式的话会通过poll函数来不断的轮询,查看驱动设备文件是否可以使用。这两种方式都需要应用程序主动的去查询设备的使用情况,如果能提供一种类似中断的机制,当驱动程序可以访问的时候主动告诉应用程序那就最好了。
“信号”为此应运而生,信号类似于我们硬件上使用的“中断”,只不过信号是软件层次上的。算是在软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。整个过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,在整个处理过程中应用程序并没有去查询驱动设备是否可以访问,一切都是由驱动设备自己告诉给应用程序的。
阻塞、非阻塞、异步通知,这三种是针对不同的场合提出来的不同的解决方法,没有优劣之分,在实际的工作和学习中,根据自己的实际需求选择合适的处理方法即可。
异步通知的核心就是信号,在arch/xtensa/include/uapi/asm/signal.h文件中定义了Linux所支持的所有信号,这些信号如下所示:
示例代码17.1.1.1 Linux信号

18 #define SIGHUP    		1  		/* 终端挂起或控制进程终止  			*/
19 #define SIGINT        	2  		/* 终端中断(Ctrl+C组合键)    		*/
20 #define SIGQUIT       	3  		/* 终端退出(Ctrl+\组合键)    		*/
21 #define SIGILL        	4  		/* 非法指令                 			*/
22 #define SIGTRAP       	5  		/* debug使用,有断点指令产生		*/
23 #define SIGABRT       	6  		/* 由abort(3)发出的退出指令 		*/
24 #define SIGIOT        	6  		/* IOT指令                    		*/
25 #define SIGBUS        	7  		/* 总线错误                 			*/
26 #define SIGFPE        	8  		/* 浮点运算错误           			*/
27 #define SIGKILL       	9  		/* 杀死、终止进程            		*/
28 #define SIGUSR1      	10  	/* 用户自定义信号1           		*/
29 #define SIGSEGV      	11  	/* 段违例(无效的内存段)    			*/
30 #define SIGUSR2      	12  	/* 用户自定义信号2           		*/
31 #define SIGPIPE      	13  	/* 向非读管道写入数据      			*/
32 #define SIGALRM      	14  	/* 闹钟                   			*/
33 #define SIGTERM      	15 	 	/* 软件终止                 			*/
34 #define SIGSTKFLT    	16  	/* 栈异常                    		*/
35 #define SIGCHLD      	17  	/* 子进程结束              			*/
36 #define SIGCONT      	18  	/* 进程继续                 			*/
37 #define SIGSTOP      	19  	/* 停止进程的执行,只是暂停 			*/
38 #define SIGTSTP      	20  	/* 停止进程的运行(Ctrl+Z组合键) 	*/
39 #define SIGTTIN      	21  	/* 后台进程需要从终端读取数据		*/
40 #define SIGTTOU      	22  	/* 后台进程需要向终端写数据 		*/
41 #define SIGURG       	23  	/* 有"紧急"数据            		*/
42 #define SIGXCPU      	24  	/* 超过CPU资源限制            	*/
43 #define SIGXFSZ      	25  	/* 文件大小超额           		*/
44 #define SIGVTALRM    	26  	/* 虚拟时钟信号           		*/
45 #define SIGPROF      	27 	 	/* 时钟信号描述           		*/
46 #define SIGWINCH     	28		/* 窗口大小改变           		*/
47 #define SIGIO        	29 		/* 可以进行输入/输出操作  		*/
48 #define SIGPOLL      	SIGIO   
49 /* #define SIGLOS    29 */
50 #define SIGPWR       	30 		/* 断点重启                 		*/
51 #define SIGSYS       	31  	/* 非法的系统调用            	*/
52 #define  SIGUNUSED   	31  	/* 未使用信号              		*/

在示例代码17.1.1.1中的这些信号中,除了SIGKILL(9)和SIGSTOP(19)这两个信号不能被忽略外,其他的信号都可以忽略。这些信号就相当于中断号,不同的中断号代表了不同的中断,不同的中断所做的处理不同,因此,驱动程序可以通过向应用程序发送不同的信号来实现不同的功能。
我们使用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用signal函数来设置指定信号的处理函数,signal函数原型如下所示:
sighandler_t signal(int signum, sighandler_t handler)
函数参数和返回值含义如下:
signum:要设置处理函数的信号。
handler:信号的处理函数。
返回值:设置成功的话返回信号的前一个处理函数,设置失败的话返回SIG_ERR。
信号处理函数原型如下所示:
typedef void (*sighandler_t)(int)
我们前面讲解的使用“kill -9 PID”杀死指定进程的方法就是向指定的进程(PID)发送SIGKILL这个信号。当按下键盘上的CTRL+C组合键以后会向当前正在占用终端的应用程序发出SIGINT信号,SIGINT信号默认的动作是关闭当前应用程序。这里我们修改一下SIGINT信号的默认处理函数,当按下CTRL+C组合键以后先在终端上打印出“SIGINT signal!”这行字符串,然后再关闭当前应用程序。新建signaltest.c文件,然后输入如下所示内容:
示例代码17.1.1.2 信号测试

1  #include <stdlib.h>
2  #include <stdio.h>
3  #include <signal.h>
4  
5  void sigint_handler(int num)
6  {
7      printf("\r\nSIGINT signal!\r\n");
8      exit(0);
9  }
10 
11 int main(void)
12 {
13     signal(SIGINT, sigint_handler);
14     while(1);
15     return 0;
16 }

在示例代码17.1.1.2中我们设置SIGINT信号的处理函数为sigint_handler,当按下CTRL+C向signaltest发送SIGINT信号以后sigint_handler函数就会执行,此函数先输出一行“SIGINT signal!”字符串,然后调用exit函数关闭signaltest应用程序。
使用如下命令编译signaltest.c:
gcc signaltest.c -o signaltest
然后输入“./signaltest”命令打开signaltest这个应用程序,然后按下键盘上的CTRL+C组合键,结果如图17.1.1.1所示:
在这里插入图片描述

图17.1.1.1 signaltest软件运行结果
从图17.1.1.1可以看出,当按下CTRL+C组合键以后sigint_handler这个SIGINT信号处理函数执行了,并且输出了“SIGINT signal!”这行字符串。
17.1.2 驱动中的信号处理
1、fasync_struct结构体
首先我们需要在驱动程序中定义一个fasync_struct结构体指针变量,fasync_struct结构体内容如下:
示例代码17.1.2.1 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_struct结构体指针变量定义到设备结构体中,比如在上一章节的key_dev结构体中添加一个fasync_struct结构体指针变量,结果如下所示:
示例代码17.1.2.2 在设备结构体中添加fasync_struct类型变量指针

1  struct key_dev{
2  		dev_t 			devid;          
3   	struct cdev 	cdev;     
4   	struct class 	*class;   
......
12  	struct fasync_struct *async_queue;  /* fasync_struct结构体 */
13 };
第12行就是在imx6uirq_dev中添加了一个fasync_struct结构体指针变量。

2、fasync函数
如果要使用异步通知,需要在设备驱动中实现file_operations操作集中的fasync函数,此函数格式如下所示:
int (*fasync) (int fd, struct file *filp, int on)
fasync函数里面一般通过调用fasync_helper函数来初始化前面定义的fasync_struct结构体指针,fasync_helper函数原型如下:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
fasync_helper函数的前三个参数就是fasync函数的那三个参数,第四个参数就是要初始化的fasync_struct结构体指针变量。当应用程序通过“fcntl(fd, F_SETFL, flags | FASYNC)”改变fasync标记的时候,驱动程序file_operations操作集中的fasync函数就会执行。
驱动程序中的fasync函数参考示例如下:
示例代码17.1.2.3 驱动中fasync函数参考示例

1  struct xxx_dev { 
2   	......
3   	struct fasync_struct *async_queue;  /* 异步相关结构体 */ 
4  };
5 
6  static int xxx_fasync(int fd, struct file *filp, int on)
7  {
8   	struct xxx_dev *dev = (xxx_dev)filp->private_data;
9  
10  	if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
11      	return -EIO;
12  	return 0;
13 }
14
15 static struct file_operations xxx_ops = {
16  	......
17  	.fasync = xxx_fasync,
18 	 	......
19 };
在关闭驱动文件的时候需要在file_operations操作集中的release函数中释放fasync_struct,fasync_struct的释放函数同样为fasync_helper,release函数参数参考实例如下:

示例代码17.1.2.4 释放fasync_struct参考示例

1 static int xxx_release(struct inode *inode, struct file *filp)
2 {
3   	return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
4 }
5 
6 static struct file_operations xxx_ops = {
7  	 ......
8   	.release = xxx_release,
9 };
第3行通过调用示例代码17.1.2.3中的xxx_fasync函数来完成fasync_struct的释放工作,但是,其最终还是通过fasync_helper函数完成释放工作。

1、kill_fasync函数
当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”。kill_fasync函数负责发送指定的信号,kill_fasync函数原型如下所示:
void kill_fasync(struct fasync_struct *fp, int sig, int band)
函数参数和返回值含义如下:
fp:要操作的fasync_struct。
sig:要发送的信号。
band:可读时设置为POLL_IN,可写时设置为POLL_OUT。
返回值:无。
17.1.3 应用程序对异步通知的处理
应用程序对异步通知的处理包括以下三步:
1、注册信号处理函数
应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用signal函数来设置信号的处理函数。前面已经详细的讲过了,这里就不细讲了。
2、将本应用程序的进程号告诉给内核
使用fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。
3、开启异步通知
使用如下两行程序开启异步通知:
flags = fcntl(fd, F_GETFL); /
获取当前的进程状态 /
fcntl(fd, F_SETFL, flags | FASYNC); /
开启当前进程异步通知功能 */
重点就是通过fcntl函数设置进程状态为FASYNC,经过这一步,驱动程序中的fasync函数就会执行。
17.2 硬件原理图分析
本章实验硬件原理图参考13.2小节即可。
17.3 实验程序编写
本实验对应的例程路径为:开发板光盘01、程序源码Linux驱动例程15_asyncnoti。
本章实验我们在上一章实验“14_noblockio”的基础上完成,在其中加入异步通知相关内容即可,当按键按下以后驱动程序向应用程序发送SIGIO信号,应用程序获取到SIGIO信号以后读取并且打印出按键值。
17.3.1 修改设备树文件
因为是在实验“14_noblockio”的基础上完成的,因此不需要修改设备树。
17.3.2 程序编写
新建名为“15_asyncnoti”的文件夹,然后在15_asyncnoti文件夹里面创建vscode工程,工作区命名为“asyncnoti”。将“14_noblockio”实验中的noblockio.c复制到15_asyncnoti文件夹中,并重命名为asyncnoti.c。接下来我们就修改asyncnoti.c这个文件,在其中添加异步通知关的代码,完成以后的asyncnoti.c内容如下所示(因为是在上一章实验的noblockio.c文件的基础上修改的,因为了减少篇幅,下面的代码有省略):
示例代码17.3.2.1 asyncnoti.c文件代码段

32  #include <linux/fcntl.h>
33 
34  #define KEY_CNT         1       	/* 设备号个数  	*/
35  #define KEY_NAME        "key"   	/* 名字       	*/
36 
37  /* 定义按键状态 */
38  enum key_status {
39      KEY_PRESS = 0,      			/* 按键按下 		*/ 
40      KEY_RELEASE,        			/* 按键松开  	*/ 
41      KEY_KEEP,           			/* 按键状态保持	*/ 
42  };
43 
44  /* key设备结构体 */
45  struct key_dev{
46      dev_t devid;            		/* 设备号     	*/
47      struct cdev cdev;       		/* cdev     	*/
48      struct class *class;    		/* 类      		*/
49      struct device *device;  		/* 设备    		*/
50      struct device_node  *nd; 	/* 设备节点 		*/
51      int key_gpio;           		/* key所使用的GPIO编号 */
52      struct timer_list timer;    	/* 按键值     	*/
53      int irq_num;            		/* 中断号      	*/  
54      atomic_t status;        		/* 按键状态 		*/
55      wait_queue_head_t r_wait;   	/* 读等待队列头	*/
56      struct fasync_struct *async_queue;  /* fasync_struct结构体 */
57  };
......
153 static void key_timer_function(struct timer_list *arg)
154 {
155     static int last_val = 1;
156     int current_val;
157
158     /* 读取按键值并判断按键当前状态 */
159     current_val = gpio_get_value(key.key_gpio);
160     if (0 == current_val && last_val){
161         atomic_set(&key.status, KEY_PRESS); 		/* 按下 */ 
162         wake_up_interruptible(&key.r_wait); 		/* 唤醒 */ 
163         if(key.async_queue)
164             kill_fasync(&key.async_queue, SIGIO, POLL_IN);
165     }
166     else if (1 == current_val && !last_val) {
167         atomic_set(&key.status, KEY_RELEASE);   	/* 松开 */ 
168         wake_up_interruptible(&key.r_wait); 		/* 唤醒 */ 
169         if(key.async_queue)
170             kill_fasync(&key.async_queue, SIGIO, POLL_IN);
171     }
172     else
173         atomic_set(&key.status, KEY_KEEP);        	/* 状态保持 */ 
174
175     last_val = current_val;
176 }
......
221  /*
222  * @description 	: fasync函数,用于处理异步通知
223  * @param – fd  	: 文件描述符
224  * @param – filp 	: 要打开的设备文件(文件描述符)
225  * @param – on   	: 模式
226  * @return       	: 负数表示函数执行失败
227  */
228 static int key_fasync(int fd, struct file *filp, int on)
229 {
230     return fasync_helper(fd, filp, on, &key.async_queue);
231 }
......
246 /*
247  * @description  	: 关闭/释放设备
248  * @param - filp 	: 要关闭的设备文件(文件描述符)
249  * @return        	: 0 成功;其他 失败
250  */
251 static int key_release(struct inode *inode, struct file *filp)
252 {
253     return key_fasync(-1, filp, 0);
254 }
......
274 /* 设备操作函数 */
275 static struct file_operations key_fops = {
276     .owner = THIS_MODULE,
277     .open = key_open,
278     .read = key_read,
279     .write = key_write,
280     .release =  key_release,
281     .poll = key_poll,
282     .fasync = key_fasync,
283 };
......
373 module_init(mykey_init);
374 module_exit(mykey_exit);
375 MODULE_LICENSE("GPL");
376 MODULE_AUTHOR("ALIENTEK");
377 MODULE_INFO(intree, "Y");

第32行,添加fcntl.h头文件,因为要用到相关的API函数。
第56行,在设备结构体key_dev中添加fasync_struct指针变量。
第153~176行,在key_timer_function函数中,当按键按下或松开动作发生时调用kill_fasync函数向应用程序发送SIGIO信号,通知应用程序按键数据可以进行读取了。
第228~231行,设备操作函数集file_operations结构体中的fasync函数key_fasync,该函数中直接调用fasync_helper函数进行相关处理。
第253行,在key_release函数中也调用key_fasync函数释放fasync_struct指针变量。
第282行,将key_fasync函数绑定到key_fops变量的fasync函数指针中。
17.3.3 编写测试APP
测试APP要实现的内容很简单,设置SIGIO信号的处理函数为sigio_signal_func,当驱动程序向应用程序发送SIGIO信号以后sigio_signal_func函数就会执行。sigio_signal_func函数内容很简单,就是通过read函数读取按键值。新建名为asyncnotiApp.c的文件,然后输入如下所示内容:
示例代码17.3.3.2 asyncnotiApp.c文件代码段

13  #include <stdio.h>
14  #include <unistd.h>
15  #include <sys/types.h>
16  #include <sys/stat.h>
17  #include <fcntl.h>
18  #include <stdlib.h>
19  #include <string.h>
20  #include <signal.h>
21
22  static int fd;
23
24  /*
25   * SIGIO信号处理函数
26   * @param – signum  	: 信号值
27   * @return            	: 无
28   */
29  static void sigio_signal_func(int signum)
30  {
31      unsigned int key_val = 0;
32
33      read(fd, &key_val, sizeof(unsigned int));
34      if (0 == key_val)
35          printf("Key Press\n");
36      else if (1 == key_val)
37          printf("Key Release\n");
38  }
39
40
41  /*
42   * @description 	: main主程序
43   * @param – argc 	: argv数组元素个数
44   * @param – argv 	: 具体参数
45   * @return       	: 0 成功;其他 失败
46   */
47  int main(int argc, char *argv[])
48  {
49      int flags = 0;
50
51      /* 判断传参个数是否正确 */
52      if(2 != argc) {
53          printf("Usage:\n"
54                 "\t./asyncKeyApp /dev/key\n"
55                );
56          return -1;
57      }
58
59      /* 打开设备 */
60      fd = open(argv[1], O_RDONLY | O_NONBLOCK);
61      if(0 > fd) {
62          printf("ERROR: %s file open failed!\n", argv[1]);
63          return -1;
64      }
65
66      /* 设置信号SIGIO的处理函数 */
67      signal(SIGIO, sigio_signal_func);
68      fcntl(fd, F_SETOWN, getpid()); 		/* 将当前进程的进程号告诉给内核 */ 
69      flags = fcntl(fd, F_GETFD);        	/*获取当前的进程状态  */ 
70      fcntl(fd, F_SETFL, flags | FASYNC);/* 设置进程启用异步通知功能 */ 
71
72
73      /* 循环轮询读取按键数据 */
74      for ( ; ; ) {
75
76          sleep(2);
77      }
78
79      /* 关闭设备 */
80      close(fd);
81      return 0;
82  }

第29~38行,sigio_signal_func函数,SIGIO信号的处理函数,当驱动程序有效按键按下以后就会发送SIGIO信号,此函数就会执行。此函数通过read函数读取按键状态数据,然后通过printf函数打印在终端上。
第67行,通过signal函数设置SIGIO信号的处理函数为sigio_signal_func。
第68~70行,设置当前进程的状态,开启异步通知的功能。
第74~77行,for循环,等待信号产生。
17.4 运行测试
17.4.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第五章实验基本一样,只是将obj-m变量的值改为asyncnoti.o,Makefile内容如下所示:
示例代码17.4.1.1 Makefile文件

1  KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
...... 
4  obj-m := asyncnoti.o
......
11 clean:
12  $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第4行,设置obj-m变量的值为asyncnoti.o。
输入如下命令编译出驱动模块文件:

make ARCH=arm64 //ARCH=arm64必须指定,否则编译会失败
编译成功以后就会生成一个名为“asyncnoti.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试asyncnotiApp.c这个测试程序:
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc asyncnotiApp.c -o asyncnotiApp
编译成功以后就会生成asyncnotiApp这个应用程序。
17.4.2 运行测试
在Ubuntu中将上一小节编译出来的blockio.ko和blockioApp这两个文件通过adb命令发送到开发板的/lib/modules/4.19.232目录下,命令如下:
adb push asyncnoti.ko asyncnotiApp /lib/modules/4.19.232
发送成功以后进入到开发板目录lib/modules/4.19.232中,输入如下命令加载asyncnoti.ko驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe asyncnoti //加载驱动
驱动加载成功以后使用如下命令来测试中断:
./asyncnotiApp /dev/key
使用杜邦线将图13.2.1中GPIO3_C5这个IO接到开发板的3.3V电压上,模拟按键被按下,如图17.4.2.1所示:
在这里插入图片描述

图17.4.2.1 读取到的按键值
从图17.4.2.1可以看出,捕获到SIGIO信号,并且按键值获取成功,大家可以自行以后台模式运行asyncnotiApp,查看一下这个应用程序的CPU使用率。如果要卸载驱动的话输入如下命令即可:
rmmod asyncnoti.ko

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/765082.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java发送请求-get源码

发送请求 配置依赖-pom.xml Welcome! - The Apache HTTP Server Project 官网解释这个源码httpClient是执行httpget和httppost 步骤&#xff1a; 查看httpClient源码&#xff0c;源码和方法都没有有用的解释 查看CloseableHttpClient源码类 这个抽象类实现2个接口&#xf…

docker 哲学 - 网络桥接器、容器网络接口 、容器间的通信方式

1、解释 docker0 veth eth 2、vethXX 和 ethXX 是肯定一一对应吗 比如 eth1 对应 veth1 3、如果 A容器使用 默认创建方式 。定义他内部网络为 eth0&#xff0c;容器B使用 --network 连上 已创建的网络 172.89.2.1 。此时假设 B的 ip是 172.89.2.2 &#xff0c;容器网络接口是 e…

2024年企业级通用人工智能的关键技术趋势

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

腾讯三面被问到有没有参加过CTF_我反手就是一套军体拳打得面试官哑口无言!

目录 ​ 前言&#xff1a; 正文&#xff1a; 什么是CTF&#xff1f; 什么是PWN? 为什么要学CTF&#xff1f; CTF竞赛模式&#xff1a; CTF各大题型简介&#xff1a; 学之前的思考&#xff1a;分析赛题情况 常规做法 CTF比赛需要的知识储备 CTF比赛的神器&#xff…

51单片机中断信号的种类及应用场景

在嵌入式系统中&#xff0c;中断是一种重要的事件处理机制&#xff0c;它可以在程序执行的任何时候暂停当前任务&#xff0c;转而执行与之相关的特殊任务或事件。51单片机作为一种常见的微控制器&#xff0c;其中断功能在各种应用中起着关键作用。然而&#xff0c;对于初学者和…

Jmeter-基础元件使用(二)-属性及对数据库简单操作

一、Jmeter属性 当我们想要在不同线程组中使用某变量&#xff0c;就需要使用属&#xff0c;此时Jmeter属性的设置需要函数来进行set和get操作 1.创建set函数 2.然后采用Beanshell取样器进行函数执行 3.调用全局变量pro_id 4.将上面生成的函数字符串粘贴到另一个线程组即可…

二、阅读器的开发(初始)-- 2、阅读器开发

1、epubjs核心工作原理 1.1 epubjs的核心工作原理解析 epub电子书&#xff0c;会通过epubjs去实例化一个Book对象&#xff0c;Book对象会对电子书进行解析。Book对象可以通过renderTo方法去生成一个Rendition对象&#xff0c;Rendition主要负责电子书的渲染&#xff0c;通过R…

PointNet++论文复现(一)【PontNet网络模型代码详解 - 分类部分】

PontNet网络模型代码详解 - 分类部分 专栏持续更新中!关注博主查看后续部分! 分类模型的训练: ## e.g., pointnet2_ssg without normal features python train_classification.py --model pointnet2_cls_ssg --log_dir pointnet2_cls_ssg python test_classification.py…

【C#】C#窗体应用修改窗体的标题和图标

修改窗体顶部的标题和图表&#xff0c;如果不修改则会使用默认的图标&#xff0c;标题默认为Form1&#xff0c;如第一张图&#xff0c;这时候如果想换成和系统有关的内容&#xff0c;如第二张图&#xff0c;可以使用下面的方法进行修改&#xff0c;修改后打开该软件任务栏显示的…

学习笔记Day14:Linux下软件安装

软件安装 Anaconda 所有语言的包(package)、依赖(dependency)和环境(environment)管理器&#xff0c;类似应用商店 Conda < Miniconda < Anaconda&#xff08;有交互界面&#xff09; Linux下Miniconda即可 安装Miniconda 搜索北外/清华miniconda镜像网站&#xff…

echarts图表动态监听dataZoom滑动,控制柱条的宽度以及数值的显示隐藏

当数值过多时&#xff0c;显示所有柱条看着会很凌乱且文字会挤在一起&#xff0c;于是就需要监听datazoom的滑动&#xff0c;拿到对应的阈值后做出相应的配置。 “dataZoom” 事件通常用于响应用户对图表进行数据缩放的操作。 这里是datazoom官网api地址&#xff1a;点击跳转至…

服务器端(Debian 12)配置jupyter与R 语言的融合

融合前&#xff1a; 服务器端Debian 12,域名&#xff1a;www.leyuxy.online 1.安装r-base #apt install r-base 2.进入R并安装IRkernel #R >install.packages(“IRkernel”) 3.通过jupyter notebook的Terminal执行&#xff1a; R >IRkernel::installspec() 报错 解决…

DFS基础——迷宫

迷宫 配套视频讲解 关于dfs和bfs的区别讲解。 对于上图&#xff0c;假设我们要找从1到5的最短路&#xff0c;那么我们用dfs去找&#xff0c;并且按照编号从大到小的顺序去找&#xff0c;首先找到的路径如下&#xff0c; 从节点1出发&#xff0c;我们发现节点2可以走&#xff…

在Linux上运行JMeter(非界面)

参考&#xff1a; 查看文件类型&#xff1a;https://www.linuxprobe.com/files-tehre-fangfa.html 华为云平台 配置&#xff1a;jdk环境、jmeter环境 jmeter配置&#xff08;在/etc/profile文件中&#xff09;&#xff1a; export JMETER_HOME/path/to/jmeter/installati…

嵌入式学习41-数据结构2

今天学习了链表的增删改查 &#xff08;暂定&#xff01;&#xff01;后续再补内容&#xff09; 高内聚 &#xff1a;一个函数只实现一个功能 …

Docker 镜像仓库

目录 1、搭建私有 registry 服务端创建镜像仓库 客户端推送镜像 镜像导入导出 2、Nginx 代理 registry 仓库 SSL 证书 & https 协议 SSL证书 https协议 SSL 的验证流程 客户端安装 Nginx 使用 openssl 生成CA根证书和根证书key 创建 Nginx 服务证书 配置启动 N…

Airgorah:一款功能强大的WiFi安全审计工具

关于Airgorah Airgorah是一款功能强大的WiFi安全审计工具&#xff0c;该工具可以轻松发现和识别连接到无线接入点的客户端&#xff0c;并对特定的客户端执行身份验证攻击测试&#xff0c;捕捉WPA握手包&#xff0c;并尝试破解接入点的密码。在该工具的帮助下&#xff0c;广大研…

在Ubuntu上使用Script命令捕获命令与其输出

在Ubuntu上使用Script命令捕获命令与其输出 起初&#xff0c;是为了记录软件的安装过程&#xff0c;就在想有没有简单高效的记录方法&#xff0c;之后就找到了script命令。 使用 script命令&#xff0c;可以很容易地记录下你在终端里所有的操作与输出&#xff0c;非常适合用来…

是时候来唠一唠synchronized关键字了,Java多线程的必问考点!

写在开头 在之前的博文中&#xff0c;我们介绍了volatile关键字&#xff0c;Java中的锁以及锁的分类&#xff0c;今天我们花5分钟时间&#xff0c;一起学习一下另一个关键字&#xff1a;synchronized。 synchronized是什么&#xff1f; 首先synchronized是Java中的一个关键字…

Tensorflow 2.0 常见函数用法(一)

文章目录 0. 基础用法1. tf.cast2. tf.keras.layers.Dense3. tf.variable_scope4. tf.squeeze5. tf.math.multiply 0. 基础用法 Tensorflow 的用法不定期更新遇到的一些用法&#xff0c;之前已经包含了基础用法参考这里 &#xff0c;具体包含如下图的方法&#xff1a; 本文介…