【正点原子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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码+视频,R语言logistic回归交互项(交互作用)的可视化分析

交互作用效应(p for Interaction)在SCI文章中可以算是一个必杀技&#xff0c;几乎在高分的SCI中必出现&#xff0c;因为把人群分为亚组后再进行统计可以增强文章结果的可靠性&#xff0c;不仅如此&#xff0c;交互作用还可以使用来进行数据挖掘。在既往文章中&#xff0c;我们已…

MySQL:数据库基础

文章目录 什么是数据库数据库的认识登陆数据库理解数据库数据库的相关概念数据库的工作方式 数据库的基本使用服务器/数据库/表MySQL架构 本篇开始进行关于MySQL的学习&#xff0c;首先要对于数据库有一个基本的认识 什么是数据库 数据库主要是用来管理文件的&#xff0c;那此…

化工企业能源在线监测管理系统,智能节能助力生产

化工企业能源消耗量极大&#xff0c;其节能的空间也相对较大&#xff0c;所以需要控制能耗强度&#xff0c;保持更高的能源利用率。 化工企业能源消耗现状 1、能源管理方面 计量能源消耗时&#xff0c;计量器具存在问题&#xff0c;未能对能耗情况实施完全计量&#xff0c;有…

苍穹外卖-day03

1. 公共字段自动填充 1.1 问题分析 业务表中的公共字段&#xff1a; 序号字段名含义数据类型1create_time创建时间datetime2create_user创建人idbigint3update_time修改时间datetime4update_user修改人idbigint 而针对于这些字段&#xff0c;我们的赋值方式为&#xff1a; 在新…

分布式Raft原理详解,从不同角色视角分析相关状态

分布式Raft原理详解&#xff0c;从不同角色视角分析相关状态 1. CAP定理2.Raft 要解决的问题3. Raft的核心逻辑3.1. Raft的核心逻辑2.1. 复制状态机2.2. 任期 Term2.3. 任期的意义&#xff1a;逻辑时钟2.4 选举定时器 3. Leader选举逻辑4. 从节点视角查看Leader选举4.1. Follow…

MATLAB中的数学建模:基础知识、实例与方法论

前言 在当今科技高速发展的时代&#xff0c;数学建模成为了解析复杂世界的关键工具&#xff0c;而MATLAB作为一种专业的科学计算软件&#xff0c;为我们提供了强大的数学建模平台。MATLAB不仅仅是Matrix Laboratory的简称&#xff0c;更是一个集数值分析、矩阵计算、算法开发和…

IP SSL证书注册流程

使用IP地址申请SSL证书&#xff0c;需要用公网IP地址申请&#xff0c;申请之前确保直接的IP地址可以开放80或者443端口两者选择1个就好&#xff0c;端口不需要一直开放&#xff0c;只要认证的几分钟内开放就可以了&#xff0c;然后IP地址根目录可以上传txt文件。 IP SSL证书认…

【蓝桥杯嵌入式】四、各种外设驱动(十一)ADC(1):软件触发与中断触发方式

温馨提示&#xff1a;本文不会重复之前提到的内容&#xff0c;如需查看&#xff0c;请参考附录 【蓝桥杯嵌入式】附录 目录 重点提炼&#xff1a; 一、需求分析 1、需要的外设资源分析&#xff1a; 2、外设具体分析&#xff1a; 比赛时ADC可能需要配置的部分&#xff1a;…

排序算法记录(冒泡+快排+归并)

文章目录 前言冒泡排序快速排序归并排序 前言 冒泡 快排 归并&#xff0c;这三种排序算法太过经典&#xff0c;但又很容易忘了。虽然一开始接触雀氏这些算法雀氏有些头大&#xff0c;但时间长了也还好。主要是回忆这些算法干了啥很耗时间。 如果在笔试时要写一个o(nlogn)的…

手机网页视频批量提取工具可导出视频分享链接|爬虫采集下载软件

解放你的抖音视频管理——全新抖音批量下载工具震撼上线&#xff01; 在这个信息爆炸的时代&#xff0c;如何高效地获取、管理和分享视频内容成为了许多用户的迫切需求。为了解决这一难题&#xff0c;我们研发了全新的视频批量下载工具&#xff0c;让你轻松畅享海量音视频资源。…

免费的本地图像无损放大工具upscayl,支持六种模型

文章目录 upscayl其他模型其他设置 upscayl upscayl是一款免费的图像无损放大软件&#xff0c;scayl应该就是scale&#xff0c;不知道是哪国语言。进入官网后可直接下载&#xff0c;支持Windows, Linux, MaxOS等主流平台&#xff0c;对于Windows而言&#xff0c;还提供了exe和…

单相桥式全控整流电路

1仿真目的 通过对单相桥式全控整流电路的仿真研究&#xff0c;分析电路带电阻负载与阻感负载的不同工作情况。研究对电路的影响 2仿真原理 2.1单相桥式 如图所示为单相桥式全控电路的框图&#xff0c;设负载为电阻负载。在桥式逆变电路中&#xff0c;桥臂的上下两个开关器件…

teamcenter 无法打开数据集,未找到兼容的工具

原因 teamcenter 图片无法打开看 解决 修改注册表&#xff1a; 注册表位置&#xff1a;计算机\HKEY_CLASSES_ROOT\jpegfile\shell\open\command 注册表的值&#xff1a;“%systemroot%\system32\mspaint.exe” “%1”

BetterDisplay Pro for Mac(显示器校准软件) v2.0.11激活版

BetterDisplay Pro是一款由waydabber开发的Mac平台上的显示器校准软件&#xff0c;可以帮助用户调整显示器的颜色和亮度&#xff0c;以获得更加真实、清晰和舒适的视觉体验。 软件下载&#xff1a;BetterDisplay Pro for Mac v2.0.11激活版 以下是BetterDisplay Pro的主要特点&…

【Redis】哨兵机制

1 &#x1f351;基本概念&#x1f351; 由于对 Redis 的许多概念都有不同的名词解释&#xff0c;所以在介绍 Redis Sentinel 之前&#xff0c;先对⼏个名词概念进⾏必要的说明。 名词逻辑结构物理结构主节点Redis 主服务⼀个独⽴的 redis-server 进程从节点Redis 从服务⼀个独…

浅析ArcGis中的软件——ArcMap、ArcScene、 ArcGlobe、ArcCatalog

为什么要写这么一篇介绍ArcGis的文章呢&#xff1f;因为大部分人也包括ArcGisdada&#xff0c;在使用ArcMap应用程序创建工程时总以为我们就是使用了ArcGis这个软件的所有。其实不然&#xff0c;在后期的接触和使用中慢慢发现原来ArcMap只是ArcGis这个综合平台的一部分&#xf…

fifo ip核 ————读写时钟同步

1.原理 timescale 1ns/1ns module tb_fifo();reg sys_clk ; reg sys_rst_n ; reg [7:0] pi_data ; reg rd_req ; reg wr_req ; reg [2:0] cnt;wire empty ; wire full ; wire [7:0] po_data ; wire [7:0] usedw ;initial begins…

《大模型面试宝典》(2024版) 正式发布!

2022 年11月底&#xff0c;OpenAI 正式推出 ChatGPT &#xff0c;不到两个月的时间&#xff0c;月活用户就突破1亿&#xff0c;成为史上增长最快的消费者应用。 目前国内已发布的大模型超过200个&#xff0c;大模型的出现彻底改变了我们的生活和学习方式。 现在只要你想从事 A…

结构体变量的引用、结构体变量的初始化、结构体数组

一、 结构体变量的引用 在定义了结构体变量以后&#xff0c;当然可以引用这个变量。但应遵守以下规则: 不能将一个结构体变量作为一个整体进行输入和输出。例如,已定义studentl和 student2为结构体变量并且它们已有值。不能这样引用: printf ("%d,%s,%c,%d,%f,%s\n&quo…

idea报错Terminated with exit code 1

今天学项目的时候&#xff0c;中途打开一个新的项目&#xff0c;pom.xml文件一直在下载依赖&#xff0c;下载了很久都没有下载成功&#xff0c;检查自己的Maven配置&#xff0c;感觉没问题 把项目移动到没有中文的目录下重新启动&#xff0c;也还是不行&#xff0c;后来发现原…