【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) : 表示可以自定义维护环境变量

请添加图片描述

请添加图片描述

五、相关代码

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

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

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

相关文章

A股上市公司财务松弛数据集(2000-2022年)

01、数据介绍 财务松弛是指企业在运营过程中&#xff0c;由于各种原因导致其财务状况出现一定程度的松弛或宽裕状态。这种状态通常表现为企业持有较多的现金和流动性资产&#xff0c;同时负债相对较少&#xff0c;或者企业有较多的未使用授信额度等。 本数据包括&#xff1a;…

【LeetCode】链表oj专题

前言 经过前面的学习&#xff0c;咋们已经学完了链表相关知识&#xff0c;这时候不妨来几道链表算法题来巩固一下吧&#xff01; 如果有不懂的可翻阅之前文章哦&#xff01; 个人主页&#xff1a;小八哥向前冲~-CSDN博客 数据结构专栏&#xff1a;数据结构【c语言版】_小八哥…

【管理篇】如何处理团队里的老资格员工和高能力员工?

目录标题 两类员工对比&#x1f93a;老资格员工高能力员工 作为领导你应该怎么做&#xff1f; 在管理团队时&#xff0c;处理老资格员工和高能力员工是一项至关重要的任务。这两类员工在团队中扮演着不同的角色和有着不同的需求&#xff0c;因此需要针对性的管理和激励。下面将…

漫谈音频深度伪造技术

作为人工智能时代的新型媒体合成技术&#xff0c;深度伪造技术近年来在网络媒体中的涉及领域越发广泛、出现频次越发频繁。据路透社报道&#xff0c;2023年&#xff0c;社交媒体网站上发布50万个深度伪造的语音和视频。 1、深度伪造技术的五个方面 音频深度伪造技术&#xff…

Java八股文3

3.垃圾回收 1.对象什么时候可以被垃圾器回收 1.垃圾回收的概念 为了让程序员更专注于代码的实现&#xff0c;而不用过多的考虑内存释放的问题&#xff0c;所以&#xff0c; 在Java语言中&#xff0c;有了自动的垃圾回收机制&#xff0c;也就是我们熟悉的GC(Garbage Collection)…

Unity 性能优化之静态批处理(三)

提示&#xff1a;仅供参考&#xff0c;有误之处&#xff0c;麻烦大佬指出&#xff0c;不胜感激&#xff01; 文章目录 前言一、静态批处理是什么&#xff1f;二、使用步骤1.勾选Static Batching2.测试静态合批效果 三、静态合批得限制1、游戏对象处于激活状态。2、游戏对象有一…

CMakeLists.txt语法规则:条件判断说明一

一. 简介 前面学习了 CMakeLists.txt语法中的 部分常用命令&#xff0c;常量变量&#xff0c;双引号的使用。 本文继续学习 CMakeLists.txt语法中的条件判断。 二. CMakeLists.txt 语法规则&#xff1a;条件判断 在 cmake 中可以使用条件判断&#xff0c;条件判断形式如下…

STM32 01

1、编码环境 1.1 安装keil5 1.2 安装STM32CubeMX 使用STM32CubeMX可以通过界面的方式&#xff0c;快速生成工程文件 安装包可以从官网下载&#xff1a;https://www.st.com/zh/development-tools/stm32cubemx.html#overview 安装完要注意更新一下固件包的位置&#xff0c;因为…

vivado 在硬件中调试串行 I/O 设计-属性窗口

只要在“硬件 (Hardware) ”窗口中选中 GT 或 COMMON 块、在“链接 (Link) ”窗口中选中链接 &#xff0c; 或者在“扫描 (Scan)”窗口中选中扫描 &#xff0c; 那么就会在“ Properties ”窗口中显示该对象的属性。对于 GT 和 COMMON &#xff0c; 包括这些对象的所有属性、…

电商日志项目(一)

电商日志项目 一、项目体系架构设计1. 项目系统架构2. 项目数据流程二、环境搭建1. NginxLog文件服务1.1. 上传,解压1.2. 编译安装1.3. 启动验证2. Flume-ng2.1. 上传解压2.2. 修改配置文件2.3. 修改环境变量2.4. 验证3. Sqoop3.1. 上传解压3.2. 配置环境变量3.3. 修改配置文件…

如何进行Go语言的性能测试和调优?

文章目录 开篇一、性能测试1. 使用标准库中的testing包2. 使用第三方工具 二、性能调优1. 优化算法和数据结构2. 减少不必要的内存分配和垃圾回收3. 并发和并行 结尾 开篇 Go语言以其出色的性能和简洁的语法受到了广大开发者的喜爱。然而&#xff0c;在实际开发中&#xff0c;…

微服务架构与单体架构

微服务架构与与单体架构比较 微服务架构是一种将应用程序作为一组小的、独立服务的系统架构风格&#xff0c;每个服务运行在其自己的进程中&#xff0c;并通常围绕业务能力组织。这些服务通过定义良好且轻量级的机制&#xff08;通常是HTTP REST API&#xff09;进行通信。微服…

Redis(基础指令和五大数据类型)

文章目录 1.基本介绍1.多种数据结构支持2.应用场景 2.Redis安装&#xff08;直接安装到云服务器&#xff09;1.安装gcc1.yum安装gcc2.查看gcc版本 2.将redis6.2.6上传到/opt目录下3.进入/opt目录下然后解压4.进入 redis-6.2.6目录5.编译并安装6.进入 /usr/local/bin 查看是否有…

智慧文旅开启沉浸式文化体验,科技让旅行更生动:借助智慧技术,打造沉浸式文化体验场景,让旅行者在旅行中深度感受文化的魅力

一、引言 随着科技的飞速发展&#xff0c;传统旅游行业正经历着前所未有的变革。智慧文旅&#xff0c;作为一种新兴的旅游模式&#xff0c;正以其独特的魅力&#xff0c;吸引着越来越多的旅行者。智慧文旅不仅改变了人们的旅行方式&#xff0c;更在深度上丰富了人们的文化体验…

Spring入门及注解开发

1 引言 自定义注解可以用来为代码添加元数据信息,简化配置,提高代码的可读性和可维护性。通过自定义注解,可以实现自定义的业务逻辑、约束条件、配置参数等功能。在Spring中,自定义注解常用于标记组件、配置依赖注入、AOP切面等。 自定义注解可以添加元数据信息,低代码框…

关于图形库

文章目录 1. 概念介绍2. 使用方法2.1 普通路由2.2 命名路由 3. 示例代码4. 内容总结 我们在上一章回中介绍了"使用get显示Dialog"相关的内容&#xff0c;本章回中将介绍使用get进行路由管理.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…

AEC Capital Limited:开启可持续金融新纪元

在当今社会&#xff0c;环保和可持续发展已成为全球关注的焦点。在这个背景下&#xff0c;AEC Capital Limited作为香港的一家金融服务公司&#xff0c;以其专业、高端的服务和创新的理念&#xff0c;成为可持续金融领域的引领者。我们致力于将环境保护与金融服务相结合&#x…

观测与预测差值自动变化系统噪声Q的自适应UKF(AUKF_Q)MATLAB编写

简述 基于三维模型的UKF&#xff0c;设计一段时间的输入状态误差较大&#xff0c;此时通过对比预测的状态值与观测值的残差&#xff0c;在相应的情况下自适应扩大系统方差Q&#xff0c;构成自适应无迹卡尔曼滤波&#xff08;AUKF&#xff09;&#xff0c;与传统的UKF相比&…

mac监听 linux服务器可视化(Grafana+Promethus+Node_exporter)

Grafana和promethus(普罗米修斯)的安装和使用 监控系统的Prometheus类似于一个注册中心&#xff0c;我们可以只需要配置一个Prometheus,而在其他服务器&#xff0c;只需要安装node_exporter,它们的数据流转就是通过exporter采集数据信息&#xff0c;然后告诉prometheus它的位置…

华为二层交换机与路由器连通上网实验

华为二层交换机与路由器连通上网实验 二层交换机是一种网络设备&#xff0c;用于在局域网&#xff08;LAN&#xff09;中转发数据帧。它工作在OSI模型的第二层&#xff0c;即数据链路层。二层交换机通过学习和维护MAC地址表&#xff0c;实现了数据的快速转发和广播域的隔离。 实…