<5>_Linux进程控制

目录

一,进程创建,fork/vfork

1,fork创建子进程,操作系统都做了什么

2,写时拷贝的做了什么

二,进程终止,echo $?

1,进程终止时,操作系统做了什么

2,进程终止的常见方式

3,如何正确终止一个程序

三,进程等待

1,为什么要进行,进程等待

2,如何等待,等待是什么

(1)进程等待必要性

(2)进程等待的方法

3,获取子进程status

四,进程替换

1,替换原理

2,替换函数

3,函数理解

4,命名理解

五,微型shell,重新认识shell运行原理

1,原理

2,实现微型shell

点个赞吧!!!666


一,进程创建,fork/vfork

1,fork创建子进程,操作系统都做了什么

fork创建子进程,是不是系统里多了一个进程?是的!

进程=内核数据结构+ 进程代码和数据!

进程代码和数据,一般从磁盘中来,也就是你的C/C++程序,加载之后的结果!

创建子进程,给子进程分配对应的内核结构,必须子进程自己独有了,因为进程具有独立性!

理论上,子进程也要有自己的代码和数据!

可是一般而言,我们没有加载的过程,也就是说,子进程没有自己的代码和数据!

所以,子进程只能”使用“父进程的代码和数据!

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

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

对于数据而言,什么时候分离?

如果,创建进程的时候,就直接拷贝分离。这杨样会导致,可能拷贝子进程根本不会用到数据空间,即使用到了,也可能只是读取。

而即使是OS,也不知道哪些空间可能会被写入,即使提前拷贝了,也不会立马使用。所以,OS选择了写时拷贝技术,将父子进程的数据进行分离。

OS为何要选择写时拷贝的技术,对父子进程进行分离?

用的时候再给你分配,是高效使用内存的一种表现,而且OS无法在代码执行前预知哪些空间会被访问。

所以,fork创建父子进程之后,代码是共享的,内核的数据会各进程写时拷贝一份。

2,写时拷贝的做了什么

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

分配新的内存块和内核数据结构给子进程。

将父进程部分数据结构内容拷贝至子进程。

添加子进程到系统进程列表当中。

fork返回,开始调度器调度。

fork之后,父子进程代码共享是所有代码都共享的。

(1)我们的代码汇编之后会,会有很多行代码,而且每行代码加载到内存之后,都有对应的地址。

(2)因为进程随时可能被中断(可能并没有执行完),下次回来,还必须从之前的位置继续执行(不是最开始的位置),这就要求CPU必须随时记录下,当前进程执行的位置,所以,CPU内有对应的寄存器EIP(PC程序计数器),用来记录当前执行位置。

(3)寄存器在CPU内,只有一份,寄存器内的数据,是可以有多份的。进程的上下文数据,在fork创建子进程之后,对于子进程已经不重要了。虽然父子进程各自调度,各自都会修改EIP,但是已经不重要了,因为子进程已经认为自己的EIP起始值,就是fork之后的代码。

二,进程终止,echo $?

1,进程终止时,操作系统做了什么

当然是要释放进程申请的,相关内核数据结构和对应的数据与代码,本质就是释放系统资源。

2,进程终止的常见方式

(1)进程退出场景:

代码运行完毕,结果正确。

代码运行完毕,结果不正确。

代码异常终止,程序崩溃了。

(2)进程退出码:

查看退出码使用:echo $?

0,表示成功。

非0,表示失败,具体是几,要看退出的原因。

程序崩溃的时候,退出码无意义。一般而言,退出码对应的return语句,没有被执行。

[user@iZwz9eoohx59fs5a6ampomZ linux-52]$ cat exitcode.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>int main()
{int number;for(number = 0; number < 100; number++){printf("%d: %s\n", number, strerror(number));}
}
[user@iZwz9eoohx59fs5a6ampomZ linux-52]$ ./exitcode 
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
。。。。。。

3,如何正确终止一个程序

使用 exit vs return 语句。

return语句,就是终止进程的,return+退出码。

exit语句,在代码的任何地方调用,都表示直接终止进程。

exit的头文件是stdlib.h,exit(int status)有一个参数,这个参数就是退出码。

exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

1. 执行用户通过 atexit或on_exit定义的清理函数

2. 关闭所有打开的流,所有的缓存数据均被写入

3. 调用_exit

三,进程等待

1,为什么要进行,进程等待

(1)子进程退出,父进程不管子进程,子进程就要处于僵尸状态。

(2)父进程创建子进程,是要让子进程办事的,那么子进程把任务完成的怎么样,父进程关系吗?如果需要,如果得知?如果不需要,如何处理?

2,如何等待,等待是什么

(1)进程等待必要性

之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。

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

最后,父进程派给子进程的任务完成的如何,我们需要知道。如果子进程运行完成,结果对还是不对,或者是否正常退出。

父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

(2)进程等待的方法

wait方法

头文件:

#include<sys/types.h>

#include<sys/wait.h>

函数:

pid_t wait(int*status);

返回值:

成功返回被等待进程pid,失败返回-1。

参数:

输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

基本验证-等待僵尸进程

// 会话·1[user@iZwz9eoohx59fs5a6ampomZ linux-53]$ cat myproc.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.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(0);}else{//父进程printf("我是父进程,pid: %d, ppid: %d\n", getpid(), getppid());sleep(7);pid_t ret = wait(NULL);//阻塞式等待if(ret > 0){printf("等待子进程成功,ret: %d\n", ret);}while(1){printf("cnt: %d, 我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);                                              }}
}// 会话·2[user@iZwz9eoohx59fs5a6ampomZ linux-53]$ ./myproc 
我是父进程,pid: 31946, ppid: 30659
cnt: 5, 我是子进程, pid: 31947, ppid: 31946
cnt: 4, 我是子进程, pid: 31947, ppid: 31946
cnt: 3, 我是子进程, pid: 31947, ppid: 31946
cnt: 2, 我是子进程, pid: 31947, ppid: 31946
cnt: 1, 我是子进程, pid: 31947, ppid: 31946
等待子进程成功,ret: 31947
cnt: 31946, 我是父进程, pid: 30659, ppid: -386691177
cnt: 31946, 我是父进程, pid: 30659, ppid: -386691177
cnt: 31946, 我是父进程, pid: 30659, ppid: -386691177
^C

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,子进程存在且正常运行,则进程可能阻塞。

如果不存在该子进程,则立即出错返回。

3,获取子进程status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。

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

否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位)

// 会话1[user@iZwz9eoohx59fs5a6ampomZ linux-53]$ ./myproc
我是子进程: 5
我是子进程: 4
我是子进程: 3
我是子进程: 2
我是子进程: 1
子进程执行完毕,子进程的退出码:11
[user@iZwz9eoohx59fs5a6ampomZ linux-53]$ cat myproc.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){// 子进程int cnt = 5;while(cnt){printf("我是子进程: %d\n", cnt);sleep(1);cnt--;}exit(11);}else{// 父进程int status = 0;// 只有子进程退出的时候,父进程才会waitpid函数,进行返回,此时父进程还活着// wait/waitpid 可以在目前的情况下,让进程退出具有一定的顺序性// 将来可以让父进程进行更多的收尾工作// id > 0  等待指定进程// id== 0  TODO// id== -1 等待任意一个子进程退出,等价于wait()pid_t result = waitpid(id, &status, 0);//阻塞状态下,等待子进程退出if(result > 0){// 可以不这么检测// printf("父进程等待成功,退出码:%d\n,退出信号:%d\n", (status>>8)&0xFF, status & 0x7F);if(WIFEXITED(status)){// 子进程是正常退出的printf("子进程执行完毕,子进程的退出码:%d\n", WEXITSTATUS(status));}else{   printf("子进程异常退出:%d\n", WIFEXITED(status));}}}
}// 会话2
[user@iZwz9eoohx59fs5a6ampomZ linux-53]$ while :; do ps axj | head -1 && ps axj | grep myproc |grep ; sleep 1; echo "-----------------------------------------";donePPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30659 17885 17885 30659 pts/1    17885 S+    1001   0:00 ./myproc
17885 17886 17885 30659 pts/1    17885 S+    1001   0:00 ./myproc
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30659 17885 17885 30659 pts/1    17885 S+    1001   0:00 ./myproc
17885 17886 17885 30659 pts/1    17885 S+    1001   0:00 ./myproc
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30659 17885 17885 30659 pts/1    17885 S+    1001   0:00 ./myproc
17885 17886 17885 30659 pts/1    17885 S+    1001   0:00 ./myproc
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30659 17885 17885 30659 pts/1    17885 S+    1001   0:00 ./myproc
17885 17886 17885 30659 pts/1    17885 S+    1001   0:00 ./myproc
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30659 17885 17885 30659 pts/1    17885 S+    1001   0:00 ./myproc
17885 17886 17885 30659 pts/1    17885 S+    1001   0:00 ./myproc
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
-----------------------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
^C

四,进程替换

1,替换原理

fork()之后,父子各自执行父进程代码的一部分,父子代码共享,数据写时拷贝各自一份。如果子进程就想有自己的代码,执行一个全新的程序呢?这时就使用到进程替换。

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

2,替换函数

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

#include <unistd.h>
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 execve(const char *path, char *const argv[], char *const envp[]);

3,函数理解

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。

如果调用出错则返回-1。

所以exec函数只有出错的返回值而没有成功的返回值。

最后一个参数,必须是NULL,要标识参数传递完毕。

案例:创建子进程,只使用最简单的exec函数。

printf("当前进程的结束代码!\n");为什么不打印呢?

因为,execl是程序替换,该函数成功调用之后,会将当前进程的所有代码数据都进行替换,包括已经执行和没有执行的,所以一旦调用成功,后续的所有代码都不会执行。

printf("当前进程的开始代码!\n");也被替换了,只是因为它在execl之前就打印了,才会显示出来"当前进程的开始代码!"。

[user@iZwz9eoohx59fs5a6ampomZ linux-54]$ cat myproc.c
#include <stdio.h>
#include <unistd.h>int main()
{printf("当前进程的开始代码!\n");printf("当前进程的结束代码!\n");return 0;
}
[user@iZwz9eoohx59fs5a6ampomZ linux-54]$ ./myproc 
当前进程的开始代码!
当前进程的结束代码!
[user@iZwz9eoohx59fs5a6ampomZ linux-54]$ cat myproc.c
#include <stdio.h>
#include <unistd.h>int main()
{printf("当前进程的开始代码!\n");execl("/usr/bin/ls", "ls", "-l", "-a", "-i", NULL);printf("当前进程的结束代码!\n");return 0;
}
[user@iZwz9eoohx59fs5a6ampomZ linux-54]$ ./myproc 
当前进程的开始代码!
total 28
1449872 drwxrwxr-x  2 user user 4096 Jul  3 13:46 .
1441793 drwxrwxr-x 11 user user 4096 Jul  3 13:34 ..
1449895 -rw-rw-r--  1 user user   64 Jul  3 13:35 makefile
1449894 -rwxrwxr-x  1 user user 8536 Jul  3 13:46 myproc
1449896 -rw-rw-r--  1 user user  222 Jul  3 13:45 myproc.c

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);
}

五,微型shell,重新认识shell运行原理

1,原理

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

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

1. 获取命令行

2. 解析命令行

3. 建立一个子进程(fork)

4. 替换子进程(execvp)

5. 父进程等待子进程退出(wait)

2,实现微型shell

根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了.

#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
#define SEP " "// 保存完整的命令行字符串
char cmd_line[NUM];
// 保存打散之后的命令行字符串
char *g_argv[SIZE];// shell 运行原理:通过子进程执行命令,父进程等待&&解析命令
int main()
{//0.命令行解释器,一定是一个常驻内存的进程,不退出while(1){//1.打印出提示信息 [root@localhost myshell]#printf("[root@localhost myshell]# ");fflush(stdout);memset(cmd_line, '\0', sizeof cmd_line);//2.获取用户的输入,输入的是各自指令和选型"ls -a -l -i"if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;                                                                            }cmd_line[strlen(cmd_line)-1] = '\0';//去掉空行,把\n变成\0,字符串长度下标就是\n,"ls -a -l -i\n\0 "//printf("echo: %s\n", cmd_line);//3.把输入的命令行字符串解析,从"ls -a -l -i",变成"ls","-a","-i","-l"//第一次调用,要传入原始字符串g_argv[0] = strtok(cmd_line, SEP);int index = 1;// 加颜色if(strcmp(g_argv[0], "ls") == 0){g_argv[index++] = "--color=auto";                                                    }// 设置ll命令别名if(strcmp(g_argv[0], "ll") == 0){g_argv[0] = "ls";g_argv[index++] = "-l";g_argv[index++] = "--color=auto";}//第二次调用,如果还要解析原始字符串,传入NULLwhile(g_argv[index++] = strtok(NULL, SEP));//for(index = 0; g_argv[index]; index++)//    printf("g_argv[%d]: %s\n", index, g_argv[index]);//4.执行命令,内置命令,让父进程(shell)自己执行的命令,就叫做内置(内键)命令        //内置命令,本质就是shell中的一个函数调用if(strcmp(g_argv[0], "cd") == 0)        {if(g_argv[1] != NULL) chdir(g_argv[1]);continue;}//5.父进程调用子进程执行,fork()//子进程pid_t id = fork();if(id == 0){printf("下面的功能让是子进程执行的\n");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\n", WEXITSTATUS(status));}
}

点个赞吧!!!666

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

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

相关文章

阿里云服务器正确配置 Docker 国内镜像的方法

&#x1f4e6; 原理说明&#xff1a;什么是“Docker 镜像加速器”&#xff1f; Docker 默认会从官方仓库 registry-1.docker.io 拉取镜像。由于网络原因&#xff0c;在中国大陆访问这个地址较慢甚至失败。 镜像加速器的作用是&#xff1a; 在国内部署一个缓存服务器&#xf…

PH热榜 | 2025-07-05

1. todai 标语&#xff1a;你的第一份个性化快乐生活指数 介绍&#xff1a;Todai 是你个人的人工智能助手&#xff0c;帮助你获得心理清晰和情感平衡。你可以随时随地记录自己的情绪&#xff0c;发现情绪变化的规律&#xff0c;并获取基于科学的工具。 产品网站&#xff1a;…

c++ duiLib环境集成

duiLib的Github链接&#xff1a;https://github.com/duilib/duilib 使用vcpkg快速安装duilib以及配置。步骤如下&#xff1a; 1、用git下载vcpkg&#xff0c;下载报错&#xff0c;这个错误通常表明在Git克隆过程中&#xff0c;与GitHub服务器的SSL连接被意外重置。改用http下…

一项基于粒子图像测速PIV系统的泥石流模拟冲击实验

1实验背景 全国进入“七下八上”防汛关键期&#xff0c;泥石流作为山区常见地质灾害&#xff0c;突发性强&#xff0c;破坏力大&#xff0c;对人民群众生命财产安全造成威胁&#xff0c;传统观测手段难以实现对碎石运动轨迹与水流场耦合效应的精细观测。而粒子图像测速PIV技术…

ADAS功能介绍

ADAS功能介绍 ADAS&#xff08;Advanced Driving Assistance System&#xff09;高级驾驶辅助系统&#xff0c;可分为如下几大类功能。 IA&#xff08;Information Assist&#xff09;信息辅助类 IA类功能&#xff0c;均不包含驾驶行为的控制。这些功能又可以进一步细分为三…

【LUT技术专题】CLUT代码讲解

本文是对CLUT技术的代码讲解&#xff0c;原文解读请看CLUT文章讲解。 1、原文概要 CLUT利用矩阵在保持3DLUT映射能力的前提下显著降低了参数量。整体流程如下所示。 整体还是基于3D-LUT的框架&#xff0c;只不过添加了一个压缩自适应的变换矩阵。作者使用的损失函数在3DLUT的…

在LinuxMint 22.1(Ubuntu24.04)上安装使用同花顺远航版

刚刚在LinuxMint 22.1(Ubuntu24.04)安装完成同花顺远航版&#xff0c;体验特别好&#xff0c;忍不住要及时给深受Linux平台无好用行情软件之苦的朋友们进行分享了。在此之前我一直只能用同花顺Linux原生版的行情软件&#xff0c;但是该软件只有很基本的行情功能&#xff0c;而且…

解决vue3路由配合Transition时跳转导致页面不渲染的问题

问题复现 <router-view v-slot"{ Component, route }"><transition name"fade" mode"out-in"><keep-alive><component :is"Component" :key"route.path" /></keep-alive></transition>…

java: 无法访问org.springframework.boot.SpringApplication,类文件具有错误的版本 61.0, 应为 52.0

问题 java: 无法访问org.springframework.boot.SpringApplication 错误的类文件: /D:/.m2/repository/org/springframework/boot/spring-boot/3.3.13/spring-boot-3.3.13.jar!/org/springframework/boot/SpringApplication.class 类文件具有错误的版本 61.0, 应为 52.0 请删除…

Docker拉取nacos镜像

以下是使用 Docker 拉取并运行 Nacos&#xff08;阿里巴巴开源的配置中心和服务发现组件&#xff09;镜像的详细指南&#xff1a; 1. 拉取 Nacos 官方镜像 拉取最新版 Nacos 镜像&#xff08;推荐指定版本以避免兼容性问题&#xff09;&#xff1a; # 拉取最新版本&#xff…

【CTF-Web环境搭建】kali

Kali虚拟机下载 这里在官网上下载下kali虚拟机Get Kali | Kali Linux 网速比较慢的话打开一下加速器 下载完成后 得到一个压缩包 选择一个合适的地方将这个压缩包解压一下 记住这个文件目录 这里为了后续方便 简历一个叫做Virtual Machines的文件夹 里面就可以放不同的虚拟机…

微服务架构的演进:迈向云原生

微服务架构的演进&#xff1a;迈向云原生ps:最近在学习的时候&#xff0c;发现好多技术方案最终都有云原生的影子&#xff0c;这里浅谈一下云原生的发展趋势随着互联网技术的发展&#xff0c;软件开发模式经历了从单体应用到微服务架构的重大转变。而在今天&#xff0c;微服务架…

服务器如何配置防火墙规则开放/关闭端口?

配置服务器防火墙规则&#xff08;开放/关闭端口&#xff09;是服务器安全管理的基础操作&#xff0c;不同操作系统和防火墙工具的配置方式有所不同。以下是主流系统的详细操作指南&#xff1a;一、Linux系统&#xff08;iptables/firewalld/UFW&#xff09;1. iptables&#x…

基于SpringBoot+Redis实现外呼频次限制功能

针对外呼场景中的号码频次限制需求&#xff08;如每3天只能呼出1000通电话&#xff09;&#xff0c;我可以提供一个基于Spring Boot和Redis的完整解决方案。 方案设计 核心思路 使用Redis的计数器过期时间机制 采用滑动窗口算法实现精确控制 通过Lua脚本保证原子性操作 实…

下一代 2D 图像设计工具「GitHub 热点速览」

长期以来&#xff0c;2D 设计领域似乎已是 Adobe 与 Figma 的天下&#xff0c;层叠的图层、熟悉的工具栏&#xff0c;一切都显得那么顺理成章&#xff0c;却也让不少设计师在创意的边界上感到了些许乏力。当我们以为设计工具的革新只能是小修小补时&#xff0c;开源社区再次扮演…

L58.【LeetCode题解】模拟算法习题集1(Z 字形变换、外观数列)

目录 1.Z 字形变换 方法1: 模拟 代码 提交结果 方法2:优化后的模拟 代码 提交结果 2.外观数列 方法1:模拟 代码 提交结果 方法2:打表 知识回顾 代码 1.Z 字形变换 https://leetcode.cn/problems/zigzag-conversion/ 将一个给定字符串 s 根据给定的行数 numRows &…

Flink MySQL CDC 环境配置与验证

一、MySQL 服务器配置详解 1. 启用二进制日志&#xff08;Binlog&#xff09; MySQL CDC 依赖二进制日志获取增量数据&#xff0c;需在 MySQL 配置文件&#xff08;my.cnf 或 my.ini&#xff09;中添加以下配置&#xff1a; # 启用二进制日志 log-binmysql-bin # 二进制日志…

如何查看自己电脑的CUDA版本?

在搜索栏输入命令提示符 打开 输入 nvidia-smi图片中的两个是CUDA版本和显卡的信息

opencv使用 GStreamer 硬解码和 CUDA 加速的方案

在Conda环境中从源代码编译OpenCV&#xff08;支持CUDA和GStreamer&#xff09; 以下是完整的方案步骤&#xff0c;包括必要的依赖库安装过程&#xff1a; 1. 安装Miniconda&#xff08;如果尚未安装&#xff09; # 下载Miniconda安装脚本 wget https://repo.anaconda.com/m…

Java面试宝典:多线程一

1. run() vs start() 陷阱题 下面程序的运行结果 public static void main(String[] args) {Thread t = new Thread(