CSAPP shelllab

CSAPP shell lab

shell lab 的目标

实现shell 功能,包括解析命令,调用可执行文件和内置命令,(quit, jobs,fg, 和bg)。实现job控制和signal handler。

shell 介绍

在这里插入图片描述

Shell中的作业(job)管理是一种用于跟踪和控制正在运行的进程的机制。Shell通常支持前台任务和后台任务,它们之间有以下差别:

  1. 前台任务(Foreground Jobs):

    • 前台任务是由用户启动并在前台运行的任务。当你在Shell中运行一个命令,它通常会成为前台任务,并且会占用你的终端会话,直到任务完成或被中断。
    • 前台任务会接收来自终端的输入,并将其输出显示在终端上。这使得它们非常适合需要与用户交互的任务,例如编辑文本文件或运行交互式程序。
    • 要将一个正在运行的前台任务放入后台,你可以使用 Ctrl+Z 暂停任务,然后使用 bg 命令将其放入后台运行。
  2. 后台任务(Background Jobs):

    • 后台任务是在后台(背景)运行的任务,不会占用终端会话。用户可以继续输入其他命令,而不必等待后台任务完成。
    • 后台任务通常不接收终端输入,它们的输出通常会被重定向到文件或丢弃。
    • 用户可以使用 & 符号将一个命令放入后台,例如:command &

作业管理在shell中的主要目标是允许用户有效地控制和监视正在运行的任务。一些常见的作业管理命令包括:

  • jobs:显示当前正在运行的作业列表。
  • fg:将一个后台任务切换到前台运行。
  • bg:将一个暂停的前台任务或一个后台任务切换到后台运行。
  • kill:用于终止一个正在运行的作业。
  • nohup:用于在后台运行一个命令,不受终端关闭的影响。
  • disown:从shell的作业表中移除一个作业,使其不再受shell的控制。

作业管理是shell的一个重要功能,允许用户更灵活地处理多个任务,提高了终端的使用效率。不同的shell可能会有不同的作业管理实现方式,但基本概念通常是相似的。

step1 解析用户输入

输入例子:
在这里插入图片描述

字符串解析:
在这里插入图片描述
字符串解析函数:

/** parseline - 解析命令行并构建 argv 数组。* * 用单引号括起来的字符被视为一个单独的参数。如果用户请求一个后台作业,则返回 true;如果用户请求一个前台作业,则返回 false。*/
int parseline(const char *cmdline, char **argv) 
{static char array[MAXLINE]; /* holds local copy of command line */char *buf = array;          /* ptr that traverses command line */char *delim;                /* points to first space delimiter */int argc;                   /* number of args */int bg;                     /* background job? */printf("parse line : %s", cmdline);strcpy(buf, cmdline);buf[strlen(buf)-1] = ' ';  /* replace trailing '\n' with space */while (*buf && (*buf == ' ')) /* ignore leading spaces */buf++;/* Build the argv list */argc = 0;if (*buf == '\'') {buf++;delim = strchr(buf, '\'');}else {delim = strchr(buf, ' ');printf("parse line delim : %s", delim);}while (delim) {argv[argc++] = buf;*delim = '\0';buf = delim + 1;while (*buf && (*buf == ' ')) /* ignore spaces */buf++;if (*buf == '\'') {buf++;delim = strchr(buf, '\'');}else {delim = strchr(buf, ' ');}}argv[argc] = NULL;if (argc == 0)  /* ignore blank line */return 1;/* should the job run in the background? */if ((bg = (*argv[argc-1] == '&')) != 0) {argv[--argc] = NULL;}return bg;
}

step2 用户执行指令

ppt中关于shell的指令代码参考:
在这里插入图片描述

全局变量和job数据结构:
在这里插入图片描述
这里简化了pgid 的部分。

waitpid 函数例子

在这里插入图片描述

shell 介绍

在这里插入图片描述
在解析了命令行之后,eval 函数调用 builtin_command 函数,该函数检查第一个命令行参数是否是内置的 shell 命令。如果是,则立即解释该命令并返回 1。否则,返回 0。简单 shell 只有一个内置命令,即 quit 命令,用于终止 shell。实际的 shell 有许多命令,例如 pwdjobsfg

如果 builtin_command 返回 0,则 shell 创建一个子进程,并在子进程中执行请求的程序。如果用户要求在后台运行程序,则 shell 返回到循环的顶部并等待下一个命令行。否则,shell 使用 waitpid 函数等待作业终止。当作业终止时,shell 继续下一次迭代。

简单的 shell 有一个缺陷,即它不会清理任何后台子进程。纠正这个缺陷需要使用信号。
在这里插入图片描述

在这里插入图片描述
一个进程可以通过使用 signal 函数来修改与信号关联的默认动作。

处理器(handler)是用户定义的函数的地址,称为信号处理器,每当进程接收到类型为signum的信号时都会调用该函数。通过将处理器的地址传递给signal函数来改变默认操作称为安装处理器(installing handler)。对处理器的调用称为捕获信号(catching signal)。执行处理器被称为处理信号(handling signal)。

当进程捕获到类型为k的信号时,为信号k安装的处理器将以一个整数参数k被调用。这个参数允许相同的处理器函数捕获不同类型的信号。

当处理器执行其返回语句时,控制通常会返回到进程由于接收到信号而被中断的控制流中的指令。因为在某些系统中,中断的系统调用会立即返回一个错误。
在这里插入图片描述
在这里插入图片描述
信号集(例如set)可以使用以下函数进行操作:sigemptyset 将 set 初始化为空集。sigfillset 函数将所有信号添加到 set 中。sigaddset 函数将 signum 添加到 set 中,sigdelset 删除 set 中的 signum,sigismember 如果 signum 是 set 的成员,则返回1,否则返回0。
在这里插入图片描述
在这里插入图片描述

addjob和deletejob存在race, 需要确保addjob发生在deletejob之前。
在这里插入图片描述
在这里插入图片描述
sigsuspend 函数是原子性的,可以更好的避免冲突。

在Sigprocmask 屏蔽前收到signal

在调用 sigprocmask 函数之前收到信号是可能的。sigprocmask 用于修改进程的信号屏蔽集,而不是防止在调用之前收到信号。信号是异步事件,可能会在任何时刻发生,即使在执行 sigprocmask 之前。

如果在执行 sigprocmask 之前收到了一个信号,那么该信号将按照信号的默认处理方式进行处理,除非在信号发生之前设置了信号处理函数或者通过 sigaction 显式设置了信号的处理方式。

sigprocmask 的目的是允许程序员在某个临界区域内阻塞或解除阻塞特定信号,以确保在这个区域内不会受到特定信号的干扰。但是,这并不是一个绝对的保证,因为在执行 sigprocmask 之前和之后都有可能收到信号。

为了更精确地控制信号的处理,可以使用 sigaction 函数来设置信号处理函数,并在处理函数中进行信号屏蔽,以确保在处理信号期间其他相关的信号不会中断当前的处理。这样可以更精确地控制信号的处理流程。

async-signal-safe

在这里插入图片描述
在这里插入图片描述
“async-signal-safe” 通常是 “asynchronous-signal-safe” 的缩写,指的是在异步信号处理上下文中安全使用的函数。异步信号是在程序执行期间随时发生的,通常是由操作系统或其他部分的程序触发的。由于异步信号可能在任何时候中断程序的正常执行,因此在信号处理程序中只能使用一些特定的函数,这些函数是异步信号安全的,以确保程序的稳定性和可靠性。

异步信号安全的函数通常是那些不使用全局变量、不分配内存、不进行文件 I/O 等可能导致不确定性的操作的函数。这些函数应该是线程安全的,而且不能被其他信号中断。

具体的异步信号安全函数取决于编程语言和操作系统。在C语言中,一些标准的异步信号安全函数包括 write, read, signal, kill, getpid等。在特定的环境中,还可能存在特定的异步信号安全函数。需要查看相关文档以获取更具体的信息。

volatile

在计算机编程中,volatile 是一个关键字,用于告诉编译器不要对标记为 volatile 的变量进行优化。通常,编译器会对变量进行优化,以提高程序的性能和效率。这可能包括将变量的值存储在寄存器中,而不是内存中,或者对变量的访问顺序进行重新排序。

然而,有些变量的值可能会在程序的执行过程中由于外部因素而发生变化,而编译器无法检测到这些变化。这种情况下,如果编译器进行了优化,可能会导致程序出现错误的行为。比如,当一个变量被一个信号处理程序修改时,编译器并不知道这一点,它可能仍然使用之前的值。

在这种情况下,可以使用 volatile 关键字来告诉编译器,这个变量的值可能会在编译器控制之外的情况下发生变化,因此编译器不应该对它进行优化。这样,每次对 volatile 变量的读取和写入都会从内存中进行,而不是从寄存器或者缓存中。

总的来说,volatile 关键字用于标记那些可能会被程序以外的因素修改的变量,以确保编译器不会对其进行错误的优化。

sig_atomic_t

sig_atomic_t 是 C 和 C++ 中的一种整数类型,通常用于信号处理程序中。它被设计为在信号处理程序中原子地访问和修改,以确保线程安全性。

在信号处理程序中,信号可以在任何时候中断程序的正常执行,甚至可能在一个指令级的操作中间发生。这就需要确保对共享数据的访问是原子的,即要么完全执行,要么不执行。因此,sig_atomic_t 类型被设计为足够大以容纳任何信号值,并且在对其进行读取和写入时是原子的。

通常,sig_atomic_t 用于信号处理程序中的全局变量,以确保当处理信号时,对它们的访问是线程安全的。虽然在信号处理程序中仍然需要小心地处理共享数据,但使用 sig_atomic_t 类型可以确保对这些数据的访问是原子的,不会导致竞态条件或其他并发问题。

需要注意的是,sig_atomic_t 并不保证具有原子性的操作范围扩展到它所覆盖的所有操作系统和平台。在某些情况下,可能需要额外的同步机制来确保对共享数据的安全访问。

系统调用是可以收到signal

系统调用是用户空间程序与内核之间的接口,它们允许用户空间程序请求内核执行某些特权操作。当系统调用执行时,用户空间程序的控制权转移到内核空间,内核执行相应的操作,然后将控制返回给用户空间程序。

在执行系统调用期间,用户空间程序可以收到各种信号,具体取决于系统和应用程序的实现。一些可能的情况包括:

  1. SIGSEGV(段错误):如果系统调用尝试访问无效的内存区域,可能会导致段错误信号。

  2. SIGALRM(定时器信号):某些系统调用可能设置了定时器,当定时器到期时,会收到SIGALRM信号。

  3. SIGKILLSIGTERM:某些系统调用可能会在错误或异常情况下导致进程终止信号。

  4. SIGPIPE:如果系统调用尝试向已关闭的管道写入数据,可能会收到SIGPIPE信号。

关于你提到的慢系统调用被中断时可能产生的信号,通常是SIGINT(中断信号)。当进程受到SIGINT信号时,系统调用可能被中断,具体取决于系统的实现。

需要注意的是,不同的操作系统和实现可能有不同的行为,因此在编写跨平台的代码时,程序员需要仔细处理与信号相关的情况。

execve 函数

在这里插入图片描述
在这里插入图片描述
在调用execve后,它加载文件名(指定的可执行文件),然后调用了第7.9节中描述的启动代码。启动代码负责设置栈,并将控制传递给新程序的主例程。新程序的主例程通常具有以下形式的原型:

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

这是main函数的标准原型,其中:

  • int argc 是命令行参数的数量(argument count)。
  • char *argv[] 是一个指向参数字符串的数组(argument vector)。
  • char *envp[] 是一个指向环境变量字符串的数组(environment pointer)。

这个main函数是新程序的入口点,它接收命令行参数,并执行程序的主要逻辑。在启动过程中,启动代码负责准备好这些参数,然后将控制传递给main函数,使得新程序能够开始执行。

"主例程"通常指的是一个程序的主要执行入口点,即程序的主函数。在C语言中,主函数通常被命名为main,是程序运行时的起始点。

主例程执行程序的主要逻辑,它是程序中首先被执行的函数。

主例程在程序启动时首先被调用,它执行程序的主要功能,并可以调用其他函数来完成特定任务。在程序执行结束时,main函数返回一个整数值,表示程序的执行状态,该值被传递给操作系统。

strcpy 函数

strcpy 函数是C语言中字符串操作的标准库函数之一,用于将一个字符串复制到另一个字符串中。函数的原型如下:

#include <string.h>char *strcpy(char *dest, const char *src);
  • dest:目标字符串,即要将源字符串拷贝到的位置。
  • src:源字符串,即要被拷贝的字符串。

函数的作用是将源字符串(src)中的字符复制到目标字符串(dest)中,直到遇到源字符串的空字符 \0 为止。最终得到的目标字符串也是以空字符结尾的。strcpy 返回目标字符串的起始地址(即 dest 的值)。

需要注意的是,strcpy 函数不会检查目标字符串的长度是否足够容纳源字符串的内容,因此使用时需要确保目标字符串足够大以避免溢出。

下面是一个简单的示例:

#include <stdio.h>
#include <string.h>int main() {char source[] = "Hello, World!";char destination[20]; // 目标字符串要足够大strcpy(destination, source);printf("Source: %s\n", source);printf("Destination: %s\n", destination);return 0;
}

在这个例子中,strcpy 被用来将 source 中的内容复制到 destination 中。如果 destination 的长度不足,可能会导致缓冲区溢出的问题,因此在实际使用中应该确保目标字符串足够大。为了更安全的字符串操作,可以使用 strncpy 函数,该函数允许指定要复制的最大字符数,从而避免溢出的风险。

通过char *指针遍历char []

通过 char * 指针遍历 char [] 数组是一种常见的C语言操作。字符串在C中通常以char数组的形式表示,以空字符 '\0' 结尾。你可以使用指针来遍历这个数组,直到遇到空字符为止。

下面是一个简单的示例,演示如何通过 char * 指针遍历 char [] 数组:

#include <stdio.h>int main() {// 定义一个字符数组char myString[] = "Hello, World!";// 使用 char * 指针遍历数组char *ptr = myString;// 遍历数组,直到遇到空字符while (*ptr != '\0') {printf("%c ", *ptr);ptr++;  // 移动指针到下一个字符}printf("\n");return 0;
}

在这个例子中,char *ptr 指向字符串 myString 的开头,然后通过循环遍历数组中的每个字符,直到遇到空字符为止。printf 语句用于打印每个字符,ptr++ 用于将指针移动到数组中的下一个字符。

这种方式是C语言中经常使用的遍历字符串的方法,利用指针的特性逐个访问字符。需要注意的是,在使用指针遍历数组时,确保数组以空字符结尾,以便知道何时结束遍历。

strchr 函数介绍

strchr 函数是C语言标准库中的一个字符串查找函数,用于在字符串中查找指定字符的第一次出现位置。函数的原型如下:

#include <string.h>char *strchr(const char *str, int c);
  • str:要在其中查找的字符串。
  • c:要查找的字符。

函数返回一个指向找到的字符的指针,如果未找到字符,则返回空指针(NULL)。strchr 会从 str 字符串的开头开始查找,一直向后,直到找到第一个与 c 匹配的字符,或者查找到字符串的末尾(即空字符 \0)。返回的指针指向匹配字符的位置,可以用于进一步操作或索引。

以下是一个示例,演示如何使用 strchr 函数:

#include <stdio.h>
#include <string.h>int main() {char myString[] = "Hello, World!";char *ptr = strchr(myString, 'o');if (ptr != NULL) {printf("Found 'o' at position %ld\n", ptr - myString);} else {printf("'o' not found in the string\n");}return 0;
}

在这个例子中,我们在字符串 myString 中查找字符 'o' 的位置,然后打印出字符 'o' 在字符串中的位置。如果字符 'o' 未找到,strchr 将返回空指针,我们可以通过检查返回值是否为 NULL 来判断是否找到了字符。

strchr 还可以用来查找其他字符,不仅仅是单个字符,例如可以用它来查找特定的子字符串的首次出现位置。

字符串比较函数

在C语言中,字符串比较通常使用标准库中的 strcmp 函数。以下是 strcmp 函数的简单用法:

#include <string.h>int strcmp(const char *str1, const char *str2);

该函数接受两个字符串 str1str2 作为参数,并返回一个整数值,表示两个字符串的比较结果。返回值如下:

  • 如果 str1 等于 str2,则返回 0。
  • 如果 str1 小于 str2,则返回负值。
  • 如果 str1 大于 str2,则返回正值。

示例:

#include <stdio.h>
#include <string.h>int main() {char str1[] = "hello";char str2[] = "world";int result = strcmp(str1, str2);if (result == 0) {printf("Strings are equal.\n");} else if (result < 0) {printf("str1 is less than str2.\n");} else {printf("str1 is greater than str2.\n");}return 0;
}

在上面的示例中,strcmp 用于比较两个字符串 str1str2 的大小关系。根据比较结果,程序输出相应的消息。

操作系统和信号处理

操作系统通过信号(signal)机制来向程序发送异步通知,以实现一些特定事件的处理。当程序收到信号时,它可以执行与该信号相关的特定操作。下面是操作系统如何通过信号通知程序的一般步骤:

  1. 信号的生成: 信号通常由操作系统、其他进程,或者硬件事件生成。例如,用户按下 Ctrl+C 时,产生的 SIGINT 信号就是一个由操作系统生成的信号。

  2. 信号传递: 一旦信号生成,操作系统将尝试将信号传递给目标进程。这包括了找到目标进程的进程标识符(PID)等步骤。

  3. 信号的接收: 目标进程通过注册信号处理器来声明对特定信号的兴趣。如果目标进程没有注册信号处理器,那么系统将执行默认的信号处理行为。

  4. 信号处理器的执行: 如果目标进程注册了信号处理器,当信号到达时,操作系统会调用与之相关联的处理函数。这个函数中包含了程序在接收到该信号时应该执行的逻辑。

  5. 程序状态的改变: 信号处理器执行完成后,程序可能会根据信号类型执行特定的操作,如终止程序、清理资源、修改全局状态等。这取决于信号类型和程序的设计。

需要注意的是,信号是一种异步机制,因此处理信号的过程并不是程序按照正常顺序执行的一部分。操作系统会在程序当前执行的任何地方中断它,然后执行信号处理器。这为程序提供了一种在发生异步事件时执行相应操作的机制。

值得注意的是,程序可以选择忽略某些信号或自定义对信号的处理方式,从而灵活地控制程序在不同情境下的行为。

pending signal

在操作系统中,pending signals(待处理的信号)是指已经被发送给进程但尚未被处理的信号。处理 pending signals 的时机通常有两个关键点:

  1. 信号传递时机: 当一个信号被发送给进程时,它首先被标记为 pending(等待处理)。然而,该信号不会立即中断进程的正常执行。相反,系统会选择一个合适的时机来传递并处理这个信号。这个时机通常是在进程执行系统调用、陷入内核、或者发生某种事件(如时钟中断)时。

  2. 系统调用返回时: 在某些情况下,pending signals 的处理被推迟到系统调用返回时。这是因为在系统调用期间,进程可能会处于临界区(critical section),其中一些操作是不可中断的。因此,系统可能选择在系统调用完成后,但在返回到用户空间之前处理 pending signals。

在处理 pending signals 时,有几个相关的概念:

  • 信号阻塞(Signal Blocking): 进程可以选择阻塞(block)某些信号,使得这些信号在阻塞期间不会被处理。当信号被阻塞时,它们会被放入 pending signals 队列中。一旦解除阻塞,这些 pending signals 就有机会被处理。

  • 信号屏蔽字(Signal Mask): 信号屏蔽字是一个位掩码,用于表示当前被阻塞的信号集合。通过修改信号屏蔽字,进程可以动态地控制哪些信号是阻塞的,哪些是未阻塞的。

  • 信号处理器(Signal Handler): 对于每个信号,进程可以注册一个信号处理器。当信号被处理时,相应的处理器函数会被调用。

总体而言,处理 pending signals 是由操作系统负责的,但开发人员可以通过注册信号处理器和修改信号屏蔽字来对信号的处理进行控制。处理信号的时机和方式可以受到操作系统的调度策略、信号处理器的注册情况以及信号的优先级等多个因素的影响。

signal handler 需要上下文切换吗?

在信号处理器(Signal Handler)中是否需要进行上下文切换,取决于信号的类型以及信号处理器的执行上下文。

一般来说,信号处理器是在中断上下文(Interrupt Context)中执行的,而不是在用户进程的正常执行上下文中。中断上下文是指在内核执行的上下文,而不是在用户空间执行的上下文。当信号发生时,内核会中断当前进程的执行,转而执行与该信号相关联的信号处理器。

由于信号处理器在中断上下文中执行,通常情况下并不涉及用户进程的完整上下文切换。然而,有一些情况可能导致信号处理器需要访问用户进程的上下文信息,例如处理 SIGSEGV(段错误)信号时需要确定引起段错误的地址。在这种情况下,可能需要一部分上下文切换,以便信号处理器能够访问用户进程的内存和状态信息。

需要注意的是,一些信号可能会异步地中断用户进程的正常执行,而其他信号可能会在进程主动执行系统调用或发生其他特定事件时被处理。这些差异可能导致信号处理器在不同的上下文中执行。

总的来说,信号处理器的执行上下文通常是中断上下文,但在处理某些信号时可能涉及到一定程度的上下文切换,以便访问用户进程的信息。

signal预定义的默认动作

预定义的默认动作是每种信号类型在发生时由内核自动执行的默认行为。以下是一些常见信号的默认动作:

  1. SIGTERM(终止信号):默认动作是终止进程。
  2. SIGSEGV(段错误信号):默认动作是终止进程并转储核心。
  3. SIGSTOP(停止信号):默认动作是暂停进程,只能由 SIGCONT 信号重新启动。
  4. SIGKILL(强制终止信号):默认动作是强制终止进程,不能被捕获、忽略或阻塞。
  5. SIGCONT(继续信号):默认动作是继续(恢复)之前被停止的进程。

虽然大多数信号有默认动作,但有一些信号可以通过信号处理器来捕获、忽略或自定义处理。通过使用 signal() 系统调用或 sigaction() 函数,可以为进程中的特定信号设置自定义的信号处理函数。这样,当信号发生时,不会执行默认动作,而是执行相应的信号处理函数。

#include <signal.h>void custom_signal_handler(int signo) {// 自定义的信号处理函数逻辑
}int main() {// 设置自定义的信号处理函数signal(SIGTERM, custom_signal_handler);// 此处可以执行其他初始化操作// 主循环或其他程序逻辑return 0;
}

请注意,对于某些信号,例如 SIGKILL,是不能被捕获或忽略的,因为它们设计为终止进程的一种强制手段。

sigaction 函数

sigaction 是一个用于设置和检查信号处理程序的系统调用。它提供了一种更强大和可移植的方式来处理信号,相对于较旧的 signal 函数而言。

下面是 sigaction 函数的基本介绍:

#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • signum:指定要操作的信号的编号。
  • act:指向一个 struct sigaction 结构体的指针,用于设置新的信号处理程序及相关的属性。
  • oldact:可选参数,指向一个 struct sigaction 结构体的指针,用于存储之前的信号处理程序及相关的属性。

struct sigaction 的定义如下:

struct sigaction {void (*sa_handler)(int);          // 指定信号处理函数的地址void (*sa_sigaction)(int, siginfo_t *, void *);  // 指定带有三个参数的信号处理函数的地址sigset_t sa_mask;                 // 设置在信号处理函数执行期间要阻塞的信号集int sa_flags;                     // 设置信号处理的各种标志void (*sa_restorer)(void);        // 恢复处理函数
};

关键字段解释:

  • sa_handler:指定普通的信号处理函数的地址。可以设置为 SIG_DFL(默认处理)或 SIG_IGN(忽略信号)。
  • sa_sigaction:如果非空,将调用具有三个参数的信号处理函数,而不是 sa_handler
  • sa_mask:指定在信号处理函数执行期间要阻塞的信号集。
  • sa_flags:用于设置信号处理的各种标志,例如 SA_RESTART(如果系统调用被中断,自动重启)。
  • sa_restorer:不再被使用。

sigaction 的返回值为 0 表示成功,-1 表示失败。失败时,可以使用 errno 变量获取错误信息。

sigaction 提供了更灵活和可靠的信号处理方式,因此在编写信号处理代码时,通常优先使用 sigaction 而不是较旧的 signal 函数。

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

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

相关文章

2023年:个人年度成长与团队协作成就

文章目录 个人职业发展的喜悦团队成就的辉煌公众号CSDN申请了移动安全领域新星创作者获得6月城市之星北京TOP 10获得23年博客之星TOP 41年度总结 知识星球 开拓新领域的决心免费知识大陆付费知识大陆 展望未来福利时间知识星球会员一年知识星球立减88券 在这个充满挑战与机遇的…

(2)(2.9) Holybro Microhard P900无线电遥测设备

文章目录 前言 1 特点 2 规格 3 包装内包括 前言 Holybro Microhard Radio 集成了 microhard Pico 系列射频模块&#xff0c;能够在强大的拓扑结构中提供高性能无线串行通信&#xff0c;如点对点、点对多点和安全 Mesh&#xff08;P840 不提供 Mesh&#xff09;。 它采用跳…

SQL注入的剩余类型

除了联合查询注入&#xff0c;报错注入&#xff0c;盲注注入 sql注入还有以下几类&#x1f9b9;&#x1f9b9;&#x1f9b9;&#x1f9b9;&#x1f9b9; 开始填坑 1.UA注入 原理&#xff1a;有些网站会把用户的UA信息写入数据库&#xff0c;用来收集和统计用户…

【技术分享】Ubuntu 20.04如何更改用户名

产品简介 本文适用于所有RK3568/RK3588平台产品在Ubuntu 20.04系统上如何更改用户名&#xff0c;本文以IDO-EVB3588开发板为例&#xff0c;在ubuntu20.04系统上修改用户名industio为usernew。 IDO-EVB3588开发板是一款基于RK3588平台的产品。该开发板集成了四核Cortex-A76和四…

扫描电子显微镜电子束辐射损伤和如何减轻

扫描电镜&#xff08;Scanning Electron Microscope, SEM&#xff09;是一种常用的材料表征技术&#xff0c;它通过聚焦电子束扫描样品表面&#xff0c;利用电子与样品相互作用产生的信号来获得高分辨率的形貌图像。然而&#xff0c;电子束的辐射可能会对样品造成损伤&#xff…

Qt6入门教程 13:QPushButton

目录 一.QPushButton 1.多选 2.互斥 3.设置菜单 4.图标按钮 4.1给按钮添加图标 4.2异形按钮 二.设置Qt样式表 一.QPushButton QPushButton是与QAbstractButton最接近的完全体按钮&#xff0c;它具备QAbstractButton的所有特性&#xff0c;并且支持设置菜单。 1.多选 …

2024幻兽帕鲁服务器,阿里云配置

阿里云幻兽帕鲁服务器Palworld服务器推荐4核16G配置&#xff0c;可以选择通用型g7实例或通用算力型u1实例&#xff0c;ECS通用型g7实例4核16G配置价格是502.32元一个月&#xff0c;算力型u1实例4核16G是432.0元/月&#xff0c;经济型e实例是共享型云服务器&#xff0c;价格是32…

docker容器运维命令

文章目录 docker psdocker execdocker inspectdocker topdocker attachdocker waitdocker exportdocker importdocker portdocker cpdocker diffdocker renamedocker statsdocker update总结 docker ps 列出容器。 docker ps [OPTIONS]OPTIONS说明&#xff1a; -a :显示所有的…

Python列表中的append功能及用法举例

Python列表中的append功能及用法举例 &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333;append()&#x1f333;&#x1f340;功能介绍&#x1f340;&#x1f340;语法&#x1f340;&#x1f340;示例&#x1f340;&#x1f340;注意事项&#x…

总结NB-IoT模块和单片机的区别

在学习了NB-IoT模块后&#xff0c;紧接着又学习了单片机系统&#xff0c;单片机和NB-IoT模块有什么不同之处呢&#xff0c;总结为以下几点。 大纲如图&#xff1a; 一、硬件层面 1、采用芯片不同&#xff0c; &#xff08;1&#xff09;封装&#xff1a;封装尺寸、方式不同&a…

网络原理,网络通信以及网络协议

​​​​&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录专栏&#xff1a;网络原理,网络通信以及网络协议 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 网络原理概念网络通信局域网LAN广域网WAN 网络通信IP地址端口号…

VUE3动漫影视视频网站模板源码

文章目录 1.视频设计来源1.1 主界面1.2 动漫、电视剧、电影视频界面1.3 播放视频界面1.4 娱乐前线新闻界面1.5 关于我们界面 2.效果和源码2.1 动态效果2.2 源码结构 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/deta…

MyBatis概述与MyBatis入门程序

MyBatis概述与MyBatis入门程序 一、MyBatis概述二、入门程序1.准备开发环境&#xff08;1&#xff09;准备数据库&#xff08;2&#xff09;创建一个maven项目 2.编写代码&#xff08;1&#xff09;打包方式和引入依赖&#xff08;2&#xff09;新建mybatis-config.xml配置⽂件…

Web前端开发工具总结

一、nvm&#xff0c;node&#xff0c;npm之间的区别 nodejs&#xff1a;在项目开发时的所需要的代码库。相当于JDK npm&#xff1a;nodejs 包管理工具&#xff0c;npm 可以管理 nodejs 的第三方插件。在安装的 nodejs 的时候&#xff0c;npm 也会跟着一起安装。相当于Maven。 …

Redis学习——高级篇③

Redis学习——高级篇③ Redis7高级之缓存双写一致性之更新策略探讨&#xff08;三&#xff09; 1.缓存双写一致性2.数据库和缓存一致性的几种更新策略2.1 可停机的情况2.2 不可停机的情况,四种更新策略&#xff08;推荐最后一种&#xff0c;看场景&#xff09;1.❌先…

Java8-Stream 流基本应用-groupBy进行分组

groupBy进行分组 Testpublic void testStreamGroupBy(){List<UserInfoModel> resultnew ArrayList<>();for (int i 0; i < 10; i) {UserInfoModel usernew UserInfoModel();user.setUserId(i"");user.setUserName("kangshihang");result.a…

【代码随想录-链表】移除链表元素

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

Kafka(九)跨集群数据镜像

目录 1 跨集群镜像的应用场景1.1 区域集群和中心集群1.2 高可用(HA)和灾备(DR)1.3 监管与合规1.4 云迁移1.5 聚合边缘集群的数据 2 多集群架构2.1 星型架构2.2 双活架构2.2 主备架构2.2.1 如何实现Kafka集群的故障转移2.2.1.1 故障转移包括的内容1. 灾难恢复计划2. 非计划内的故…

DELMIAWORKS核心优势深度探析

一&#xff1a;制造BOM从产品结构到制造场景 BOM是所有制造系统的核心&#xff0c;DELMIAWORKS作为专业的制造运营系统&#xff0c;在理解制造BOM方面的能力已经超越了传统系统的范畴。其与传统系统的最大区别在于DELMIAWORKS将焦点放在制造场景的描述上&#xff0c;而非产品结…

259:vue+openlayers: 显示海量多边形数据,10ms加载完成

第259个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers项目中通过WebGLVectorLayerRenderer方式加载海量多边形数据。这里相当于将海量的数据放在同一个层的source中,然后通过webglTile的方式渲染出这一层。 本示例数据为5000个多边形,加载速度超级快。 直接…