嵌入式 Linux多进程

news2025/1/7 5:34:20

目录

一、Linux进程概述

1、进程标识

2、进程的运行身份

3、进程的状态

4、Linux下进程的结果及管理

5、一些进程相关信息&相关命令

进程process:

进程相关命令:

 二、Linux进程创建与控制

1、fork进程创建函数

2、进程的终止

3、wait和waitpid函数

4、exec函数族

5、system函数

三、守护进程

1、概念

2、进程守护的特性 

3、daemon进程的编程规则 

 4、daemon库函数


一、Linux进程概述

进程是一个程序一次执行的过程,是操作系统动态执行的基本单元。

进程的概念主要有两点

第一,进程是一个实体

  每个进程都有自己的虚拟地址空间,包括文本区数据区、和堆栈区

  文本区域存储处理器执行的代码;

  数据区存储变量和动态分配的内存;

  堆栈区存储着活动进程调用的指令和本地变量。

第二,进程是一个“执行中的程序”

  它和程序有本质区别。

  程序是静态的,它是一些保存在磁盘上的指令的有序集合;

  而进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建、调度和消亡

的过程,是 Linux 的基本调度单位。

只有当处理器赋予程序生命时,它才能成为一个活动的实体,称之为进程

 内核的调度器负责在所有的进程间分配 CPU 执行时间,称为时间片(time slice),它轮流在每个进程分得的时间片用完后从进程那里抢回控制权。

1、进程标识

OS会为每个进程分配一个唯一的整型 ID,做为进程的标识号(PID)。查看进程命令为:ps -ef

进程 0 是调度进程,常被成为交换进程,它不执行任何程序,是内核的一部分,因此也被称为系统进程

进程除了自身的ID外,还有父进程ID(PPID)

也就是说每个进程都必须有它的父进程,操作系统不会无缘无故产生一个新进程。

所有进程的祖先进程是同一个进程,它叫做 init 进程ID为1,init进程是内核自举后的第一个启动的进程。init 进程负责引导系统、启动守护(后台)进程并且运行必要的程序。它不是系统进程,但它以系统的超级用户特权运行。

2、进程的运行身份

进程在运行过程中,必须具有一类似于用户的身份,以便进行进程的权限控制。

缺省情况下,哪个登录用户运行程序,该程序进程就具有该用户的身份。

1)进程真实的用户ID和组ID

假设当前登录用户为gotter,他运行了ls程序,则ls在运行过程中就具有 gotter 的身份,该ls进程的用户ID和组ID分别为gotter和gotter所属的组。

这类型的ID叫做进程的真实用户ID真实组ID

真实用户ID和真实组 ID可以通过函数 getuid()和 getgid()获得。

2)进程有效用户ID和有效组ID

与真实ID对应,进程还具有有效用户ID有效组ID的属性,内核对进程的访问权限检查时,它检查的是进程的有效用户ID和有效组ID,而不是真实用户ID和真实组ID。

缺省情况下,用户的(有效用户ID和有效组ID)与(真实用户 ID 和真实组 ID)是相同的

有效用户id和有效组id通过函数 geteuid()和 getegid()获得。

实例:

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

int main()
{
 	printf("uid:%d gid:%d euid:%d egid:%d\n",
           getuid(),getgid(),geteuid(),getegid());
 	return 0;
}

测试:可以使用id命令查看用户gid,uid和组

[root@localhost process]# id

uid=0(root) gid=0(root) groups=0(root)……

[root@localhost process]#

编译生成可执行文件 main.out,程序文件的属性可能为:

[root@localhost process]# ls -l main.out

-rwxr-xr-x 1 root root 5133 11月 16 21:42 main.out

执行结果可能为:

[root@localhost process]# gcc -o main.out main.c

[root@localhost process]# ./main.out

uid:0 gid:0 euid:0 egid:0

[root@localhost process]#

现在将 main.out 的所有者可执行属性改为s 。

  [root@localhost process]# chmod u+s main.out

[root@localhost process]# ls -l

-rwsr-xr-x 1 root root 5133 11月 16 21:42 a.out

 可以看到,进程的有效用户身份变为了root,而不是cxx 了。

这是因为文件main.out的访问权限的所有者可执行为设置了s的属性,设置了该属性以后,用户运行main.out 时,main.out进程的有效用户身份将不再是运行main.out的用户,而是main.out文件的所有者

问题:怎么查看正在执行的main函数进程?

需要在main.c中添加while(1)循环,然后重新打开一个终端(快捷键:ctrl+shift+t),然后输入ps -ef命令即可查看。

3、进程的状态

进程是程序的执行过程,根据它的生命周期可以划分成 3 种状态;

  1. 执行态:该进程正在运行,即进程正在占用 CPU。
  2. 就绪态:进程已经具备执行的一切条件,正在等待分配 CPU 的处理时间片。
  3. 等待态:进程不能使用 CPU,若等待事件发生(等待的资源分配到)则可将其唤醒。

4、Linux下进程的结果及管理

Linux 系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。也就是说,进程之间是分离的任务,拥有各自的权利和责任。

其中,每个进程都运行在各自独立的虚拟地址空间,因此,即使一个进程发生了异常,它也不会影响到系统的其他进程。

 Linux中的进程包含以下几个部分

1)“数据段”:

放全局变量、常数以及动态数据分配的数据空间。数据段分成普通数据段(包括可读可写/只读数据段,存放静态初始化的全局变量或常量)、 BSS 数据段(存放未初始化的全局变量)以及(存放动态分配的数据)。

  1. “正文段”:

存放的是 CPU 执行的机器指令部分。

  1. “堆栈段”:

存放的是子程序的返回地址、子程序的参数以及程序的局部变量等

5、一些进程相关信息&相关命令

进程process:

是OS的最小单元,地址空间大小为4G(0 ~ 4G-1),其中1G给OS3G给进程(进程的可寻址空间){代码区 数据区 堆栈}

进程相关命令

 二、Linux进程创建与控制

1、fork进程创建函数

原型:

#include <unistd.h>
pid_t fork(void);
pid_t vfork(void);

在 linux 中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

返回值说明

它执行一次返回两个值(一般的函数只有1个返回值)。

其中父进程的返回值是子进程的进程号。

而子进程的返回值为 0。

若出错则返回-1。

因此可以通过返回值来判断是父进程还是子进程。

fork函数创建子进程的过程说明

1)使用 fork 函数得到的子进程是父进程的一个复制品

它从父进程继承了进程的地址空间,包括进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端,而子进程所独有的只有它的进程号、资源使用和计时器等。

2)通过这种复制方式创建出子进程后,原有进程和子进程都从函数fork返回,各自继续往下运行。

也就是说, fork不仅仅复制了进程的资源,更复制了原有进程的运行状态。所以复制出的新的进程(子进程)虽然什么都和父进程一样,但它从 fork 函数后面开始运行。

3)但是原进程的 fork 返回值与子进程的 fork 返回值不同

在原进程中,fork返回子进程的pid;

而在子进程中,fork返回 0;

如果fork返回负值,表示创建子进程失败。

  1. vfork 函数和fork函数:

相同点:它的作用和返回值与 fork 相同,二者都创建一个子进程。

不同点

(1)vfork函数并不是将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或 exit),所以也就不会存放该地址空间。

(2)vfork 保证子进程先比父进程先运行,在它调用exec 或 exit之后父进程才可能被调度运行。

(3) fork:子进程拷贝父进程的数据段,父子进程执行次序不能确定;

vfork: 子进程与父进程共享数据段,子进程先运行,父进程后运行;

实例:

#include <stdio.h>
#include <unistd.h>
int main()
{
 	fork();
 	fork();
 	fork();
 	printf("Hello World\n");
 	return 0;
}
自己运行上面的程序看看结果是什么,并思考为什么会这样。
如果有多个 fork()呢?并输出其返回值。

示例 2:
#include <stdio.h>
#include <unistd.h>
//int m=100; //数据段

int main()
{
 	int m = 100; //堆栈段
 	printf("aaaa\n");
 	int n = fork();//把fork()改为vfork();后再次验证结果
 	if(n > 0){
//wait();
 		m++;
 		printf("bbbb n = %d m = %d\n", n, m);
 	}
 	else{
 			printf("cccc n = %d m = %d\n", n, m);
 	}
 	return 0;
//注意看m n的值
}

用fork继承父进程打开的文件

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
 	char szBuf[32] = {'\0'};
 	int iFile = open("./a.txt", O_RDONLY);
 		//父进程
 		if(fork() > 0){
 		close(iFile);
 		return 0;
 	}
 	//子进程
 	sleep(3); //wait for parent process closing fd
 	read(iFile, szBuf, sizeof(szBuf)-1);
 	printf("string:%s\n",szBuf);
 	close(iFile);
 	return 0;
}

2、进程的终止

进程的终止有 5 种方式:

1)main函数的自然返回(return 0)。

2)调用exit函数。

3)调用_exit函数。

4)接收到某个信号。如 ctrl+c SIGINT ctrl+\ SIGQUIT

5)调用 abort 函数,它产生SIGABRT信号。所以是上一种方式的特例。

 前 3 种方式为正常的终止,后 2 种为非正常终止。

但是无论哪种方式,进程终止时都将执行相同的关闭打开的文件,释放占用的内存等

只是后两种终止会导致程序有些代码不会正常的执行,比如对象的析构、atexit函数的执行等。

 1)exit 和_exit 函数说明

exit 和_exit 函数都是用来终止进程的。

当程序执行到 exit 和_exit 时,进程会无条件的停止剩下的所有操作,清除包括PCB(进程控制块)在内的各种数据结构,并终止本程序的运行。

 exit 函数和_exit 函数的最大区别在于exit函数在退出之前会检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的“清理 I/O 缓冲”.

2)什么是缓冲I/O?

由于linux 的标准函数库中,有一种被称作“缓冲 I/O”操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。

每次读文件时,会连续读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区中读取;

同样,每次写文件的时候,也仅仅是写入内存中的缓冲区,等满足一定的条件(如达到一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。

这种技术大大增加了文件读写的速度但也为编程带来了麻烦

比如有一些数据,认为已经写入文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit 函数直接将进程关闭,缓冲区中的数据就会丢失。

因此,如想保证数据的完整性,建议使用 exit 函数

3)exit 和_exit函数原型

#include <stdlib.h> 		//exit 的头文件
#include <unistd.h>  		//_exit 的头文件
void exit(int status);
void _exit(int status);

status 是一个整型的参数,可以利用这个参数传递进程结束时的状态。

一般来说, 0 表示正常结束;其他的数值表示出现了错误,进程非正常结束

实例:

#include <stdio.h>
#include <stdlib.h>
int main()
{
 	printf("hello\n");
 	printf("world");
 	exit(0);
}

可以发现,调用exit 函数,缓冲区中的记录也能正常输出。

3、wait和waitpid函数

用fork函数启动一个子进程时,子进程就有了它自己的生命并将独立运行。

1)孤儿进程

如果父进程先于子进程退出,则子进程成为孤儿进程。此时将自动被PID为 1 的进程(即 init进程)接管

孤儿进程退出后,它的清理工作由祖先进程 init 自动处理。但在init进程清理子进程之前,它一直消耗系统的资源,所以要尽量避免。

编写一个孤儿进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
 	pid_t pid = fork();
 	if( pid == 0){
 		printf("子进程…\n");
 		while(1) ;
 	}
 	else{
 		printf("父进程 8 秒后退出…\n");
 		sleep(8);
 		printf("父进程退出\n");
 		exit(10);
 	}
}

2)僵尸进程 & wait和waitpid函数

如果子进程先退出,系统不会自动清理掉子进程的环境,而必须由父进程调用 wait 或 waitpid 函数来完成清理工作。

如果父进程不做清理工作,则已经退出的子进程将成为僵尸进程(defunct)

在系统中如果存在的僵尸(zombie)进程过多,将会影响系统的性能,所以必须对僵尸进程进行处理。

编写一个僵尸进程

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

int main()
{
 	pid_t pid = fork();
 	if( pid == 0 ){
 		exit(10);
 	}
 	else{
 			sleep(10);
 		//while(1);
 	}
}

通过用 ps –aux 快速查看发现Z的僵尸进程(要加while(1)的时候,用CTRL+Z退出)。

如何避免大量僵尸进程

  1. 方法一:改写父进程,调用waitpid()函数回收子进程资源。
  2. 方法二:杀死父进程,让子进程形成孤儿进程,由init进程接管。如在终端中输入命令
  3.  kill -9 5166表示杀死父进程ID为5166的进程。
  4. 方法三:关闭终端
  5. 方法四:重启系统

4、exec函数族

exec*由一组函数组成:

extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);

int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

int execle(const char *path, const char *arg , ..., char * const envp[]);
int execve(const char * path, char *const argv[], char *const envp[]);

exec函数族的作用运行第一个参数指定的可执行程序

exec不会创建一个新的进程,只是把原有的代码段、数据段替换,进程ID没有变。

exec 函数族提供了一个在进程中启动另一个程序执行的方法。

exec 函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。

exec与fork的区别:

这些函数族的工作过程与fork完全不同

 exec*参数说明:

 exec函数族的参数传递有两种方式

1)逐个列举的方式,

  2)将所有参数整体构造指针数组传递。

函数名有标识的字母来区分参数传递方式:

 

注意:

【1】对于有参数 envp 的函数,它会使用程序员自定义的环境变量。

【2】如果自定义的环境变量中包含了将要执行的可执行程序的路径,那么第一个参数中是不是我们就可以不用写全路径了呢?不是的,必须写全路径。

因为我们自定义的环境变量不是用来寻找这个可执行程序的,而是在这个可执行程序运行起来之后给新进程用的。

【3】可以用 env 命令查看环境变量

execl实例:

#include <unistd.h>
#include <stdio.h>

int main()
{
 	printf("aaaa\n");
 	execl("/bin/ls", "ls", "-l", NULL); //执行 ls -l指令
 	printf("bbbb\n");
 		printf("bbbb\n");
 	return 0;
}

5、system函数

函数如下:

实例:

 

#include <stdio.h>
#include <stdlib.h>

int main()
{
 	//system("ls -l"); 			//执行命令		
 	system("clear");		//表示清屏
 	return 0;
}

三、守护进程

1、概念

Daemon 运行在后台也称作“后台服务进程”。它是没有控制终端与之相连的进程。它独立于控制终端,通常周期的执行某种任务。

那么为什么守护进程要脱离终端后台运行呢?

守护进程脱离终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的任何终端信息所打断。

那么为什么要引入守护进程呢?

由于在linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依赖这个终端,这个终端就称为这些进程的控制终端

当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能突破这种限制,它被执行开始运转,直到整个系统关闭时才退出

几乎所有的服务器程序都用 daemon进程的形式实现,如:Apache 和wu-FTP,。

很多Linux下常见的命令,如inetd和ftpd,末尾的字母 d通常就是指 daemon

2、进程守护的特性 

1)守护进程最重要的特性是后台运行。

2)其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录已经文件创建掩码等。

这些环境通常是守护进程从父进程那里继承下来的。

3)守护进程的启动方式。

3、daemon进程的编程规则 

1)创建子进程,父进程退出

调用 fork产生一个子进程,同时父进程退出。

我们所有后续工作都在子进程中完成。这样做我们可以交出控制台的控制权,并为子进程作为进程组长作准备;由于父进程已经先于子进程退出,会造成子进程没有父进程,变成一个孤儿进程( orphan)。每当系统发现一个孤儿进程,就会自动由 1 号进程收养它,这样,原先的子进程就会变成1号进程的子进程。

2)在子进程中创建新会话

使用系统函数 setsid()。

由于创建守护进程的第一步调用了 fork 函数来创建子进程,再将父进程退出。

由于在调用 fork 函数的时候,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端并没有改变。

因此,还不是真正意义上的独立开来。

而调用 setsid 函数会创建一个新的会话并自任该会话的组长。

调用 setsid 函数有下面3个作用

  A.让进程摆脱原会话的控制,

  B.让进程摆脱原进程组的控制,

  C.让进程摆脱原控制终端的控制;

进程组:是一个或多个进程的集合。

进程组有进程组 ID 来唯一标识。

除了进程号(PID)之外,进程组 ID(GID)也是一个进程的必备属性。

每个进程都有一个组长进程,其组长进程的进程号等于进程组ID。且该进程组ID不会因为组长进程的退出而受影响。

会话周期:会话期是一个或多个进程组的集合。

通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。

控制终端:由于在linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依赖这个控制终端。

3)改变当前目录为根目录

使用 fork 函数创建的子进程继承了父进程的当前工作目录。

由于在进程运行中,当前目录所在的文件是不能卸载的,这对以后的使用会造成很多的不便。可以利用 chdir("/");把当前工作目录切换到根目录。

4)重设文件权限掩码

umask(0);-->将文件权限掩码设为 0,Deamon 创建文件不会有太大麻烦。

5)关闭所有不需要的文件描述符

新进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,而它们一直消耗系统资源。

另外守护进程已经与所属的终端失去联系,那么从终端输入的字符不可能到达守护进程,守护进程中常规方法(如 printf)输出的字符也不可能在终端上显示。所以通常关闭从 0到 MAXFILE 的所有文件描述符。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>

void Daemon()
{
 	const int MAXFD=64;
 	int i=0;

 	if(fork()!=0) 			//父进程退出
 		exit(0);
 	setsid();  				//成为新进程组组长和新会话领导,脱离控制终端
 	chdir("/");  			//设置工作目录为根目录
 		umask(0);  				//重设文件访问权限掩码
 	for(;i<MAXFD;i++) 		//尽可能关闭所有从父进程继承来的文件
 		close(i);
}
int main()
{
 	Daemon(); 				//成为守护进程
 	while(1){
 		sleep(1);
 	}
 	return 0;
}

 4、daemon库函数

原型:

#include <unistd.h>

int daemon(int nochdir, int noclose);

 

功能:创建一个守护进程.

参数说明

nochdir:=0将当前目录更改至“/”

noclose:=0将标准输入、标准输出、标准错误重定向至“/dev/null”

返回值说明

成功:0

失败:-1

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

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

相关文章

react setState学习记录

react setState学习记录1.总体看来2.setState的执行是异步的3.函数式setState1.总体看来 (1). setState(stateChange, [callback])------对象式的setState 1.stateChange为状态改变对象(该对象可以体现出状态的更改) 2.callback是可选的回调函数, 它在状态更新完毕、界面也更新…

【微信小程序】-- WXSS 模板样式- 全局样式和局部样式(十四)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

taobao.opensecurity.isv.uid.get( 获取open security uid for isv )

&#xffe5;免费不需用户授权 根据 open_uid 获取 open_uid_isv 用于同一个 isv的多个app间数据关联 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 点击获取key和secret 请求示例 TaobaoClient client new DefaultTaobaoClient(url, a…

你想要的Android性能优化系列:启动优化 !

App启动优化为什么要做App的启动优化&#xff1f;网页端存在的一个定律叫8秒定律&#xff1a;即指用户访问一个网站时&#xff0c;如果等待打开的时间超过8秒&#xff0c;超过70%的用户将会放弃等待。同样的&#xff0c;移动端也有一个8秒定律&#xff1a;如果一个App的启动时间…

华为OD机试题【乱序整数序列两数之和绝对值最小】用 C++ 编码,速通 (2023.Q1)

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明乱序整…

嵌入式学习笔记——概述

嵌入式系统概述前言“嵌入式系统”概念1.是个啥&#xff1f;2.可以干啥&#xff1f;3.有哪些入坑方向&#xff1f;4.入坑后可以有多少薪资&#xff1f;单片机1.什么是单片机&#xff1f;2.架构简介3.基于ARM架构的单片机结构简介总结前言 断更很长时间了&#xff0c;写博客确实…

【Leedcode】栈和队列必备的面试题(第四期)

【Leedcode】栈和队列必备的面试题&#xff08;第四期&#xff09; 文章目录【Leedcode】栈和队列必备的面试题&#xff08;第四期&#xff09;一、题目二、思路图解1.声明结构体2.循环链表开辟动态结构体空间3.向循环队列插入一个元素4.循环队列中删除一个元素5. 从队首获取元…

STM32C0介绍(1)----概述

概述 STM32C0系列微控制器是意法半导体公司推出的一款低功耗、高性能的微控制器产品。它们被设计用于需要小型、低功耗和高度可集成的应用程序&#xff0c;如传感器、消费品、电池供电设备、家庭自动化和安全等应用。该系列的微控制器采用ARM Cortex-M0内核&#xff0c;具有丰…

软件测试之缺陷

缺陷 1. 软件缺陷的概述 1.1 软件缺陷定义 软件缺陷, 通常又被叫做bug或者defect, 即为软件或程序中存在的某种破坏正常运行能力的问题、错误,其存在会导致软件产品在某种程度上不能满足用户的需求. 软件缺陷是指存在于软件(程序、数据、文档中的)那些不符合用户需求的问题.…

Ubuntu 交叉编译工具链安装

Ubuntu 交叉编译工具链安装 1 交叉编译器安装 ARM 裸机、Uboot 移植、Linux 移植这些都需要在 Ubuntu 下进行编译&#xff0c;编译就需要编译器&#xff0c;我们在第三章“Linux C 编程入门”里面已经讲解了如何在 Liux 进行 C 语言开发&#xff0c;里面使用 GCC 编译器进行代…

如何使用bomber扫描软件物料清单(SBOM)以查找安全漏洞

关于bomber bomber是一款针对软件物料清单&#xff08;SBOM&#xff09;的安全漏洞扫描工具&#xff0c;广大研究人员可以通过该工具扫描和检测软件物料清单&#xff08;SBOM&#xff09;。 当你向一家供应商索要了他们的一个封闭源代码产品的软件材料清单&#xff0c;而他们…

Spring6全面详解

Spring6全面详解 自2022年11月&#xff0c;Spring6正式发布。在Spring6中&#xff0c;对于JDK的要求最低为 17。&#xff08;17 - 19&#xff09; 部分文本摘抄于尚硅谷视频&#xff08;bilibili&#xff09;做了一定的排版和个人的理解。如果不是很熟悉&#xff0c;可以去看 …

ABAP 辨析 标准表|排序表|哈希表

1、文档介绍 本文档将介绍内表的区别和用法&#xff0c;涉及标准表、排序表、哈希表 2、用法与区别 2.1、内表种类 内表顶层为任意表&#xff0c;任意表分为索引表和哈希表&#xff0c;索引表又可分为标准表和排序表&#xff0c;结构如图&#xff1a; 2.2、内表用法 2.2.1…

GeoTools 存在 sql 注入漏洞

漏洞描述 GeoTools 是一个用于处理地理空间数据&#xff08;如地理信息系统: GIS&#xff09;的开源代码库&#xff0c;并且支持 OGC 过滤器表达式语言的解析和编码。PostGIS是PostgreSQL数据库的扩展程序&#xff0c;增加了数据库对地理对象的支持。PostGIS DataStore 为GeoT…

Android Framework-操作系统基础

最近在看《深入理解Android内核设计思想&#xff08;第2版&#xff09;》&#xff0c;个人感觉很不错&#xff0c;内容很多&#xff0c;现将书里个人认为比较重要的内容摘录一下&#xff0c;方便后期随时翻看。 计算机体系结构 硬件是软件的基石&#xff0c;所有的软件功能最…

【蓝桥杯嵌入式】点亮LED灯,流水灯的原理图解析与代码实现——STM32

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都在这儿哦&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 - 蓝…

不用PS,也能实现抠图的工具

对于非设计专业的同学来说&#xff0c;专门下载 PS 抠图有点大材小用&#xff0c;而且运用 PS 对电脑配置一定要求。不过现在有了更多选择&#xff0c;市面上出现了越来越多的抠图软件&#xff0c;不过越多的抠图软件选择也意味着需要花费时间试错因此本文将给大家推荐 3 款非常…

递归算法(recursion algorithm)

递归算法 什么是递归算法 在过程或者函数里调用自身的算法&#xff1b; 递归算法&#xff08;recursion algorithm&#xff09;&#xff0c;通过重复将问题分解为同类的子问题而解决问题的方法&#xff0c; Java中函数可以通过调用自身来进行递归&#xff0c;大多数编程语句…

jQuery 属性操作

jQuery 属性操作 Date: February 28, 2023 Sum: jQuery属性操作、文本属性值、元素操作、尺寸、位置操作 jQuery 属性操作 设置或获取元素固有属性值 prop() 所谓元素固有属性就是元素本身自带的属性&#xff0c;比如 元素里面的 href &#xff0c;比如 元素里面的 type。 …

以太网调试经验总结

1.MDC时钟捕获 在bringup时&#xff0c;首先需要确认MDC/MDIO控制通道是否正常&#xff0c;通过捕获MDC时钟以确认MDC/MDIO的工作状态是否正常&#xff0c;MDC时钟频率由具体的PHY芯片决定&#xff0c;不同的PHY芯片支持的MDC时钟频率范围不通。 注意1&#xff1a;MDC时钟频率不…