当前位置: 首页 > 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: 存放有初始化的…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用

文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么&#xff1f;1.1.2 感知机的工作原理 1.2 感知机的简单应用&#xff1a;基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

多模态图像修复系统:基于深度学习的图片修复实现

多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用

一、方案背景​ 在现代生产与生活场景中&#xff0c;如工厂高危作业区、医院手术室、公共场景等&#xff0c;人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式&#xff0c;存在效率低、覆盖面不足、判断主观性强等问题&#xff0c;难以满足对人员打手机行为精…...

深入浅出Diffusion模型:从原理到实践的全方位教程

I. 引言&#xff1a;生成式AI的黎明 – Diffusion模型是什么&#xff1f; 近年来&#xff0c;生成式人工智能&#xff08;Generative AI&#xff09;领域取得了爆炸性的进展&#xff0c;模型能够根据简单的文本提示创作出逼真的图像、连贯的文本&#xff0c;乃至更多令人惊叹的…...

小木的算法日记-多叉树的递归/层序遍历

&#x1f332; 从二叉树到森林&#xff1a;一文彻底搞懂多叉树遍历的艺术 &#x1f680; 引言 你好&#xff0c;未来的算法大神&#xff01; 在数据结构的世界里&#xff0c;“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的&#xff0c;它…...

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…...