Linux 信号处理简析

news2024/11/15 17:15:19

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 分析背景

本文基于 ARM32 架构 + Linux 4.14 内核源码进行分析。

3. 信号概述

3.1 信号分类

信号这个概念,起始于 UNIX 操作系统,经过一系列的演变,形成了今天由 POSIX 标准定义的信号。按信号的编号区间和处理的实时性,我们简单的将信号分为标准信号实时信号两类。

3.1.1 标准信号

标准信号起始于 UNIX 操作系统,编号区间为 1-31 。标准信号的编号如下表:

Signal        x86/ARM     Alpha/   MIPS   PARISC   Notes
            most others   SPARC
─────────────────────────────────────────────────────────────────
SIGHUP           1           1       1       1
SIGINT           2           2       2       2
SIGQUIT          3           3       3       3
SIGILL           4           4       4       4
SIGTRAP          5           5       5       5
SIGABRT          6           6       6       6
SIGIOT           6           6       6       6
SIGBUS           7          10      10      10
SIGEMT           -           7       7      -
SIGFPE           8           8       8       8
SIGKILL          9           9       9       9
SIGUSR1         10          30      16      16
SIGSEGV         11          11      11      11
SIGUSR2         12          31      17      17
SIGPIPE         13          13      13      13
SIGALRM         14          14      14      14
SIGTERM         15          15      15      15
SIGSTKFLT       16          -       -        7
SIGCHLD         17          20      18      18
SIGCLD           -          -       18      -
SIGCONT         18          19      25      26
SIGSTOP         19          17      23      24
SIGTSTP         20          18      24      25
SIGTTIN         21          21      26      27
SIGTTOU         22          22      27      28
SIGURG          23          16      21      29
SIGXCPU         24          24      30      12
SIGXFSZ         25          25      31      30
SIGVTALRM       26          26      28      20
SIGPROF         27          27      29      21
SIGWINCH        28          28      20      23
SIGIO           29          23      22      22
SIGPOLL                                            Same as SIGIO
SIGPWR          30         29/-     19      19
SIGINFO          -         29/-     -       -
SIGLOST          -         -/29     -       -
SIGSYS          31          12      12      31
SIGUNUSED       31          -       -       31

可见,对于不同的硬件架构实现,信号的编号并不相同,但它们需要保证,同名的信号,具有相同的含义。我们再来看一下部分标准信号的具体含义、以及它们的默认处理动作:

 Signal      Standard   Action   Comment
────────────────────────────────────────────────────────────────────────
SIGABRT      P1990      Core    Abort signal from abort(3)
SIGALRM      P1990      Term    Timer signal from alarm(2)
SIGBUS       P2001      Core    Bus error (bad memory access)
SIGCHLD      P1990      Ign     Child stopped or terminated
SIGCLD         -        Ign     A synonym for SIGCHLD
SIGCONT      P1990      Cont    Continue if stopped
SIGEMT         -        Term    Emulator trap
SIGFPE       P1990      Core    Floating-point exception
SIGHUP       P1990      Term    Hangup detected on controlling terminal
                                or death of controlling process
SIGILL       P1990      Core    Illegal Instruction
SIGINFO        -                A synonym for SIGPWR
SIGINT       P1990      Term    Interrupt from keyboard
SIGIO          -        Term    I/O now possible (4.2BSD)
SIGIOT         -        Core    IOT trap. A synonym for SIGABRT
SIGKILL      P1990      Term    Kill signal
SIGLOST        -        Term    File lock lost (unused)
SIGPIPE      P1990      Term    Broken pipe: write to pipe with no
                                readers; see pipe(7)
SIGPOLL      P2001      Term    Pollable event (Sys V);
                                synonym for SIGIO
SIGPROF      P2001      Term    Profiling timer expired
SIGPWR         -        Term    Power failure (System V)
SIGQUIT      P1990      Core    Quit from keyboard
SIGSEGV      P1990      Core    Invalid memory reference
SIGSTKFLT      -        Term    Stack fault on coprocessor (unused)
SIGSTOP      P1990      Stop    Stop process
SIGTSTP      P1990      Stop    Stop typed at terminal
SIGSYS       P2001      Core    Bad system call (SVr4);
                                see also seccomp(2)
SIGTERM      P1990      Term    Termination signal
SIGTRAP      P2001      Core    Trace/breakpoint trap
SIGTTIN      P1990      Stop    Terminal input for background process
SIGTTOU      P1990      Stop    Terminal output for background process
SIGUNUSED      -        Core    Synonymous with SIGSYS
SIGURG       P2001      Ign     Urgent condition on socket (4.2BSD)
SIGUSR1      P1990      Term    User-defined signal 1
SIGUSR2      P1990      Term    User-defined signal 2
SIGVTALRM    P2001      Term    Virtual alarm clock (4.2BSD)
SIGXCPU      P2001      Core    CPU time limit exceeded (4.2BSD);
                                see setrlimit(2)
SIGXFSZ      P2001      Core    File size limit exceeded (4.2BSD);
                                see setrlimit(2)
SIGWINCH       -        Ign     Window resize signal (4.3BSD, Sun)

3.1.2 实时信号

标准信号的处理,不具备实时性。对某一个标准信号,只有当前有挂起的,后续的信号都会被忽略,也就是只会响应第一个信号。为此,引入了实时信号,对于同一实时信号的多次触发,会建立信号队列,将信号入队,让每个信号都得到处理。
实时信号编号区间为 32-64,glibc 的 pthread ,使用了 32-3332-34 这几个信号,同时将标记实时信号起始编号的宏 SIGRTMIN 重定义为 34 或 35

3.2 信号的发起

信号的发起,可以经由系统调用 sys_kill()sys_tgkill() 显式发起,其中:

sys_kill() 发送给线程组,处理信号的线程可以是线程组中内的任何线程;
sys_tgkill() 发送给线程组内特定线程,信号经由该线程上下文处理。

另外一类信号发起的发起的方式,是由进程在某些特定条件下(如空指针访问),由内核隐式发起,如 SIGSEGV
在分析信号的发起流程前,先看一下信号进程处理信号相关的数据结构:
在这里插入图片描述
接下来看一下信号的发起流程。先看发送信号给线程组的流程:

sys_kill(pid, sig)
	struct siginfo info;

	info.si_signo = sig;
	info.si_errno = 0;
	info.si_code = SI_USER;
	info.si_pid = task_tgid_vnr(current);
	info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
	
	kill_something_info(sig, &info, pid)
		kill_pid_info(sig, info, find_vpid(pid))
			struct task_struct *p = pid_task(pid, PIDTYPE_PID);
			group_send_sig_info(sig, info, p)
			 	do_send_sig_info(sig, info, p, true) /* 发送信号到线程组 */
			 		/* 参看信号发送的公共流程 */

发送信号给特定线程的流程:

sys_tgkill(tgid, pid, sig)
	do_tkill(tgid, pid, sig)
		struct siginfo info = {};
		
		info.si_signo = sig;
		info.si_errno = 0;
		info.si_code = SI_TKILL;
		info.si_pid = task_tgid_vnr(current);
		info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
		
		do_send_specific(tgid, pid, sig, &info)
			struct task_struct *p = find_task_by_vpid(pid);
			do_send_sig_info(sig, info, p, false) /* 发送信号到特定线程 */
				/* 参看信号发送的公共流程 */

发送信号到线程组或线程组内特定线程的公共流程:

do_send_sig_info(sig, info, p, group)
	send_signal(sig, info, p, group)
		__send_signal(sig, info, t, group, from_ancestor_ns)
			/* prepare_signal() 返回 0 表示接收信号 */
			if (!prepare_signal(sig, t,
					from_ancestor_ns || (info == SEND_SIG_PRIV) || (info == SEND_SIG_FORCED)))
				goto ret;
			
			/*
			 * group == true : 将信号放入线程组共享的挂起队列
			 * group == false: 将信号放入线程独立的挂起队列
			 */
			pending = group ? &t->signal->shared_pending : &t->pending;

			/* 对标准信号,如果重复收到,仅需要入队1次 */ 
			if (legacy_queue(pending, sig))
				goto ret;

			/* 分配挂起信号队列节点对象 */
			q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);
			/* 添加挂起信号到对应队列 */
			list_add_tail(&q->list, &pending->list);
			copy_siginfo(&q->info, info);

			signalfd_notify(t, sig); /* 唤醒在 signalfd 上等待信号的进程 */
			sigaddset(&pending->signal, sig); /* 设置挂起信号的掩码 */
			/*
			 * 选择信号处理进程,告知进程有挂起的信号待处理 (设置 TIF_SIGPENDING 标
			 * 记),然后唤醒进程 
			 */
			complete_signal(sig, t, group);

3.3 信号的处理

3.3.1 信号处理的准备工作

在进程启动时,会做一些进程信号处理的准备工作,其具体流程如下:

load_elf_binary()
	...
	arch_setup_additional_pages(bprm, !!elf_interpreter)
		signal_page = get_signal_page()
			page = alloc_pages(GFP_KERNEL, 0); /* 分配1个物理页面 */
			addr = page_address(page); /* 返回页面的虚拟地址 */
			offset = 0x200 + (get_random_int() & 0x7fc); /* 页面内随机偏移 */
			signal_return_offset = offset; /* 保存页内随机偏移到 @signal_return_offset */
			/* 拷贝【信号处理接口返回内核空间代码片段】到页面内偏移 @offset 处 */ 
			memcpy(addr + offset, sigreturn_codes, sizeof(sigreturn_codes))
		...
		/* 映射【信号处理接口返回内核空间代码片段】所在页面到进程虚拟地址空间 */
		hint = sigpage_addr(mm, npages);
		addr = get_unmapped_area(NULL, hint, npages << PAGE_SHIFT, 0, 0);
		vma = _install_special_mapping(mm, addr, PAGE_SIZE,
				VM_READ | VM_EXEC | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC,
				&sigpage_mapping);
		/* 记录【信号处理接口返回内核空间代码片段】虚拟地址到进程地址空间 mm_struct */
		mm->context.sigpage = addr;
	...

3.3.2 信号的处理流程

中断返回用户空间系统调用返回用户空间时,系统对挂起的信号进行处理。处理流程如下:

/* @arch/arm/kernel/entry-common.S */
ret_fast_syscall:
	ldr	r1, [tsk, #TI_FLAGS]		@ re-check for syscall tracing
	tst	r1, #_TIF_SYSCALL_WORK | _TIF_WORK_MASK	@ 检查进程的 _TIF_SIGPENDING 标记
	...
/* 有挂起的工作要做,先做完挂起的工作,再返回用户空间 */
slow_work_pending:
	mov	r0, sp
	mov	r2, why
	@arch/arm/kernel/signal.c
	bl	do_work_pending @ 处理挂起的信号
		do {
			...
			if (thread_flags & _TIF_SIGPENDING) { /* 挂起信号可能导致系统调用的中断 */
				do_signal(regs, syscall)
					if (get_signal(&ksig)) { /* 取出一个挂起的信号 */
						handle_signal(&ksig, regs); /* 处理取出的挂起信号 */
							setup_frame(ksig, oldset, regs)
								/* 从用户空间栈分配 sigframe 变量空间 */
								struct sigframe __user *frame = get_sigframe(ksig, regs, sizeof(*frame));
								setup_sigframe(frame, regs, set)
									context = (struct sigcontext) {
										.arm_r0        = regs->ARM_r0,
										.arm_r1        = regs->ARM_r1,
										.arm_r2        = regs->ARM_r2,
										.arm_r3        = regs->ARM_r3,
										.arm_r4        = regs->ARM_r4,
										.arm_r5        = regs->ARM_r5,
										.arm_r6        = regs->ARM_r6,
										.arm_r7        = regs->ARM_r7,
										.arm_r8        = regs->ARM_r8,
										.arm_r9        = regs->ARM_r9,
										.arm_r10       = regs->ARM_r10,
										.arm_fp        = regs->ARM_fp,
										.arm_ip        = regs->ARM_ip,
										.arm_sp        = regs->ARM_sp,
										.arm_lr        = regs->ARM_lr,
										.arm_pc        = regs->ARM_pc,
										.arm_cpsr      = regs->ARM_cpsr,
								
										.trap_no       = current->thread.trap_no,
										.error_code    = current->thread.error_code,
										.fault_address = current->thread.address,
										.oldmask       = set->sig[0],
									};
									__copy_to_user(&sf->uc.uc_mcontext, &context, sizeof(context)); /* 保存用户空间上下文:信号处理会破坏它们 */
									...
								setup_return(regs, ksig, frame->retcode, frame)
									/* 用户空间设置的信号处理接口 */
									unsigned long handler = (unsigned long)ksig->ka.sa.sa_handler;
									...
									if (__put_user(sigreturn_codes[idx],   rc) ||
		    							__put_user(sigreturn_codes[idx+1], rc+1))
										return 1;
									/* 进程启动时,映射到进程地址空间的【信号处理接口返回内核空间代码片段】地址 */
									struct mm_struct *mm = current->mm;
									retcode = mm->context.sigpage + signal_return_offset +
				  (idx << 2) + thumb;
				  					regs->ARM_r0 = ksig->sig; /* 信号处理接口的 参数0 为信号编码 */
									regs->ARM_sp = (unsigned long)frame;
									regs->ARM_lr = retcode; /* 信号处理接口返回到sigreturn_codes 代码片段处:即发起系统调用 sys_sigreturn() 返回内核空间,然后再返回用户空间被中断的代码处 */
									regs->ARM_pc = handler; /* 处理信号时,返回用户空间时,返回到信号处理接口 */
									regs->ARM_cpsr = cpsr;

									return 0;
							signal_setup_done(ret, ksig, 0)
					}
			}
			...
		} while (thread_flags & _TIF_WORK_MASK);

	/* 从中断或系统调用返回用户空间,进入用户空间配置的信号处理接口 */
	signal_handler()

	/*
	 * 信号处理接口 signal_handler() 返回时,执行 sigreturn_codes 处的代码片段:
	 * arch/arm/kernel/sigreturn_codes.S
	 */
sigreturn_codes:
	mov	r7, #(__NR_sigreturn - __NR_SYSCALL_BASE)
	swi	#(__NR_sigreturn)|(__NR_OABI_SYSCALL_BASE)
		/* 进入系统调用 sys_sigreturn() */
		sys_sigreturn()
			frame = (struct sigframe __user *)regs->ARM_sp;
			/* 恢复用户空间因信号处理被破坏上下文 */
			restore_sigframe(regs, frame)
				__copy_from_user(&context, &sf->uc.uc_mcontext, sizeof(context))
				regs->ARM_r0 = context.arm_r0;
				regs->ARM_r1 = context.arm_r1;
				regs->ARM_r2 = context.arm_r2;
				regs->ARM_r3 = context.arm_r3;
				regs->ARM_r4 = context.arm_r4;
				regs->ARM_r5 = context.arm_r5;
				regs->ARM_r6 = context.arm_r6;
				regs->ARM_r7 = context.arm_r7;
				regs->ARM_r8 = context.arm_r8;
				regs->ARM_r9 = context.arm_r9;
				regs->ARM_r10 = context.arm_r10;
				regs->ARM_fp = context.arm_fp;
				regs->ARM_ip = context.arm_ip;
				regs->ARM_sp = context.arm_sp;
				regs->ARM_lr = context.arm_lr;
				regs->ARM_pc = context.arm_pc;
				regs->ARM_cpsr = context.arm_cpsr;
	
	/* 从系统调用 sys_sigreturn() 返回用户空间继续执行,信号处理完毕!!! */

我们用下图来总结下信号的处理流程:

用户态
		signal                signal handler                   继续执行被中断的程序
----------                  ------------------                ----------------------
          |                |                  |              |
		  |                |           sys_sigreturn()       |
		  |                |                  |              |
----------V----------------^------------------V--------------^-----------------------> t
          |                |                  |              |
          |                |                  |              | 
		  |                |                  |              |
           ----------------                    --------------
             do_signal()                       sys_sigreturn()
内核态

4. 实例

学习信号处理的细节,到底意义何在?在Linux应用编程: API基础中,提到一个Async-Signal-Safe Function的概念,这类函数可以在信号处理函数内调用,除此之外的其它函数,如果在信号处理函数内调用,可能导致程序死锁、或者数据处理混乱等问题。让我们来看一个在信号处理接口内不适当地调用函数,导致死锁的例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>

pthread_mutex_t recursive_disallow_mutex;

void async_signal_not_safe(void)
{
    pthread_mutex_lock(&recursive_disallow_mutex);
    sleep(5);
    pthread_mutex_unlock(&recursive_disallow_mutex);
}

void signal_int(int signo)
{
    async_signal_not_safe();
}

int main(void)
{
	pthread_mutexattr_t attr;

	/* 不允许 pthread_mutex_t 递归使用 */
	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
	pthread_mutex_init(&recursive_disallow_mutex, &attr);
	pthread_mutexattr_destroy(&attr);

	if (signal(SIGINT, signal_int) == SIG_ERR) {
		printf("signal(SIGINT) error");
		return -1;
	}

	async_signal_not_safe();

    return 0;
}

编译运行,按下 Ctrl+C ,用 gdb 观察程序运行情况:

bill@bill-virtual-machine:~/Study/app/signal$ sudo gdb attach -p 3560
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
attach: No such file or directory.
Attaching to process 3560
Reading symbols from /home/bill/Study/app/signal/async-signal-not-safe...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libpthread.so.0...Reading symbols from /usr/lib/debug/.build-id/c5/57b8146e8079af46310b549de6912d1fc4ea86.debug...done.
done.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.23.so...done.
done.
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/ld-2.23.so...done.
done.
__lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
135	../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory.
(gdb) thread apply all bt

Thread 1 (Thread 0x7f6f08429700 (LWP 3560)):
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1  0x00007f6f0800bdbd in __GI___pthread_mutex_lock (mutex=0x6010a0 <recursive_disallow_mutex>) at ../nptl/pthread_mutex_lock.c:80
#2  0x0000000000400904 in async_signal_not_safe ()
#3  0x000000000040092b in signal_int ()
#4  <signal handler called>
#5  0x00007f6f07d04370 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-template.S:84
#6  0x00007f6f07d042da in __sleep (seconds=0) at ../sysdeps/posix/sleep.c:55
#7  0x000000000040090e in async_signal_not_safe ()
#8  0x00000000004009af in main ()

发现程序一直卡在了信号处理函数 signal_int() 调用链:

signal_int()
	async_signal_not_safe()
		pthread_mutex_lock(&recursive_disallow_mutex)

也就是说,程序发生了死锁。从前面分析的信号处理流程,这里发生问题的场景,在如下场景进入了信号处理接口:

main()
	async_signal_not_safe()
		pthread_mutex_lock(&recursive_disallow_mutex)
			sleep(5)

sleep() 使进程陷入睡眠期间,按下 Ctrl+C 生成了 SIGINT 信号;在 sleep() 睡眠时间到达后,系统唤醒进程,从 sleep() 系统调用返回用户空间,发现进程有挂起的信号,于是进入信号处理流程:

signal_int()
	async_signal_not_safe()
		pthread_mutex_lock(&recursive_disallow_mutex)

此时因为锁 recursive_disallow_mutex 尚未释放,同时禁用了锁 recursive_disallow_mutex 的递归使用,从而导致死锁。
类似如上的场景还有很多,如在 main()signal_int() 中都同时调用 malloc()/free() 等接口,都会导致死锁,或者数据损坏等莫名其妙的错误。

5. 参考资料

man signal
关于异步信号安全

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

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

相关文章

4、程序计数器PC

介绍 JVM中的程序计数寄存器&#xff08;Program Counter Register&#xff09;中&#xff0c;Register的命名源于CPU的寄存器&#xff0c;寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。这里&#xff0c;并非是广义上所指的物理寄存器&#xff0c;或许…

XINDOO的2022年年终总结

已经好几个月没有认认真真写一篇博客了&#xff0c;借着年底静下心来认认真真写一篇年终总结&#xff0c;这也是我人生中第10篇的年终总结了。 先看下去年立的flag&#xff0c;不用想去年立的flag一个都没完成。首先1 算是勉强及格&#xff1b;2 redis的博客一篇没写&#xff1…

前端学习第二站——JavaScript

目录 1. 简介 2. 变量与数据类型 2.1 声明变量 2.2 基本类型 2.3 对象类型 2.3.1 函数 Function ​ 2.3.2 数组Array 2.3.3 对象 Object ⭐️⭐️ 3. 运算符和表达式 1) 2) || 4) ... 5) [] {} 4. 控制语句 1) for in 2) for of 3) try catch 1. 简介 JavaScr…

移动应用安全过去及未来发展情况思考汇总

声明 本文是学习移动安全总结 2019. 下载地址而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 序言 随着2019年的逝去&#xff0c;二十一世纪第二个十年也已随之结束。回顾过去的十年&#xff0c;我们的生活随着科技的进步发生了翻天覆地的变化&#x…

MySQL索引最佳实践及Explain详解

一、Explain工具介绍 使用EXPLAIN关键字可以模拟优化器执行SQL语句&#xff0c;分析你的查询语句或是结构的性能瓶颈在 select 语句之前增加 explain 关键字&#xff0c;MySQL 会在查询上设置一个标记&#xff0c;执行查询会返回执行计划的信息&#xff0c;而不是执行这条SQL …

leetcode 169. 多数元素-java题解

题目所属分类 超经典问题 可以选用投票法 原题链接 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 代码案例&#xff1a;输入…

java 瑞吉外卖优化day2 读写分离 ShardingJDBC

问题分析&#xff1a; mysql主从复制 介绍&#xff1a; 补充&#xff1a;从库可以有多个 提前准备好两台服务器&#xff0c;分别安装Mysql并启动服务成功 主库Master 192.168.138.100 从库Slave 192.168.138.101 Window系统则是在my.ini文件里直接配置 mysql -uroot -…

Day 16-Vue3 技术_新的组件

1.Fragment —— 片段组件 在Vue2中: 组件必须有一个根标签。 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个内置Fragment虚拟元素中&#xff0c; 最后是不参与页面渲染的&#xff01; 好处: 减少标签层级, 减小内存占用。 <template><fragment>…

UART实验

目录 一、UART 1.1简介 1.2并行通信和串行通信 1.3单工和双工 1.4波特率 1.5UART帧格式 1.6UART硬件连接 1.7UART控制器 二、Exynos4412的UART控制器 三、UART寄存器 四、UART编程 五、输入输出重定向 六、使用串口控制LED 一、UART 1.1简介 Universal Asynch…

Elasticsearch学习

基本概念 运维角度物理概念 分片&#xff08;shard&#xff09;&#xff1a;一个索引所占用的物理空间分配 primary shard&#xff1a;解决数据水平扩展问题&#xff0c;分片数据量过大时&#xff0c;可以通过增加DataNode节点扩容。一个主分片等于一个lucene实例。创建索引时…

windows认证之本地认证

windows认证包括本地认证、网络认证和域认证三个部分windows认证和密码的抓取可以说是内网渗透的第一步。 1、window认证流程 Windows的登陆密码是储存在系统本地的SAM文件中的&#xff0c;在登陆Windows的时候&#xff0c;系统会将用户输入的密码与 SAM文件中的密码进行对比&…

JavaScript基础系列之引用类型细节总结

1. 前言 这里不罗列 API&#xff0c;如果需要 API 可以自行查询。只会强调一些不起眼但是很重要的细节问题 2. Object 2.1 生成对象几种方式&#xff1a; 曾经被面试过哦&#xff0c; 虽然很基础&#xff0c;但是划重&#xff01;&#xff01;&#xff01; const obj new Obj…

【机器学习】线性回归(理论)

线性回归&#xff08;理论&#xff09; 目录一、概论1、何为线性回归2、问题的抽象3、误差的引入4、极大似然估计的引入5、目标函数的优化二、梯度下降1、何为梯度下降2、利用梯度下降进行函数寻优3、梯度下降的一些问题Ⅰ 迭代步长Ⅱ 算法的初始位置Ⅲ 数据的取值范围差异Ⅳ 鞍…

i.MX8MP平台开发分享(IOMUX篇)- uboot驱动

专栏目录:专栏目录传送门 平台内核i.MX8MP5.15.71文章目录 1. pinfunc.h2.iomux驱动3.pinctrl_select_state_full4.imx_pinctrl_set_state1. pinfunc.h pinfunc.h中定义了所有的引脚,命名方式是MX8MP_IOMUXC___,例如下面的MX8MP_IOMUXC_GPIO1_IO00__GPIO1_IO00定义了MUX寄存…

网络信息安全-LSB图像隐写与检测的设计实现

任务目标&#xff1a; 本选题需要学习经典的图像信息隐藏算法&#xff0c;包括基于空域的隐写算法和数字水印算法。 接着你将使用某种编程语言实现这些算法&#xff0c;实现在图片中嵌入一些信息&#xff0c;例如字符串和一些 文件。除此之外&#xff0c;还需要尝试一些基础的…

吴恩达《机器学习》——Logistic多分类与神经网络

Logistic多分类与神经网络1. MINIST数据集与Logistic多分类MINIST简介数据集可视化Logistic如何实现多分类&#xff1f;One-Hot向量Python实现2. 神经网络(Neural Network, NN)神经网络前馈传播Python实现3. 基于PyTorch框架的网络搭建数据集、源文件可以在Github项目中获得 链…

使用CMake构建静态库和动态库

使用CMake构建静态库和动态库一、准备工作二、动态库的构建2.1 工程改造2.2 编译动态库2.3 更多的说明三、静态库的构建3.1 错误的尝试3.2 新的构建指令四、动态库的版本号五、安装动态库和头文件一、准备工作 本机演示环境为&#xff1a; 主机windows11 vscode 虚拟机安装的…

人工智能在药物研发和生物技术中的应用:回顾与展望

人工智能(Artificial intelligence, AI)的出现正在重新塑造整个制药和生物技术行业的发展。几乎所有大大小小的生命科学和药物发现机构&#xff0c;都对采用人工智能驱动的发现平台表现出浓厚的兴趣&#xff0c;希望通过AI来简化研发工作&#xff0c;减少发现时间和成本&#x…

【C++基础】10:STL(二)

CppSTL&#xff08;二&#xff09; OVERVIEWCppSTL&#xff08;二&#xff09;一、函数对象1.函数对象&#xff1a;&#xff08;1&#xff09;概述&#xff1a;&#xff08;2&#xff09;简单使用&#xff1a;2.谓词&#xff1a;&#xff08;1&#xff09;一元谓词&#xff1a;…

LVGL学习笔记10 - 按钮Button

目录 1. Check型按钮 2. 修改样式 2.1 设置背景 2.1.1 颜色 2.1.2 透明度 2.1.3 渐变色 2.1.4 渐变色起始位置设置 2.2 修改边界 2.2.1 宽度 2.2.2 颜色 2.2.3 透明度 2.2.4 指定边 2.3 修改边框 2.4 修改阴影 2.4.1 宽度 2.4.2 透明度 2.4.3 偏移坐标 2.3.…