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

Linux——进程控制

 

目录

1. 进程创建

1.1 fork函数

1.2 fork系统调用内部宏观流程

 1.3 fork后子进程执行位置分析

1.4 fork后共享代码分析

1.5 fork返回值

1.6 写时拷贝

1.7 fork常规用法

1.8 fork调用失败的原因

2.进程终止

2.1 进程退出场景

2.2 strerror函数—返回描述错误号的字符串

2.3 进程常见退出方法

2.4 _exit函数和exit函数

 2.5 return退出

3. 进程等待

3.1 进程等待必要性

3.2 进程等待方法

3.3 获取子进程status

3.4 进程阻塞等待和非阻塞等待

3.5 waitpid系统调用接口分析

3.6 阻塞等待代码和基于非阻塞调用的轮询检测方案

4. 进程程序替换

4.1 替换原理

4.2 替换函数

4.3 函数解释

4.4 命名理解

4.5 实现简易shell

5. 函数和进程之间的相似性:


1. 进程创建

1.1 fork函数

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

#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1

1.2 fork系统调用内部宏观流程

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

fork创建子进程,系统中多了一个进程,及包含进程对应的PCB结构体以及对应的地址空间及页表映射关系,并将代码和数据加载到内存中,并将该进程加载到运行队列,等待操作系统调度器的调度!一旦该进程被调度起来,CPU就可以通过代码和数据及地址空间和页表映射,在物理内存找到对应的代码,进行运行!

 当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以 开始它们自己的旅程,看如下程序。

int main( void )
{pid_t pid;printf("Before: pid is %d\n", getpid());if ( (pid=fork()) == -1 )perror("fork()"),exit(1);printf("After:pid is %d, fork return %d\n", getpid(), pid);sleep(1);return 0;
} 运行结果:
[root@localhost linux]# ./a.out
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0

这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after 消息有43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示

 所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。

 1.3 fork后子进程执行位置分析

 fork前后,父子进程所有代码共享!

  • 代码汇编后,加载到内存,都有与之对应的地址
  • 因为进程随时可能被中断(可能并没执行结束),下次回来,还必须从之前的位置继续执行(不是最开始位置),就需要要求CPU必须随时记录当前进程执行的位置,所以,CPU有对应的寄存器数据EIP(PC指针,也叫做程序计数器,用于记录当前正在执行代码的下一行代码的地址),因此在进程切换时,需要将寄存器信息带走(上下文数据)。


寄存器在CPU内,只有一份(切换进程必须带走数据),寄存器内的数据,是可以有多份的(寄存器上下文数据

创建子进程的时候,也会将上下文数据给子进程(解释了fork之后,子进程不会从before处运行),虽然父子进程各自调度,各自会修改EIP,但是不重要了,子进程已经认为自己的EIP起始值,是fork之后的代码

1.4 fork后共享代码分析

创建子进程,给予进程分配对应的内核结构,必须子进程自己独有了,因为进程具有独立性!理论上,子进程也要有自己的代码和数据!可是一般而言,我们没有加载的过程,也就是说,子进程没有自己的代码和数据!!所以,子进程只能”使用“父进程的代码和数据!

代码:都是不可被写的,只能读取,所以父子共享,没有问题!

数据:可能被修改的,所以,必须分离!

对于数据而言:

  1. 创建进程的时候,就直接拷贝分离吗?(导致可能拷贝子进程根本就不会用到的数据空间,即便是用到,也可能只是只读)
  2. 编译器在编译时,尚且知道节省空间,操作系统同样如此,如分批加载挂起状态
  3. 因此创建子进程,不需要将不会被访问的或者只读取的数据,再去拷贝一份,浪费空间

但是,还有必须拷贝的数据,什么样的数据值得拷贝?将来会被父子进程写入的数据!!如上图g_val或者接收fork返回值的变量

  • 一般而言,即便是操作系统,也无法知道那些空间可能被写入!
  • 即便是提前拷贝了,也可能不会立马使用,因此造成空间浪费!
  • 所以操作系统提供了写时拷贝技术,来对父子进程的数据进行分离!

1.5 fork返回值

  • 子进程返回0,
  • 父进程返回的是子进程的pid

1.6 写时拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:

 因为有写时拷贝技术的存在,所以,父子进程得以彻底分离!完成了进程的独立性的技术保证!(写时拷贝的好处)写时拷贝只在写入数据时发生拷贝,且对只读数据和代码不发生拷贝,节省空间!写时拷贝,是一种延时申请技术,可以提高整机内存的使用效率!

1.7 fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

1.8 fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

2.进程终止

进程终止时,操作系统本质上释放进程申请的相关内核数据结构和对应的代码和数据(本质上是释放资源)

2.1 进程退出场景

进程终止的常见方式?

        a. 代码跑完,结果正确

        b. 代码跑完,结果错误

        c. 代码没有跑完,进程崩溃(信号部分)

指针a和b方式,涉及问题:

main函数的返回值?main函数返回值的意义是什么?return 0; 含义是什么?为什么总是0?其他值是否可以?

main函数的返回值并不是总是0,返回0表示程序sucess,非0表示运行结果不正确。

意义一:main函数的返回值适用于返回上一级进程,用来评判该进程执行结果用的,为进程的退出码。

意义二:非零值有无数个,不同的非零值就可以标识不同的错误原因!在我们程序运行结束之后,结果不正确时,根据返回码,方便定位错误的原因细节!

2.2 strerror函数—返回描述错误号的字符串

#include <string.h>char *strerror(int errnum);int strerror_r(int errnum, char *buf, size_t buflen);/* XSI-compliant */char *strerror_r(int errnum, char *buf, size_t buflen);/* GNU-specific */
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<string.h>
int main(){printf("Hello world! pid = %d, ppid = %d\n", getpid(), getppid());for(int i = 0; i < 150; ++i){printf("%d: %s\n", i, strerror(i));}return 0;
}

2.3 进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):

1. 从main返回

2. 调用exit

3. _exit

异常退出:

ctrl + c,信号终止

2.4 _exit函数和exit函数

_exit函数是系统提供的系统调用接口:

#include <unistd.h>
void _exit(int status);参数:status 定义了进程的终止状态,父进程通过wait来获取该值

说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

exit函数是C语言库提供的接口:

#include <unistd.h>
void exit(int status);

exit 或者 _exit 在任何地方调用,都表示直接终止进程!!!

Linux提供了系统调用接口_exit()函数在unistd.h头文件中,而C的库函数exit底层实则封装了系统调用接口_exit:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

测试代码:

int main()
{printf("hello");exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
hello[root@localhost linux]#
int main()
{printf("hello");_exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
[root@localhost linux]#

分析图:

 

 2.5 return退出

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返 回值当做 exit的参数。

有且只有在main函数内,return语句,就是终止进程的!return退出码!

3. 进程等待

3.1 进程等待必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

3.2 进程等待方法

wait方法:

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:pid:Pid=-1,等待任一个子进程。与wait等效。Pid>0.等待其进程ID与pid相等的子进程。status:WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)options:WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。
  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退 出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。
  • option默认为0,表示阻塞等待,status为输出型参数
  • waitpid(pid, NULL, 0) == wait(NULL)

3.3 获取子进程status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特 位):

 测试代码:

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{pid_t pid;if ( (pid=fork()) == -1 )perror("fork");exit(1);if ( pid == 0 ){//子进程休眠20秒,会变成僵尸进程//需要父进程等待回收sleep(20);exit(10);} else {//父进程int st;int ret = wait(&st);//阻塞等待if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出//子进程退出码printf("child exit code:%d\n", (st>>8)&0XFF);} else if( ret > 0 ) { // 异常退出//子进程退出信号printf("sig code : %d\n", st&0X7F );}}return 0;
}

测试结果:

[root@localhost linux]# ./a.out #等20秒退出child exit code:10 [root@localhost linux]# ./a.out #在其他终端kill掉sig code : 9

进程异常退出,或者崩溃,本质是操作系统杀掉了你的进程!!!

操作系统如何杀掉进程呢!本质是通过发送信号的方式!

 父进程通过wait或者waitpid可以拿到进程的退出结果,为什么要用wait/waitpid函数呢!直接全局变量不行吗??

答案是不行,因为进程具有独立性,其创建子进程虽然具有相同虚拟内存地址,但是数据会发生写时拷贝,父进程无法获取到,况且如果是信号,更是无法获取!

既然进程具有独立性,进程退出码,不也是子进程的数据吗??父进程又凭什么拿到呢??wait/waitpid究竟干了什么呢??

因为wait、waitpid是系统调用接口,是系统创建的结构,可以访问内核,本质便是读取子进程的task_struct结构,此结构肯定包含了对应的信号码和退出码!

3.4 进程阻塞等待和非阻塞等待

options:WNOHANG选项,代表父进程非阻塞等待!本质为了避免魔术数字#define WNOHANG 1;

Linux C语言写的 -> 系统调用接口 -> OS自己提供的接口 ->就是C语言函数 -> 系统提供的一般大写的标记位 WNOHANG,其实就是宏定义!

WNOHANG —— Wait No Hang(等待过程中没夯住),夯住本质就是指这个进程没有被CPU调度,要么是在阻塞队列,要么是在等待被调度!

3.5 waitpid系统调用接口分析

阻塞等待和非阻塞等待,一般都是在内核中阻塞,等待被唤醒,如scanf和cin,底层必定封装了系统调用(阻塞等待),父进程通过调用waitpid来进行等待,如果子进程没有退出,waitpid这个系统调用,立马返回(非阻塞)

3.6 阻塞等待代码和基于非阻塞调用的轮询检测方案

阻塞等待:

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
int main(){pid_t id = fork();if(id < 0){perror("fork");exit(1);//表示进程运行完毕,结果不正确}else if(id == 0){//子进程int cnt = 5;while(cnt){printf("cnt: %d, 我是子进程, pid : %d, ppid : %d\n", cnt, getpid(), getppid());sleep(1);cnt--;}exit(111);}else{//父进程printf("我是父进程, pid = %d, ppid = %d \n",getpid(), getppid());int status = 0;pid_t ret = waitpid(id, &status, 0);//printf("获取子进程退出信号: %d \n获取子进程退出码 %d\n", status & 0x7F,(status >> 8) & 0xFF);//pid_t ret = wait(NULL);//阻塞方式进行等待if(WIFEXITED(status)){printf("等待进程成功, ret = %d, 子进程退出码:%d\n", ret, WEXITSTATUS(status));}}return 0;
}

基于非阻塞调用的轮询检测方案:

#include<iostream>
#include<vector>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>typedef void(*handler_t)();
std::vector<handler_t> handlers;void fun_one(){printf("这是一个临时任务1\n");
}
void fun_two(){printf("这是一个临时任务2\n");
}void Load(){handlers.push_back(fun_one);handlers.push_back(fun_two);
}int main(){pid_t id = fork();if(id == 0){//子进程int cnt = 5;while(cnt--){printf("我是子进程:%d\n", cnt);sleep(1);}exit(11);}else{//父进程int quit = 0;while(!quit){int status = 0;int res = waitpid(id, &status, WNOHANG);if(res > 0){printf("进程等待成功,退出状态码:%d\n", WEXITSTATUS(status));quit = 1;}else if(res == 0){printf("等待子进程退出,处理其他事情中......\n");if(handlers.empty()){Load();}for(auto e : handlers){e();}}else{printf("进程等待错误\n");quit = 1;}sleep(1);}}return 0;
}

4. 进程程序替换

4.1 替换原理

fork之后,父子进程各自执行代码的一部分—如果子进程就想执行一个全新的程序呢?

进程的程序替换,来完成这个功能!

程序替换,是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间中!使得子进程拥有自己的代码!

 

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

进程替换的本质是加载新的代码和数据到内存,改变子进程页表的映射关系,完成替换

4.2 替换函数

其实有六种以exec开头的函数,统称exec函数:

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
//使用可变参数列表

实际Linux提供了七个替换函数!额外一个

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

4.3 函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

4.4 命名理解

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

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

v(vector) : 参数用数组

p(path) : 有p自动搜索环境变量PATH

e(env) : 表示自己维护环境变量

 exec调用举例如下:

#include <unistd.h>
int main()
{char* const argv[] = { "ps", "-ef", NULL };char* const envp[] = { "PATH=/bin:/usr/bin", "TERM=console", NULL };execl("/bin/ps", "ps", "-ef", NULL);// 带p的,可以使用环境变量PATH,无需写全路径execlp("ps", "ps", "-ef", NULL);// 带e的,需要自己组装环境变量execle("ps", "ps", "-ef", NULL, envp);execv("/bin/ps", argv);// 带p的,可以使用环境变量PATH,无需写全路径execvp("ps", argv);// 带e的,需要自己组装环境变量execve("/bin/ps", argv, envp);exit(0);
}

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在 man手册第3节。这些函数之间的关系如下图所示。

下图exec函数族 一个完整的例子:

 

4.5 实现简易shell

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左 向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结 束。

 然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。 所以要写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork)
  4. 替换子进程(execvp)
  5. 父进程等待子进程退出(wait)

 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>#define NUM 1024
#define SIZE 32
char cmd_line[NUM];void dealStr(char* str, char** argv) {char* dealstr = NULL;const char* sep = " ";size_t i = 0;for (dealstr = strtok(str, sep); dealstr != NULL; dealstr = strtok(NULL, sep)) {if (i < SIZE) {argv[i] = dealstr;i++;}}if(strcmp(argv[0], "ls") == 0){argv[i] = (char*)"--color=auto";}argv[++i] = NULL;
}//shell运行原理:通过让子进程执行命令,父进程等待&&解析命令
int main(){while(1){//1.打印提示信息printf("[root@localhost myshell]#");fflush(stdout);//2.获取用户输入memset(cmd_line, '\0', sizeof cmd_line);char *g_argv[SIZE] = { NULL };if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line) - 1] = '\0';//printf("echo:%s\n", cmd_line);//3.命令行字符串分割dealStr(cmd_line, g_argv);     //4.TODO 内置命令,让父进程(shell)自己执行的命令,叫做内置命令,内建命令// 内建命令本质就是shell中的一个函数调用if(strcmp("cd", g_argv[0]) == 0){//not child execute, father executeif(g_argv[1] != NULL)chdir(g_argv[1]);continue;}//5.forkpid_t id = fork();if(id < 0){perror("fork");exit(1);}else if(id == 0){execvp(g_argv[0], g_argv);exit(1);}int status = 0;pid_t ret = waitpid(id, &status, 0);if(ret > 0){printf("exit code:%d -> result:%s\n",WEXITSTATUS(status), strerror(WEXITSTATUS(status)));}else{printf("wait fail!\n");exit(1);}}//end whilereturn 0;
}

5. 函数和进程之间的相似性:

exec/exit就像call/return

一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。 这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程 之内的模式扩展到程序之间。

一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来 返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。

相关文章:

Linux——进程控制

目录 1. 进程创建 1.1 fork函数 1.2 fork系统调用内部宏观流程 1.3 fork后子进程执行位置分析 1.4 fork后共享代码分析 1.5 fork返回值 1.6 写时拷贝 1.7 fork常规用法 1.8 fork调用失败的原因 2.进程终止 2.1 进程退出场景 2.2 strerror函数—返回描述错误号的字符…...

剑指 Offer 59 - I. 滑动窗口的最大值 / LeetCode 239. 滑动窗口最大值(优先队列 / 单调队列)

题目&#xff1a; 链接&#xff1a;剑指 Offer 59 - I. 滑动窗口的最大值&#xff1b;LeetCode 239. 滑动窗口最大值 难度&#xff1a;困难 下一篇&#xff1a;剑指 Offer 59 - II. 队列的最大值&#xff08;单调队列&#xff09; 给你一个整数数组 nums&#xff0c;有一个大…...

【Linux后端服务器开发】IP协议

目录 一、IP协议概述 二、协议头格式 三、网段划分 四、IP地址的数量限制 五、路由 六、分片和组装 一、IP协议概述 主机&#xff1a;配有IP地址&#xff0c;但是不进行路由控制的设备 路由器&#xff1a;即配有IP地址&#xff0c;又能进行路由控制 节点&#xff1a;主…...

React组件进阶之children属性,props校验与默认值以及静态属性static

React组件进阶之children属性,props校验与默认值以及静态属性static 一、children属性二、props校验2.1 props说明2.2 prop-types的安装2.3 props校验规则2.4 props默认值 三、静态属性static 一、children属性 children 属性&#xff1a;表示该组件的子节点&#xff0c;只要组…...

ceph集群中RBD的性能测试、性能调优

文章目录 rados benchrbd bench-write测试工具Fio测试ceph rbd块设备的iops性能测试ceph rbd块设备的带宽测试ceph rbd块设备的延迟 性能调优 rados bench 参考&#xff1a;https://blog.csdn.net/Micha_Lu/article/details/126490260 rados bench为ceph自带的基准测试工具&am…...

texshop mac中文版-TeXShop for Mac(Latex编辑预览工具)

texshop for mac是一款可以在苹果电脑MAC OS平台上使用的非常不错的Mac应用软件&#xff0c;texshop for mac是一个非常有用的工具&#xff0c;广泛使用在数学&#xff0c;计算机科学&#xff0c;物理学&#xff0c;经济学等领域的合作&#xff0c;这些程序的标准tetex分布特产…...

简单认识redis高可用实现方法

文章目录 一、redis群集三种模式二、 Redis 主从复制1、简介2、作用&#xff1a;3、流程&#xff1a;4.配置主从复制 三、Redis 哨兵模式1、简介2、原理:3、作用&#xff1a;4、哨兵结构由两部分组成&#xff0c;哨兵节点和数据节点&#xff1a;5、故障转移机制&#xff1a;6、…...

搭建git服务器

1.创建linux账户&#xff0c;创建文件 adduser git passwd gitpsw su git pwd cd ~/ mkdir .ssh cd ~/.ssh touch authorized_keys 2.特别重要(单独起一行)&#xff0c;给文件设权限 chmod 700 /home/git/.ssh chmod 600 /home/git/.ssh/authorized_keys 3.本地生产密钥并把…...

线程中断机制

如何中断一个线程&#xff1f; 首先一个线程不应该由其他线程来强制中断或者停止&#xff0c;而是应该由线程自己自行停止。所以我们看到线程的stop()、resume()、suspend()等方法已经被标记为过时了。 其次在java中没有办法立即停止一个线程&#xff0c;然而停止线程显得尤为重…...

CollectionUtils工具类的使用

来自&#xff1a;小小程序员。 本文仅作记录 org.apache.commons.collections包下的CollectionUtils工具类&#xff0c;下面说说它的用法&#xff1a; 一、集合判空 通过CollectionUtils工具类的isEmpty方法可以轻松判断集合是否为空&#xff0c;isNotEmpty方法判断集合不为…...

基于Nonconvex规划的配电网重构研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

yolo系列笔记(v4-v5)

YOLOv4 YOLOv4网络详解_哔哩哔哩_bilibili 网络结构&#xff0c;在Yolov3的Darknet的基础上增加了CSP结构。 CSP的优点&#xff1a; 加强CNN的学习能力 去除计算瓶颈。 减少显存的消耗。 结构为&#xff1a; 、 其实还是类似与残差网络的结构&#xff0c;保留下采样之前…...

小白如何高效刷题Leetcode?

文章目录 为什么会有这样的现象&#xff1f;研究与学习人生而有别 如何解决困境&#xff1f;1. 要补的&#xff1a;化抽象为具体&#xff0c;列举找规律2. 要补的&#xff1a;前人总结的套路3. 与人交流探讨4. 多写总结文章 总结 明明自觉学会了不少知识&#xff0c;可真正开始…...

使用IDEA打jar包的详细图文教程

1. 点击intellij idea左上角的“File”菜单 -> Project Structure 2. 点击"Artifacts" -> 绿色的"" -> “JAR” -> Empty 3. Name栏填入自定义的名字&#xff0c;Output ditectory 选择 jar 包目标目录&#xff0c;Available Elements 里右击…...

《MySQL 实战 45 讲》课程学习笔记(二)

日志系统&#xff1a;一条 SQL 更新语句是如何执行的&#xff1f; 与查询流程不一样的是&#xff0c;更新流程还涉及两个重要的日志模块&#xff1a;redo log&#xff08;重做日志&#xff09;和 binlog&#xff08;归档日志&#xff09;。 重要的日志模块&#xff1a;redo l…...

微软亚研院提出模型基础架构RetNet或将成为Transformer有力继承者

作为全新的神经网络架构&#xff0c;RetNet 同时实现了良好的扩展结果、并行训练、低成本部署和高效推理。这些特性将使 RetNet 有可能成为继 Transformer 之后大语言模型基础网络架构的有力继承者。实验数据也显示&#xff0c;在语言建模任务上&#xff1a; RetNet 可以达到与…...

探索单例模式:设计模式中的瑰宝

文章目录 常用的设计模式有以下几种&#xff1a;一.创建型模式&#xff08;Creational Patterns&#xff09;&#xff1a;二.结构型模式&#xff08;Structural Patterns&#xff09;&#xff1a;三.行为型模式&#xff08;Behavioral Patterns&#xff09;&#xff1a;四.并发…...

Bobo String Construction 2023牛客暑期多校训练营4-A

登录—专业IT笔试面试备考平台_牛客网 题目大意&#xff1a;给出一字符串t&#xff0c;求一个长为n的字符串&#xff0c;使tst中包含且仅包含两个t 1<n<1000;测试样例组数<1000 思路&#xff1a;一开始很容易想到如果t里有1&#xff0c;s就全0&#xff0c;否则s就全…...

【React学习】React父子组件通讯

1. 父到子传值 在React框架中&#xff0c;父组件可以通过 props 将数据传递给子组件。子组件通过读取 props 来访问父组件传递过来的数据。 当父组件的 props 发生变化时&#xff0c;React 会自动重新渲染子组件以确保子组件中使用的数据保持同步。 父组件 import React, {…...

NASM汇编

1. 前置知识 1. 汇编语言两种风格 intel&#xff1a;我们学的NASM就属于Intel风格AT&T&#xff1a;GCC后端工具默认使用这种风格&#xff0c;当然我们也可以加选项改成intel风格 2. 代码 1. 段分布 .text: 存放的是二进制机器码&#xff0c;只读.data: 存放有初始化的…...

RabbitMQ 监控与调优实战指南(二)

五、调优策略与实战&#xff1a;对症下药提升性能 5.1 配置参数调优 在 RabbitMQ 的性能优化中&#xff0c;合理调整配置参数是关键的一环&#xff0c;这些参数涉及内存、磁盘、网络等多个资源层面&#xff0c;对 RabbitMQ 的整体性能有着深远的影响。 内存相关配置&#xf…...

SAP ECC 与 SAP S/4HANA 技术架构全面对比

SAP ECC 是过去几十年众多企业核心业务系统的基石&#xff0c;涵盖财务、物流、制造等关键领域。然而&#xff0c;随着数字化转型的加速和企业需求的增长&#xff0c;其架构日益显现局限。因此&#xff0c;SAP 推出了新一代 ERP 解决方案——SAP S/4HANA。它不仅在功能上做出优…...

Halcon光度立体法

1、光度立体法&#xff0c;可用于将对象的三维形状与其二维纹理&#xff08;例如打印图像&#xff09;分离。需要用不同方向而且已知照明方向的多个光源&#xff0c;拍摄同一物体的至少三张图像。请注意&#xff0c;所有图像的相机视角必须相同。 物体的三维形状主要被计算为三…...

VisDrone无人机视觉挑战赛观察解析2025.6.5

VisDrone无人机视觉挑战赛观察解析 历史沿革与发展进程 VisDrone无人机视觉挑战赛由天津大学联合国内外多所高校及科研机构发起,自2018年起依托ECCV、ICCV等顶级计算机视觉会议连续举办,已成为全球无人机视觉领域最具影响力的学术竞赛之一。赛事以推动无人机平台视觉算法创…...

数学复习笔记 25

今天能把第五章学完。加油。今年是最好上岸的一年。 5.23&#xff1a;全是单根&#xff0c;笑死&#xff0c;居然难受了。我现在每个题&#xff0c;都要总结。总结。总结实际上也总结不出啥东西。但是我一定要总结。主动让自己思考一下。老师的思路很清奇。他认为考的稀松平常…...

告别局域网:实现NASCab云可云远程自由访问

文章目录 前言1. 检查NASCab本地端口2. Qindows安装Cpolar3. 配置NASCab远程地址4. 远程访问NASCab小结 5. 固定NASCab公网地址6. 固定地址访问NASCab 前言 在数字化生活日益普及的今天&#xff0c;拥有一个属于自己的私有云存储&#xff08;如NASCab云可云&#xff09;已成为…...

VIN码车辆识别码解析接口如何用C#进行调用?

一、什么是VIN码车辆识别码解析接口 输入17位vin码&#xff0c;获取到车辆的品牌、型号、出厂日期、发动机类型、驱动类型、车型、年份等信息。无论是汽车电商平台、二手车商、维修厂&#xff0c;还是保险公司、金融机构&#xff0c;都能通过接入该API实现信息自动化、决策智能…...

Kubernetes 集群到 Jumpserver

以下是使用 ServiceAccount Token&#xff08;令牌&#xff09; 连接 Kubernetes 集群到 Jumpserver 的 详细分步指南&#xff0c;包括权限配置、Token 获取和常见问题排查。 1. 创建 ServiceAccount 并分配权限 1.1 创建 ServiceAccount 在 Kubernetes 集群中创建一个专用于…...

服务器重启后配置丢失怎么办?

服务器重启后配置丢失是一个常见问题&#xff0c;特别是在云服务器或容器环境中&#xff0c;若未正确保存或持久化配置&#xff0c;系统重启后就会恢复默认状态。下面是问题分析 解决方案&#xff1a; &#x1f9e0; 一、常见原因分析 原因描述❌ 配置保存在临时目录如 /tmp、…...

贪心算法应用:集合划分问题详解

贪心算法与集合划分问题详解 集合划分问题是组合优化中的经典问题&#xff0c;其核心目标是将元素集合划分为若干满足特定条件的子集。本文将深入探讨贪心算法在集合划分中的应用&#xff0c;涵盖算法原理、适用场景、Java实现细节及优化策略。 一、集合划分问题定义 1.1 基础…...