当前位置: 首页 > news >正文

【C++高并发服务器WebServer】-6:信号

在这里插入图片描述

本文目录

  • 信号的概念
    • 1.1 core文件
    • 1.2 kill命令
    • 1.3 alarm函数
    • 1.4 setitimer调用
    • 1.5 signal捕捉信号
    • 1.6 信号集
    • 1.7 内核实现信号捕捉的过程
    • 1.8 sigaction
    • 1.9 sigchld

信号的概念

信号是 Linux 进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下:

  • 对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C 通常会给进程 发送一个中断信号。
  • 硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令,诸如被 0 除,或者引用了无法访问的内存区域。
  • 系统状态变化,比如 alarm 定时器到期将引起 SIGALRM 信号,进程执行的 CPU 时间超限,或者该进程的某个子进程退出。
  • 运行 kill 命令或调用 kill 函数。

使用信号主要是两个目的,一个是让进程知道已经发生了一个特定的事情,强迫进程执行它自己代码中的信号处理程序。

信号的特点有:简单、不能携带大量信息、满足某个特定条件才能发送、优先级比较高。

查看系统定义的信号列表:kill -l,前31个信号为常规信号,其余为实时信号。

在这里插入图片描述
在这里插入图片描述
下面是所有进程对应的事件表。
在这里插入图片描述
信号的5种默认处理动作有:Term终止进程、Ign当前进程忽略这个信号、Core终止进程 并且生成一个Core文件(保存进程异常退出的错误信息)、Stop暂停当前进程、Cont继续执行当前被暂停的进程。

信号的三种状态:产生、未决、递达。其中比较特殊的是SIGKILL和SIGSTOP,信号不能 被捕捉、阻塞或者忽略,只能执行默认操作。

1.1 core文件

我们 创建一个简单的代码,就是如下的代码。然后试着编译运行,会发现会报错:段错误(核心已转储)

#include <stdio.h>
#include <string.h>int main() {char * buf;//strcpy(buf, "hello");return 0;
}

通过命令ulimit -a可以查看到对应的文件大小,但是core file size这是0,所以我们可以进行对应的修改。
在这里插入图片描述
通过命令ulimit -c 1024我们可以进行修改,修改之后可以再进行对应的查看。

在这里插入图片描述
通过编译上面的代码为core.c然后运行,会发现生成的core文件很大,如下所示:
在这里插入图片描述
通过命令gdb a.out进入到调试界面,然后输入命令core-file core可以查看对应的core文件信息。

看到core中写着:程序通过SIGSEGV(访问了非法内存)信号终止了,后面也有对应的出错代码位置。

在这里插入图片描述

1.2 kill命令

#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int sig);- 功能:给任何的进程或者进程组pid, 发送任何的信号 sig- 参数:- pid :> 0 : 将信号发送给指定的进程= 0 : 将信号发送给当前的进程组= -1 : 将信号发送给每一个有权限接收这个信号的进程< -1 : 这个pid=某个进程组的ID取反 (-12345)- sig : 需要发送的信号的编号或者是宏值(不同的系统,编号可能会不一样),一般使用宏值,0表示不发送任何信号kill(getppid(), 9);kill(getpid(), 9);int raise(int sig);- 功能:给当前进程发送信号- 参数:- sig : 要发送的信号- 返回值:- 成功 0- 失败 非0kill(getpid(), sig); 这个命令等同于raise(9);void abort(void);- 功能: 发送SIGABRT信号给当前的进程,杀死当前进程kill(getpid(), SIGABRT);

通过下面的demo可以模拟对应的kill命令的过程。

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>int main() {pid_t pid = fork();if(pid == 0) {// 子进程int i = 0;for(i = 0; i < 5; i++) {printf("child process\n");sleep(1);}} else if(pid > 0) {// 父进程printf("parent process\n");sleep(2);printf("kill child process now\n");kill(pid, SIGINT);}return 0;
}

1.3 alarm函数

#include <unistd.h>
unsigned int alarm(unsigned int seconds);- 功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,函数会给当前的进程发送一个信号:SIGALARM- 参数:seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。取消一个定时器,通过alarm(0)。- 返回值:- 之前没有定时器,返回0- 之前有定时器,返回之前的定时器剩余的时间- SIGALARM :默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。alarm(10);  -> 返回0 (第一次调用)过了1秒alarm(5);   -> 返回9(返回之前的定时器剩余时间)alarm(100) -> 该函数是不阻塞的
#include <stdio.h>
#include <unistd.h>int main() {int seconds = alarm(5);printf("seconds = %d\n", seconds);  // 0sleep(2);seconds = alarm(2);    // 不阻塞printf("seconds = %d\n", seconds);  // 3while(1) { //设置while循环观察alarm(2)的效果}return 0;
}

定时器与进程的状态无关(就算进程中有sleep),alarm也会继续,alarm采用的时自然定时法。

进程实际运行的时间 = 内核时间+用户时间+消耗的时间(IO等)

1.4 setitimer调用

首先,struct itimerval是一个结构体,用于定义定时器的参数。它的定义如下:

struct itimerval {struct timeval it_interval; // 定时器的间隔时间struct timeval it_value;    // 定时器的初始时间
};

其中struct timeval的定义为,也就是这个itimerval是多级嵌套的结构体:

struct timeval {time_t tv_sec;  // 秒suseconds_t tv_usec; // 微秒
};
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);- 功能:设置定时器(闹钟)。可以替代alarm函数。精度微秒us,可以实现周期性定时- 参数:- which : 定时器以什么时间计时ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM   常用ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRMITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF- new_value: 设置定时器的属性struct itimerval {      // 定时器的结构体struct timeval it_interval;  // 每个阶段的时间,间隔时间struct timeval it_value;     // 延迟多长时间执行定时器};struct timeval {        // 时间的结构体time_t      tv_sec;     //  秒数     suseconds_t tv_usec;    //  微秒    };- old_value :记录上一次的定时的时间参数,一般不使用,指定NULL- 返回值:成功 0失败 -1 并设置错误号

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>// 过3秒以后,每隔2秒钟定时一次
int main() {struct itimerval new_value;// 设置间隔的时间new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 设置延迟的时间,3秒之后开始第一次定时new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的,执行完这个命令后下面的printf会立即执行。printf("定时器开始了...\n");if(ret == -1) {perror("setitimer");exit(0);}return 0;
}

1.5 signal捕捉信号

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);- 功能:设置某个信号的捕捉行为- 参数:- signum: 要捕捉的信号- handler: 捕捉到信号要如何处理- SIG_IGN : 忽略信号- SIG_DFL : 使用信号默认的行为- 回调函数 :  这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。回调函数:- 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义- 不是程序员调用,而是当信号产生,由内核调用- 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。- 返回值:成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL失败,返回SIG_ERR,设置错误号

比较特别的是: SIGKILL SIGSTOP不能被捕捉,不能被忽略。

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>void myalarm(int num) {printf("捕捉到了信号的编号是:%d\n", num);printf("xxxxxxx\n");
}// 过3秒以后,每隔2秒钟定时一次
int main() {// 注册信号捕捉// signal(SIGALRM, SIG_IGN);// signal(SIGALRM, SIG_DFL);// void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉到的信号的值。signal(SIGALRM, myalarm);struct itimerval new_value;// 设置间隔的时间new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 设置延迟的时间,3秒之后开始第一次定时new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定时器开始了...\n");if(ret == -1) {perror("setitimer");exit(0);}getchar();return 0;
}

1.6 信号集

许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t。

在 PCB 中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未决信号集”。这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改。

信号的“未决”是一种状态,指的是从信号的产生到信号被处理前的这一段时间。
信号的“阻塞”是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。

进程的内核区有PCB,PCB中有PID、PPID、文件描述符等,同时也有未决信号集、阻塞信号集等这些东西。

在这里插入图片描述

来简述一下未决信号集和阻塞信号集的过程:

第一段:信号的产生与未决状态

用户通过键盘按下 Ctrl + C,会触发系统产生编号为2的信号 SIGINT。然而,信号产生后并未立即被处理,而是处于“未决”状态。在内核中,所有未被处理的信号会被存储在一个特殊的集合中,称为“未决信号集”。对于 SIGINT 信号,其状态被记录在未决信号集的第二个标志位上。如果该标志位的值为 0,则表示信号当前不是未决状态;如果值为 1,则表示信号处于未决状态,等待被处理。

第二段:信号处理与阻塞机制

在处理未决信号之前,需要将其与另一个信号集——“阻塞信号集”进行比较。阻塞信号集默认情况下不阻塞任何信号,但用户可以通过调用系统的API来设置某些信号的阻塞状态。在处理信号时,系统会查询阻塞信号集中对应的标志位,判断该信号是否被设置为阻塞。如果未被阻塞,信号将被正常处理;如果被阻塞,则信号继续保持未决状态,直到阻塞解除后,信号才会被处理。

以下信号集相关的函数都是对自定义的信号集进行操作。int sigemptyset(sigset_t *set);- 功能:清空信号集中的数据,将信号集中的所有的标志位置为0- 参数:set,传出参数,需要操作的信号集- 返回值:成功返回0, 失败返回-1int sigfillset(sigset_t *set);- 功能:将信号集中的所有的标志位置为1- 参数:set,传出参数,需要操作的信号集- 返回值:成功返回0, 失败返回-1int sigaddset(sigset_t *set, int signum);- 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号- 参数:- set:传出参数,需要操作的信号集- signum:需要设置阻塞的那个信号- 返回值:成功返回0, 失败返回-1int sigdelset(sigset_t *set, int signum);- 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号- 参数:- set:传出参数,需要操作的信号集- signum:需要设置不阻塞的那个信号- 返回值:成功返回0, 失败返回-1int sigismember(const sigset_t *set, int signum);- 功能:判断某个信号是否阻塞- 参数:- set:需要操作的信号集- signum:需要判断的那个信号- 返回值:1 : signum被阻塞0 : signum不阻塞-1 : 失败
#include <signal.h>
#include <stdio.h>
#include <bits/types/sigset_t.h>int main() {// 创建一个信号集sigset_t set;// 清空信号集的内容sigemptyset(&set);// 判断 SIGINT 是否在信号集 set 里int ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 添加几个信号到信号集中sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);// 判断SIGINT是否在信号集中ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 判断SIGQUIT是否在信号集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}// 从信号集中删除一个信号sigdelset(&set, SIGQUIT);// 判断SIGQUIT是否在信号集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}return 0;
}

运行上面的代码,可以看到下面的结果。

在这里插入图片描述

1.7 内核实现信号捕捉的过程

在这里插入图片描述

1.8 sigaction

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);- 功能:检查或者改变信号的处理。信号捕捉- 参数:- signum : 需要捕捉的信号的编号或者宏值(信号的名称)- act :捕捉到信号之后的处理动作- oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL- 返回值:成功 0失败 -1struct sigaction {// 函数指针,指向的函数就是信号捕捉到之后的处理函数void     (*sa_handler)(int);// 不常用void     (*sa_sigaction)(int, siginfo_t *, void *);// 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。sigset_t   sa_mask;// 使用哪一个信号处理对捕捉到的信号进行处理// 这个值可以是0,表示使用sa_handler; 也可以是SA_SIGINFO表示使用sa_sigactionint        sa_flags;// 被废弃掉了void     (*sa_restorer)(void);
};
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>void myalarm(int num) {printf("捕捉到了信号的编号是:%d\n", num);printf("xxxxxxx\n");
}// 过3秒以后,每隔2秒钟定时一次
int main() {struct sigaction act;act.sa_flags = 0;act.sa_handler = myalarm;sigemptyset(&act.sa_mask);  // 清空临时阻塞信号集// 注册信号捕捉sigaction(SIGALRM, &act, NULL);struct itimerval new_value;// 设置间隔的时间new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 设置延迟的时间,3秒之后开始第一次定时new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定时器开始了...\n");if(ret == -1) {perror("setitimer");exit(0);}// getchar();while(1);return 0;
}

1.9 sigchld

SIGCHLD信号在三种情况下会被发送给父进程:当子进程结束、子进程暂停(例如被信号暂停执行)或子进程从暂停状态恢复运行时。然而,默认情况下,父进程会忽略SIGCHLD信号。通过正确处理SIGCHLD信号,父进程可以在子进程结束时及时调用wait或waitpid函数来收集子进程的状态信息并清理其资源,从而避免子进程变成僵尸进程。这种方法是解决僵尸进程问题的有效手段。

下面的demo是创建多个子进程,并在父进程中处理子进程结束时产生的SIGCHLD信号。为了避免在信号处理函数注册完成之前子进程就已经结束并发送SIGCHLD信号,代码首先将SIGCHLD信号阻塞,直到信号处理函数注册完成后再解除阻塞。父进程会持续运行并打印其进程ID,而子进程会在创建后立即退出。

首先初始化信号集并阻塞SIGCHLD信号:使用sigset_t类型定义一个信号集set,并通过sigemptyset将其初始化为空。
用sigaddset将SIGCHLD信号添加到信号集中。调用sigprocmask,将SIGCHLD信号添加到当前进程的信号屏蔽集中,从而阻塞SIGCHLD信号,防止其在信号处理函数注册完成之前被处理。

使用fork函数创建多个子进程。在for循环中,每次调用fork都会创建一个子进程。子进程在创建后立即退出,而父进程会继续运行。子进程退出时会向父进程发送SIGCHLD信号,但由于信号已经被阻塞,因此不会立即触发信号处理函数。

在父进程中,定义一个struct sigaction结构体act,并设置其sa_handler为自定义的信号处理函数myFun。
使用sigemptyset清空act.sa_mask,确保信号处理函数执行时不会被其他信号中断。
调用sigaction将SIGCHLD信号的处理函数设置为myFun。
调用sigprocmask,将SIGCHLD信号从信号屏蔽集中移除,解除阻塞,允许信号被处理。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>
#include <bits/types/sigset_t.h>
#include <bits/sigaction.h>void myFun(int num) {printf("捕捉到的信号 :%d\n", num);// 回收子进程PCB的资源// while(1) {//     wait(NULL); // }while(1) {int ret = waitpid(-1, NULL, WNOHANG);if(ret > 0) {printf("child die , pid = %d\n", ret);} else if(ret == 0) {// 说明还有子进程或者break;} else if(ret == -1) {// 没有子进程break;}}
}int main() {// 提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完信号捕捉sigset_t set;sigemptyset(&set);sigaddset(&set, SIGCHLD);// 这是一个函数,用于设置当前进程的信号屏蔽集。// 信号屏蔽集决定了哪些信号会被暂时阻塞(即不会被立即处理)。// SIG_BLOCK:这是一个常量,表示将信号集中的信号添加到当前进程的信号屏蔽集中,即阻塞这些信号。sigprocmask(SIG_BLOCK, &set, NULL);// 创建一些子进程pid_t pid;for(int i = 0; i < 20; i++) {pid = fork();if(pid == 0) {break;}}if(pid > 0) {// 父进程// 捕捉子进程死亡时发送的SIGCHLD信号struct sigaction act;act.sa_flags = 0;act.sa_handler = myFun;sigemptyset(&act.sa_mask);sigaction(SIGCHLD, &act, NULL);// 注册完信号捕捉以后,解除阻塞  sigprocmask(SIG_UNBLOCK, &set, NULL);while(1) {printf("parent process pid : %d\n", getpid());sleep(2);}} else if( pid == 0) {// 子进程printf("child process pid : %d\n", getpid());}return 0;
}

相关文章:

【C++高并发服务器WebServer】-6:信号

本文目录 信号的概念1.1 core文件1.2 kill命令1.3 alarm函数1.4 setitimer调用1.5 signal捕捉信号1.6 信号集1.7 内核实现信号捕捉的过程1.8 sigaction1.9 sigchld 信号的概念 信号是 Linux 进程间通信的最古老的方式之一&#xff0c;是事件发生时对进程的通知机制&#xff0c…...

HBase的原理

一、什么是HBase HBase是一个分布式&#xff0c;版本化&#xff0c;面向列的数据库&#xff0c;依赖Hadoop和Zookeeper &#xff08;1&#xff09;HBase的优点 提供高可靠性、高性能、列存储、可伸缩、实时读写的数据库系统 (2) HBase 表的特性 Region包含多行 列族包含多…...

[b01lers2020]Life on Mars1

打开题目页面如下 看了旁边的链接&#xff0c;也没有什么注入点&#xff0c;是正常的科普 利用burp suite抓包&#xff0c;发现传参 访问一下 http://5edaec92-dd87-4fec-b0e3-501ff24d3650.node5.buuoj.cn:81/query?searchtharsis_rise 接下来进行sql注入 方法一&#xf…...

Go学习:常量

变量&#xff1a;程序运行期间&#xff0c;可以改变的量&#xff0c;变量声明需要使用 var 常量&#xff1a;程序运行期间&#xff0c;不可以改变的量&#xff0c;常量声明需要使用 const 目录 1. 常量不允许修改 2. 常量赋值不使用 : 3. 常量能够自动推导类型 1. 常量不允许…...

Python 爬虫——爬取Web页面图片

从网页页面上批量下载jpg格式图片&#xff0c;并按照数字递增命名保存到指定的文件夹。 Web地址&#xff1a;http://p.weather.com.cn/2017/06/2720826.shtml#p1 import urllib import urllib.request import re #正则表达式#解析页面 def load_page(url):requesturllib.reque…...

微信小程序1.1 微信小程序介绍

1.1 微信小程序介绍 内容提要 1.1 什么是微信小程序 1.2 微信小程序的功能 1.3 微信小程序使用场景 1.4 微信小程序能取代App吗 1.5 微信小程序的发展历程 1.6微信小程序带来的机会...

记录备战第十六届蓝桥杯的过程

1.学会了原来字符串也有比较方法&#xff0c;也就是字符串987 > 98 等等&#xff0c;可以解决拼最大数问题 题目链接&#xff1a;5.拼数 - 蓝桥云课 (lanqiao.cn) 2.今天又复习了一下bfs&#xff0c;感觉还是很不熟练&#xff0c;可能是那个过程我些许有点不熟悉&#xff…...

AI 编程工具—Cursor进阶使用 Rules for AI

AI 编程工具—Cursor进阶使用 Rules for AI 这里配置是给所有的会话和内嵌模式的,你可以理解为是一个全局的配置 下面的代码是之前Cursor 给我们生成的,下面我们开始配置Rules ,来让Cursor生成的代码更加符合我们的编程习惯 def quick_sort(arr):"""使用快…...

以租赁合同的例子讲清楚 开源协议原理和区别

开源协议通俗易懂的方式介绍清楚原理和区别 开源协议其实就是软件的“使用规则”&#xff0c;决定了别人可以如何使用、修改、分享你的代码。通俗一点说&#xff0c;如果你写了一段代码&#xff0c;开源协议就是告诉别人在什么条件下他们可以使用你的代码&#xff0c;以及他们可…...

mysql如何修改密码

在MySQL中修改密码可以通过多种方式完成&#xff0c;具体取决于你的MySQL版本和你是否有足够的权限。以下是一些常用的方法来修改MySQL用户的密码&#xff1a; 方法1: 使用ALTER USER命令 这是最常用的方法&#xff0c;适用于MySQL 5.7及以上版本。 ALTER USER usernameloca…...

解数独力扣

题目 解题思路 1.双层循环每一个位置都要去判断能不能放数字 2.每到一个位置如果为空&#xff0c;for循环遍历1-9&#xff0c;通过函数判断是否能放这个数字能放开始回溯判断放下这个数字之后 3.不设结束条件&#xff0c;一直循环判断下去知道所有位置全部填满数字然后retur…...

Zookeeper(28)Zookeeper的线性化写入和顺序一致性读是什么?

Zookeeper 是一个分布式协调服务&#xff0c;它在设计上提供了强一致性的保证&#xff0c;其中包括线性化写入和顺序一致性读。这两种一致性模型确保了在分布式系统中数据的一致性和操作的确定性。 线性化写入&#xff08;Linearizable Writes&#xff09; 线性化写入保证在任…...

ARM嵌入式学习--第九天(串口通信)

--串行与并行通信介绍 通信方式是指双方之间的工作方式或信号传输方式&#xff0c;终端与其他设备&#xff08;例如其他终端&#xff0c;计算机和外部设备&#xff09;通过数据传输进行通信&#xff0c;根据数据的传输方式&#xff0c;有串行通信和并行通信 -并行通信 利用多条…...

Github 2025-01-25Rust开源项目日报Top10

根据Github Trendings的统计,今日(2025-01-25统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10Python项目1Vue项目1JavaScript项目1Deno: 现代JavaScript和TypeScript运行时 创建周期:2118 天开发语言:Rust, JavaScript协议类型…...

Android BitmapShader简洁实现马赛克/高斯模糊(毛玻璃),Kotlin(三)

Android BitmapShader简洁实现马赛克/高斯模糊&#xff08;毛玻璃&#xff09;&#xff0c;Kotlin&#xff08;三&#xff09; 发现&#xff0c;如果把&#xff08;二&#xff09; Android BitmapShader简洁实现马赛克&#xff0c;Kotlin&#xff08;二&#xff09;-CSDN博客 …...

PCIE模式配置

对于VU系列FPGA&#xff0c;当DMA/Bridge Subsystem for PCI Express IP配置为Bridge模式时&#xff0c;等同于K7系列中的AXI Memory Mapped To PCI Express IP。...

python深入SQLAlchemy使用详解

上次发布《多种方式访问mysql的对比分析》一文后&#xff0c;有读者留言&#xff0c;说SQLAlchemy的使用方法没讲清楚&#xff0c;只有一段简短的介绍&#xff0c;演示代码也比较模糊&#xff0c;SQLAlchemy在实际项目运用非常广泛&#xff0c;由于其支持 ORM 模型&#xff0c;…...

Bootstrap4 模态框

Bootstrap4 模态框 Bootstrap 是一个流行的前端框架,它可以帮助开发者快速构建响应式、移动设备优先的网站和应用程序。Bootstrap 4 是其最新版本,提供了许多易于使用的组件,其中模态框(Modal)组件是其中之一。本文将详细介绍 Bootstrap 4 模态框的用法、特性和优化技巧。…...

GSI快速收录服务:让你的网站内容“上架”谷歌

辛苦制作的内容无法被谷歌抓取和展示&#xff0c;导致访客无法找到你的网站&#xff0c;这是会让人丧失信心的事情。GSI快速收录服务就是为了解决这种问题而存在的。无论是新上线的页面&#xff0c;还是长期未被收录的内容&#xff0c;通过我们的技术支持&#xff0c;都能迅速被…...

vim如何设置制表符表示的空格数量

:set tabstop4 设置制表符表示的空格数量 制表符就是tab键&#xff0c;一般默认是四个空格的数量 示例&#xff1a; &#xff08;vim如何使设置制表符表示的空格数量永久生效&#xff1a;vim如何使相关设置永久生效-CSDN博客&#xff09;...

R语言AI模型部署方案:精准离线运行详解

R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...