【Linux】进程控制 之 进程创建 进程终止 进程等待 进程替换

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 一、进程创建fork
  • 二、进程终止
      • 2.1 退出码
      • 2.2 进程常见退出方法之正常终止
        • 2.2.1 return退出
        • 2.2.2 exit函数
        • 2.2.3 _exit函数(不建议使用)
        • 2.2.4 return && exit && _exit的区别
        • 2.2.5 进程异常
  • 三、进程等待
      • 3.1 进程等待的必要性(为什么要有进程等待)
      • 3.2 进程等待的系统调用接口
        • 3.2.1 wait()函数
        • 3.2.2 waitpid()函数
      • 3.3 非阻塞轮询WNOHANG
  • 四、进程替换
      • 4.1 什么是进程替换
      • 4.2 介绍替换函数
        • 4.2.1 execl函数
        • 4.2.2 execlp函数
        • 4.2.3 execv函数
        • 4.2.4 execvp函数
        • 4.2.5 execle函数
        • 4.2.6 execvpe函数
        • 4.2.7 execve函数
      • 4.3 巧记函数
  • 五、相关代码

一、进程创建fork

fork函数是从已存在进程中创建一个新进程,这个新进程称为子进程,而原进程为父进程。

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

函数返回值:

  • fork()创建成功:将子进程的PID作为父进程的fork函数的返回值,将0作为子进程的fork函数的返回值。
  • fork()创建失败:不创建子进程,将-1返回给父进程。原因是:系统中有太多的进程等
  • 不同的返回值的目的是:为了让子进程和父进程做不同的事 ~

65fe38c8f2c94fa7b9c179d1e9c3a5c7

  • 当程序执行到fork()函数时,操作系统会以父进程为模板,为子进程创建内核数据结构task_struct,子进程会以父进程为模板初始化属性(字段);每个进程都需要一个进程地址空间,所以子进程也会复制父进程的进程地址空间;每个进程还需要一个独立的页表结构(用于将虚拟地址映射到物理地 址),也是通过复制父进程的页表,这也就是为什么子进程能和父进程共享代码和数据的原因。

  • 由于每个进程具有独立性,当父子进程任意一方要修改数据,那么就会引发写时拷贝机制(只有修改共享的数据时才进行实际的拷贝。这样可以节省内存,并提高性能),并且这是由操作系统自动完成的。其原理是通过分页机制(以子进程的视角为例):操作系统会通过子进程的页表将对应需要修改变量的虚拟地址映射成物理地址,在物理内存中进行写时拷贝,即为这个变量开辟新的空间进行修改,那么对应的物理地址也要更新为新开辟空间的地址,而虚拟地址不发生变化。

二、进程终止

2.1 退出码

程序终止一定会有以下三种情况:

  • 程序运行成功,返回正确结果

  • 程序运行成功,返回错误结果

  • 程序异常终止(除零错误、越界访问等)

不知道大家有没有注意到这样一个问题:为什么要在main函数的最后写return 0结尾?既然返回这个数字,又是返回给谁呢?

请添加图片描述

我们都知道main函数是程序的入口,但实际上main函数只是我们用户级别代码的入口,main函数也是被其他函数调用的!

这里我以VS2019为例,教大家如何查看

  1. main 函数的定义处设置一个断点。(选中main函数所在行按F9即可)

请添加图片描述

  1. F5进入调试模式。(有一个黄色的断点在断点里面代表成功)

请添加图片描述
3. 查看调用堆栈。打开 “调试” -> “窗口” -> “调用堆栈”
请添加图片描述

  1. 然后会弹出一个窗口
    在这里插入图片描述

VS2019中,main函数就是被一个名为mainCRTStartup的函数所调用,而mainCRTStartup函数又是被操作系统所调用的。所以main函数调用结束后就应该告诉操作系统“我已经执行完毕,可以释放资源”。

C/C++中的main函数返回一个整数值,我们称之为退出码,它通常用来表示程序的退出状态

  • 返回0通常表示程序成功地执行完毕。

  • 返回非0通常表示程序在执行过程中发生了某种错误。

当一个程序执行完成并终止时,其退出码会被传递给其父进程(父进程要对子进程负责)。我们可以通过以下命令查看一个程序的退出码

  • Linux操作系统中,?是一个特殊的变量,保存着最近一次程序的退出码
echo $?

在这里插入图片描述

退出码有很多,单纯返回一个数字我们并不知道是什么意思,因此在Linux系统中,通常可以使用strerror()函数将错误码转换为对应的错误描述字符串

我们先可以查看手册来获取strerror函数的相关信息

man strerror

在这里插入图片描述

我们尝试打印1~200错误码转换为对应的错误描述字符串

在这里插入图片描述

0 -> Success
1 -> Operation not permitted
2 -> No such file or directory
3 -> No such process
4 -> Interrupted system call
5 -> Input/output error
6 -> No such device or address
7 -> Argument list too long
8 -> Exec format error
9 -> Bad file descriptor
10 -> No child processes
11 -> Resource temporarily unavailable
12 -> Cannot allocate memory
13 -> Permission denied
14 -> Bad address
15 -> Block device required
16 -> Device or resource busy
17 -> File exists
18 -> Invalid cross-device link
19 -> No such device
20 -> Not a directory
21 -> Is a directory
22 -> Invalid argument
23 -> Too many open files in system
24 -> Too many open files
25 -> Inappropriate ioctl for device
26 -> Text file busy
27 -> File too large
28 -> No space left on device
29 -> Illegal seek
30 -> Read-only file system
31 -> Too many links
32 -> Broken pipe
33 -> Numerical argument out of domain
34 -> Numerical result out of range
35 -> Resource deadlock avoided
36 -> File name too long
37 -> No locks available
38 -> Function not implemented
39 -> Directory not empty
40 -> Too many levels of symbolic links
41 -> Unknown error 41
42 -> No message of desired type
43 -> Identifier removed
44 -> Channel number out of range
45 -> Level 2 not synchronized
46 -> Level 3 halted
47 -> Level 3 reset
48 -> Link number out of range
49 -> Protocol driver not attached
50 -> No CSI structure available
51 -> Level 2 halted
52 -> Invalid exchange
53 -> Invalid request descriptor
54 -> Exchange full
55 -> No anode
56 -> Invalid request code
57 -> Invalid slot
58 -> Unknown error 58
59 -> Bad font file format
60 -> Device not a stream
61 -> No data available
62 -> Timer expired
63 -> Out of streams resources
64 -> Machine is not on the network
65 -> Package not installed
66 -> Object is remote
67 -> Link has been severed
68 -> Advertise error
69 -> Srmount error
70 -> Communication error on send
71 -> Protocol error
72 -> Multihop attempted
73 -> RFS specific error
74 -> Bad message
75 -> Value too large for defined data type
76 -> Name not unique on network
77 -> File descriptor in bad state
78 -> Remote address changed
79 -> Can not access a needed shared library
80 -> Accessing a corrupted shared library
81 -> .lib section in a.out corrupted
82 -> Attempting to link in too many shared libraries
83 -> Cannot exec a shared library directly
84 -> Invalid or incomplete multibyte or wide character
85 -> Interrupted system call should be restarted
86 -> Streams pipe error
87 -> Too many users
88 -> Socket operation on non-socket
89 -> Destination address required
90 -> Message too long
91 -> Protocol wrong type for socket
92 -> Protocol not available
93 -> Protocol not supported
94 -> Socket type not supported
95 -> Operation not supported
96 -> Protocol family not supported
97 -> Address family not supported by protocol
98 -> Address already in use
99 -> Cannot assign requested address
100 -> Network is down
101 -> Network is unreachable
102 -> Network dropped connection on reset
103 -> Software caused connection abort
104 -> Connection reset by peer
105 -> No buffer space available
106 -> Transport endpoint is already connected
107 -> Transport endpoint is not connected
108 -> Cannot send after transport endpoint shutdown
109 -> Too many references: cannot splice
110 -> Connection timed out
111 -> Connection refused
112 -> Host is down
113 -> No route to host
114 -> Operation already in progress
115 -> Operation now in progress
116 -> Stale file handle
117 -> Structure needs cleaning
118 -> Not a XENIX named type file
119 -> No XENIX semaphores available
120 -> Is a named type file
121 -> Remote I/O error
122 -> Disk quota exceeded
123 -> No medium found
124 -> Wrong medium type
125 -> Operation canceled
126 -> Required key not available
127 -> Key has expired
128 -> Key has been revoked
129 -> Key was rejected by service
130 -> Owner died
131 -> State not recoverable
132 -> Operation not possible due to RF-kill
133 -> Memory page has hardware error
# 后面没有了 ~

实际上Linux中的lspwd等命令都是可执行程序,当执行这些命令时,就是一个进程。因此使用这些命令后我们也可以查看其对应的退出码。

在这里插入图片描述

如果正常运行的话,其错误码就是0

在这里插入图片描述

2.2 进程常见退出方法之正常终止

2.2.1 return退出

main函数中使用return退出进程是我们常用的方法。这里就不再过多赘述了 ~

2.2.2 exit函数
#include <unistd.h>
void exit(int status);
# status - 退出码

使用exit函数退出进程也是我们常用的方法,可以在代码中的任何地方退出进程

在这里插入图片描述

在这里插入图片描述

2.2.3 _exit函数(不建议使用)

_exit是一个系统调用接口,它和exit函数的用法一模一样,可以在代码中的任何地方退出进程。

【文档介绍】

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.2.4 return && exit && _exit的区别
  • return在除main函数以外使用代表当前函数结束,只有在main函数中代表进程退出。

  • exit()是一个库函数,位于 <stdlib.h> 头文件中,在任意地方使用都代表进程退出。它和_exit的区别是:exit()函数会执行一系列的清理工作(如刷新缓冲区、关闭流等)

  • _exit()是一个系统调用接口,它位于 <unistd.h> 头文件中,在任意地方使用都代表进程退出。它和exit()的区别是:_exit()函数不会执行任何的清理操作,它直接终止程序。因此,使用 _exit() 可能会导致资源泄漏或未完成的操作,所以不建议使用。

在这里插入图片描述

或者可以这样理解_exitexitexit() 是对 _exit() 做的封装实现,_exit() 就只是单纯的退出程序,而 exit() 在退出之前还会做一些事,比如冲刷缓冲区,再调用 _exit()

在这里插入图片描述

2.2.5 进程异常

子进程异常终止通常是由接收到某种信号引起的。常见的信号包括SIGKILL(强制终止)、 SIGSEGV(段错误)等。当子进程收到这些信号时,它可能会以异常终止的方式结束。或者可以进程强制终止ctrl + c

在进程等待部分会做演示 ~

三、进程等待

3.1 进程等待的必要性(为什么要有进程等待)

在这里插入图片描述

在这里插入图片描述

Linux中,一个进程终止了不会立马进入死亡状态。而是会先进入僵尸状态。但如果父进程没有及时对子进程进行回收,这个进程就会变成僵尸进程,进而造成内存泄漏。(僵尸状态是指进程已经终止执行,但其相关的进程控制块PCB和资源仍然保留在系统中,直到其父进程获取子进程终止状态,子进程才能释放资源,变为死亡状态)

注意:进程一旦变成僵尸状态,那就刀枪不入,就连“杀人不眨眼”的kill -9 PID也无能为力,因为谁也没有办法杀死一个已经退出的进程。

因此,需要通过进程等待

  • 解决僵尸进程!(必须解决)

  • 获取父进程布置给子进程的任务完成的怎么样了! (可选)

3.2 进程等待的系统调用接口

我们可以通过 系统调用wait()或者waitpid() 来进行对子进程进行状态检测与回收的功能

3.2.1 wait()函数

我们可以通过man手册来查询wait()函数的相关信息

man 2 wait
# 2号手册是专门用来查系统调用接口的~

在这里插入图片描述

  • 当子进程退出时,其退出状态、终止原因等信息将会通过系统调用接口wait()由操作系统来写入 status 指向的变量中。在wait()函数中我们主要演示如何回收僵尸进程,因此这里暂时不关心子进程状态,直接设置为NULL即可。(waitpid()详细介绍status

  • wait 函数的返回值是要回收子进程的PID。如果当前进程没有子进程,则 wait() 会立即返回-1

下面我来演示让父进程调用wait函数来回收僵尸进程。

以下代码一共sleep15秒,其中前五秒父子进程一直在打印自己的消息,在后五秒中,父进程还在继续打印,子进程提前退出,此时为僵尸进程。为了更好观察结果,最后五秒父进程结束打印,此时处于阻塞状态,进行回收僵尸进程

在这里插入图片描述

执行进行命令进行动态监控进程状态

while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep;echo "-----------------------------------------------------------";sleep 1;done

在这里插入图片描述

而常识告诉我们,进程不可能只有一个。因此,当有多个子进程时,wait 函数会等待任意一个子进程结束,并返回子进程的PID。(例如以下代码样例)

注意:wait()并不保证按照子进程创建的顺序进行回收,而是依赖于内核调度的具体实现。

在这里插入图片描述

在这里插入图片描述

如果任意一个子进程都不退出,调用 wait() 的父进程会一直是阻塞状态,我们称这现象为阻塞等待。换句话说,wait 函数会阻塞父进程,直到所有子进程结束为止。如果父进程没有子进程,或者所有子进程都已经结束,wait 函数会立即返回。

在这里插入图片描述

在这里插入图片描述

【总结】

  • wait()可以回收僵尸进程

  • 如果任意一个子进程都不退出,调用 wait() 的父进程会一直是阻塞状态,我们称这现象为阻塞等待

3.2.2 waitpid()函数

在这里插入图片描述

  • waitpid() 函数中的第一个参数pid_t pid可以指定回收的子进程;如果设置为-1可以等待任一个子进程,与wait()等效。

  • waitpid函数中的第二个参数int* status是输出型参数,可以获取子进程的退出状态。

    • 如果传递NULL,表示父进程不关心子进程的退出状态信息

    • 如果传递非NULL,则通过系统调用waitpid/wait让操作系统从子进程PCB对象获取退出信息(如退出码、退出信号)反馈给父进程(这些都由操作系统完成)。

    • 由于一个进程退出的场景有三种(程序运行成功,结果错误;程序运行成功,结果正确;程序异常终止),那么父进程就需要关心:子进程为什么异常终止?没有异常,结果对吗?不对是因为什么?即退出码是什么?因此status以二进制划分为以下2个部分

    在这里插入图片描述

    • 获取异常信号:status & 0x7F。其中7F表示0111 1111;或者可以使用系统自定义的宏WTERMSIG(status)

    • 获取退出状态(退出码):(status >> 8) & 0xFF。其中FF表示1111 1111;或者可以使用系统自定义的宏WEXITSTATUS(status)

    • 补充:WIFEXITED(status):用来判断进程是正常退出还是异常退出,异常退出返回0,没有异常返回一个非0的值。

  • waitpid函数中的第三个参数int options 是设置等待的方式。

    • 设置为0,表示waitpid会阻塞父进程,直到指定的子进程退出才返回。

    • 设置为WNOHANG,表示非阻塞等待。 点击跳转

  • 返回值:若成功,返回结束子进程的PID>0);若出错(无效的子进程PID、没有子进程等),返回-1<0);还有一个等于0的情况,在非阻塞轮询会提到。

waitpid()中重点演示:父进程获取子进程状态的演示

在这里插入图片描述

在这里插入图片描述

单纯一个退出码看着有点难受,我们可以将其转化对应的错误信息

在这里插入图片描述

在这里插入图片描述

那如何验证子进程是否出现进程异常呢? 举一个例子,假设子进程不小心写了一个非法访问

在这里插入图片描述

在这里插入图片描述

虽然我的源代码的退出状态设置为3,但由于进程异常提前终止,并没有执行到exit(3),所以退出码默认为0。接下来我们可以执行kill -l来看看是什么型号导致的

在这里插入图片描述

或者可以使用kill对子进程发送型号

在这里插入图片描述

3.3 非阻塞轮询WNOHANG

当使用 waitpid 函数时,如果子进程不退出,父进程就会一直处于阻塞状态,什么都干不了,直到其子进程退出。但是,有时候我们希望父进程在等待子进程退出时不阻塞,而是可以在等待子进程退出的同时执行其他任务,这就是非阻塞轮询的概念。

因此,可以通过waitpid函数的第三个参数设置为 WNOHANGLinux系统提供的宏) + 循环,来告诉内核如果子进程没有立即退出,就不要“傻傻”的等待了,而是一边等待子进程退出,一边执行其他任务

在这里插入图片描述

在这里插入图片描述

四、进程替换

4.1 什么是进程替换

进程替换是指一个正在运行的进程被另一个进程所取代的过程。常见的进程替换方式是使用 exec 系列函数

接下来举一个例子来带大家看看

在这里插入图片描述

【运行结果】

在这里插入图片描述

我们发现:子进程在运行的过程中确实被替换成了ls -al的指令,但是为什么子进程中的最后一条打印语句没有执行?

这就和进程替换原理有关:

  • 当替换函数成功调用后,替换函数会将新程序(代码和数据)加载到子进程的进程地址空间中,覆盖掉子进程原来的所有内容。由于一开始子进程共享父进程的代码和数据,必定会触发写时拷贝,确保父子进程之间的内存隔离。一旦新程序加载完成并且子进程地址空间和页表被更新,子进程会从新程序的入口点开始执行。

  • 注意:虽然在调用替换函数之前会创建子进程,但是在整个替换过程中,并不会创建新的进程。

4.2 介绍替换函数

4.2.1 execl函数

函数原型如下:

#include <unistd.h>

int execl(const char* path, const char* arg, ...);
  • 参数path是新程序的完整路径。

  • 参数arg0argn是新程序的命令行参数。由于...表示可变参数列表,因此可以传递多个命令行参数。最后一个参数必须是NULL,表示参数列表的结束。

  • 如果execl函数调用成功,它将不会返回,因为当前进程已被替换,而如果调用失败,它将返回-1

既然替换函数可以将原有的进程替换为系统命令,当然也可以替换为我们自己写的可执行程序,因为所有程序运行起来本质上都是一个进程。

  • 比如替换C++程序

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.2.2 execlp函数
  • execlp函数会在系统的环境变量PATH中查找可执行文件,而不需要指定完整的路径。

函数原型如下:

#include <unistd.h>

int execlp(const char* file, const char* arg0, ...);
  • 参数file是要执行的程序的文件名,而不是完整的路径名。前提是这个文件名的路径需要再环境变量PATH中。

  • 参数arg0argn是新程序的命令行参数。由于...表示可变参数列表,因此可以传递多个命令行参数。最后一个参数必须是NULL,表示参数列表的结束。

  • 如果execlp函数调用成功,它将不会返回,因为当前进程已被替换,而如果调用失败,它将返回-1

在这里插入图片描述

在这里插入图片描述

4.2.3 execv函数
  • execv()execl()execlp()函数的区别在于:它接受一个指向参数的字符串指针数组,而不用明确列出每个参数。

函数原型如下:

#include <unistd.h>

int execv(const char *path, char *const argv[]);
  • 参数path是要执行的新程序的完整路径。

  • 参数argv[]表示一个字符串数组,表示新进程的命令行参数。数组的第一个元素通常是执行的程序的名称,后续元素是命令行参数,最后一个元素必须是 NULL

  • execlexeclp函数一样,如果execv函数调用成功,它将不会返回,而如果调用失败,它将返回-1

在这里插入图片描述

在这里插入图片描述

4.2.4 execvp函数
  • execvp函数与execv函数类似,它可以在系统的环境变量PATH中查找可执行文件,而不需要指定完整的路径。

函数原型如下:

#include <unistd.h>
int execvp(const char* file, char* const argv[]);
  • 参数file是要执行的程序的文件名,而不是完整的路径名。前提是这个文件名的路径需要再环境变量PATH中。

  • 参数argv[]是一个字符串数组,表示新进程的命令行参数。数组的第一个元素通常是执行的程序的名称,后续元素是命令行参数,最后一个元素必须是 NULL

  • 如果execvp函数调用成功,它将不会返回,而如果调用失败,它将返回-1

在这里插入图片描述

在这里插入图片描述

4.2.5 execle函数
  • e表示env环境变量表,execle函数允许你自定义新程序的环境变量,而无需继承父进程(bash)的环境变量。

函数原型如下:

#include <unistd.h>
int execl(const char* path, const char* arg, ..., char* const envp[]);
  • 参数path是要执行的新程序的完整路径。

  • 参数arg0argn是新程序的命令行参数。注意最后一个元素必须是 NULL

  • 最后一个参数envp[]是一个指向新程序的环境变量的指针数组,其中每个元素都是以key=value的形式表示一个环境变量,最后一个元素必须是NULL指针,用于表示环境变量列表的结束。

    • 如果提供了环境变量数组envp[],那么新程序将会使用这个环境变量数组,并且会覆盖掉原程序的环境变量。如果不提供环境变量数组,新程序将会继承原程序的环境变量。

    • 补充:如果你想要子进程在父进程的环境变量的基础上增加环境变量,那么你可以使用putenv函数(自己查文档),注意:putenv函数是针对当前进程环境变量的修改操作,不会直接影响父进程的环境变量。

  • 如果execle函数调用成功,它将不会返回,而如果调用失败,它将返回-1

替换程序代码样例

在这里插入图片描述

进程替换部分

在这里插入图片描述

在这里插入图片描述

4.2.6 execvpe函数

execvpe函数和execle函数类似,只是将第二个参数封装成了字符串指针数组

函数原型如下:

#include <unistd.h>
int execvpe(const char *file, char *const argv[], char *const envp[]);
  • 参数file是要执行的程序的文件名,而不是完整的路径名。前提是这个文件名的路径需要在环境变量PATH中。

  • 参数argv[]表示一个字符串数组,表示新进程的命令行参数。数组的第一个元素通常是执行的程序的名称,后续元素是命令行参数,最后一个元素必须是 NULL

  • 最后一个参数envp[]是一个指向新程序的环境变量的指针数组,其中每个元素都是以key=value的形式表示一个环境变量,最后一个元素必须是NULL指针,用于表示环境变量列表的结束。

    • 如果提供了环境变量数组envp[],那么新程序将会使用这个环境变量数组,并且会覆盖掉原程序的环境变量。如果不提供环境变量数组,新程序将会继承原程序的环境变量。

    • 补充:如果你想要子进程在父进程的环境变量的基础上增加环境变量,那么你可以使用putenv函数(自己查文档),注意:putenv函数是针对当前进程环境变量的修改操作,不会直接影响父进程的环境变量。

  • 如果execvpe函数调用成功,它将不会返回,而如果调用失败,它将返回-1

4.2.7 execve函数

事实上,只有execve函数才是真正的系统调用,因此以上所介绍的函数其底层都调用了系统调用接口 execve()来完成进程替换的功能!

函数原型如下:

#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]);
  • 参数filename是新程序的完整路径。

  • 参数argv[]表示一个字符串数组,表示新进程的命令行参数。数组的第一个元素通常是执行的程序的名称,后续元素是命令行参数,最后一个元素必须是 NULL

  • 最后一个参数envp[]是一个指向新程序的环境变量的指针数组,其中每个元素都是以key=value的形式表示一个环境变量,最后一个元素必须是NULL指针,用于表示环境变量列表的结束。

    • 如果提供了环境变量数组envp[],那么新程序将会使用这个环境变量数组,并且会覆盖掉原程序的环境变量。如果不提供环境变量数组,新程序将会继承原程序的环境变量。

    • 补充:如果你想要子进程在父进程的环境变量的基础上增加环境变量,那么你可以使用putenv函数(自己查文档),注意:putenv函数是针对当前进程环境变量的修改操作,不会直接影响父进程的环境变量。

  • 如果execvpe函数调用成功,它将不会返回,而如果调用失败,它将返回-1

请添加图片描述

请添加图片描述

4.3 巧记函数

这些函数原型看起来很容易混,但只要掌握了规律就很好记。

  • l(list) : 表示参数采用列表 (一一列举)

  • v(vector) : 表示参数用数组

  • p(path) : 有p表示自动搜索环境变量PATH里的路径

  • e(env) : 表示可以自定义维护环境变量

请添加图片描述

请添加图片描述

五、相关代码

本篇博客相关代码:点击跳转

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

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

相关文章

每日一博 - 闲聊架构设计中的多级缓存设计

文章目录 方法论概述客户端缓存应用层缓存服务层缓存缓存设计的注意事项总结 思维导图戳这里 方法论概述 从客户端到服务层&#xff0c;缓存的应用广泛而重要。通过合理的缓存设计&#xff0c;能够有效地提高系统的性能并降低延迟。 客户端缓存 在客户端层面&#xff0c;浏览…

LLM2Vec介绍和将Llama 3转换为嵌入模型代码示例

嵌入模型是大型语言模型检索增强生成(RAG)的关键组成部分。它们对知识库和用户编写的查询进行编码。 使用与LLM相同领域的训练或微调的嵌入模型可以显著改进RAG系统。然而&#xff0c;寻找或训练这样的嵌入模型往往是一项困难的任务&#xff0c;因为领域内的数据通常是稀缺的。…

基于AT89C51单片机的温度上下限自动控制检报警设计

点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/89247694?spm=1001.2014.3001.5501 C 源码+仿真图+毕业设计+实物制作步骤+06 题 目 基于单片机的温度检测调节系统设计 姓 名 学 号 专业班级 指导教师 年 月 日 任务书 …

Nginx 从入门到实践(2)——Rewrite重写

Nginx Rewrite Rewrite重写 Nginx Rewriteurl组成说明Rewrite基本概述Rewrite使⽤场景rewrite优点 Rewrite配置语法location匹配概述 if指令if 判断指令语法nginx以及if 判断可使用的全局变量 set命令return指令 url组成说明 https://cn.bing.com/search?qNginxRewrite&P…

udp/tcp回显网络编程

udp DatagramSocket 用于接收和发送udp数据报 构造方法&#xff1a; DatagramSocket():创建一个UDP数据报套接字的Socket&#xff0c;绑定到本地上 一个随机可用端口上&#xff0c;一般用于客户端DatagramSocket(int port):创建一个UDP数据报套接字的Socket&#xff0c;绑定到…

Proxmox VE 8 用SDN隔离用户网络

作者&#xff1a;田逸&#xff08;formyz&#xff09; 最新发布的Proxmox VE&#xff08;以下简称PVE&#xff09; 8在Web管理后台集成了易于操作的SDN&#xff08;软件定义网络&#xff09;功能插件&#xff0c;其实质是对不同的PVE用户指定不同的网络&#xff0c;进行逻辑隔离…

将要上市的自动驾驶新书《自动驾驶系统开发》中摘录各章片段 4

第十三章 车联网 数字化设备正变得越来越普遍并且相互联系。这些设备向数字生态系统智能部分的演进创造了迄今为止尚未解决安全问题的新颖应用。一个特定的例子是车辆&#xff0c;随着车辆从简单的交通方式发展到具有新的感知和通讯功能的智能实体&#xff0c;就成为智能城市的…

屏蔽罩材质和厚度对屏蔽效能的影响

​ 一&#xff0e;屏蔽效能的影响因素 屏蔽效能的影响因素主要有两个方面&#xff1a;屏蔽材料的特性和厚度&#xff1b;如下图所示&#xff0c;电磁波经过不同媒介时&#xff0c;会在分界面形成反射&#xff0c;穿过界面的电磁波一部分被反射回去&#xff0c;这部分能量损失…

偶然发现了Python的一个BUG。。。

一般情况下&#xff0c;dict(id1, **{id: 1})这句代码应该报TypeError。但如果在捕获了其他异常的情况下&#xff0c;再来执行这句代码&#xff0c;却是会报KeyError&#xff0c;如下图&#xff1a; Python3.10和Python3.9也能复现该情况&#xff0c;正当我摩拳踩掌&#xff0c…

百度下拉框负面信息如何删除?

百度头条360等搜索引擎&#xff0c;作为人们获取信息的主要途径之一。然而&#xff0c;一些知名的企业或个人可能会面临在搜索的下拉框中出现负面信息的问题&#xff0c;这可能对其声誉和形象造成不良影响。小马识途营销顾问根据自身从业经验&#xff0c;针对这类情况提出以下建…

【精品毕设推荐】基于JSP物流信息网的设计与实现

点击免费下载原文及代码、PPT 摘要 本文讲述了基于JSP物流信息网的设计与实现。该系统使用java语言开发&#xff0c;使系统具有更好的平台性和可扩展性。 该系统实现了用户登录、注册、查询快递信息、快递公司注册成为合作伙伴以及系统管理员对信息进行管理等功能。系统的主…

LeetCode 234.回文链表

题目描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为 回文链表 。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff…

解决windows中的WSL Ubuntu子系统忘记root密码和用户密码问题

1、以管理员身份运行PowerShell 2、在powershell中执行wsl.exe --user root wsl.exe --user root如果出现了上面的报错&#xff0c;则需要运行步骤3、4&#xff0c;然后在执行步骤5改密码&#xff0c;如果没有出错&#xff0c;请直接跳到第5步改密码操作&#xff01;&#xff…

一分钱不花从HTTP升级到HTTPS

HTTP升级到HTTPS是一个涉及安全性和技术实施的过程&#xff0c;主要目的是为了提升网站数据传输的安全性&#xff0c;防止数据被窃取或篡改。以下是一些关于从HTTP升级到HTTPS的技术性要点和步骤概述&#xff0c;结合上述信息资源&#xff1a; 一、理解HTTPS的重要性 HTTPS (…

微信IDE vscode插件:获取插件位置,并打开文件

背景 有没有觉得在微信开发工具里面添加一些插件可以很方便。因为微信IDE的编辑本身是依赖vscode开发&#xff0c;所以编写vscode插件自然可以在微信IDE使用。这样做好处就是可以满足到自己一些开发使用习惯。 1.获取插件的目录位置 那么如何获取插件里面的目录&#xff0c;…

【精】hadoop、HIVE大数据从0到1部署及应用实战

目录 基本概念 Hadoop生态 HIVE hdfs(hadoop成员) yarn(hadoop成员) MapReduce(hadoop成员) spark flink storm HBase kafka ES 实战 安装并配置hadoop 环境准备 准备虚拟机 安装ssh并设置免密登录 安装jdk 安装、配置并启动hadoop 添加hadoop环境变量&…

STM32F1之FLASH闪存

目录 1. 简介 2. 闪存模块组织 3. FLASH基本结构 4. FLASH解锁 5. 使用指针访问存储器 6. 程序存储器全擦除 7. 程序存储器页擦除 8. 程序存储器编程 9. 选项字节 1. 简介 STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&#xff0c;通过…

MapReduce笔记

实现分布式的作用&#xff1a; 通过并行处理提高能力通过复制机制进行容错处理与传感器等物理设备的分布相匹配通过隔离实现安全 难点&#xff1a; 许多并行的部件&#xff0c;他们之间有复杂的相互作用必须应对处理部分故障难以实现性能潜力 容错 1000多台服务器、庞大的网络…

【busybox记录】【shell指令】b2sum

目录 内容来源&#xff1a; 【GUN】【b2sum】指令介绍 【busybox】【b2sum】指令介绍 【linux】【b2sum】指令介绍 使用示例&#xff1a; BLAKE2摘要&#xff08;512bit&#xff09; - 默认输出 BLAKE2摘要&#xff08;512bit&#xff09; - 指定校验和长度 BLAKE2摘要…

Python量化炒股的数据信息获取—获取沪深股市每日成交概况信息

Python量化炒股的数据信息获取—获取沪深股市每日成交概况信息 沪深股市每日成交概况信息&#xff0c;都存放在STK_EXCHANGE_TRADE_INFO表中&#xff0c;该表保存在finance包中。要查看表中的数据信息&#xff0c;需要使用query()函数。 单击聚宽JoinQuant量化炒股平台中的“…