Linux 进程控制进程终止

news2024/11/17 12:57:41

目录

一、fork函数

1、概念 

2、父子进程的共享

3、 为什么父子进程会从fork()调用之后的位置继续执行代码

4、写时拷贝

5、为什么需要写时拷贝

6、写时拷贝的好处

7、fork常规用法

8、fork调用失败的原因

9、查看系统最大进程数

二、进程终止

1、进程退出场景

2、查看进程退出码

3、进程终止常见方式

正常终止(main,exit,_exit)

异常退出

4、三种进程终止区别

库函数(如exit())

系统接口(如_exit())

缓冲区

return 与进程退出


一、fork函数

1、概念 

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);

返回值:自进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核会进行以下操作:
  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

2、父子进程的共享

  • 当创建子进程时,操作系统会为之分配特定的内核结构,以确保其具有独立性。理论上,这意味着子进程应拥有自己的代码和数据副本。
  • 然而,在实际操作中,我们通常不会经历一个显式的加载过程来为子进程提供独立的代码和数据。这导致子进程实际上是在“共享”父进程的代码和数据。

  • 对于代码部分,由于它是只读的,父子进程共享同一份代码不会引起问题。这种方法既高效又节省内存,因为不需要为每个子进程复制代码。
  • 对于数据部分,情况则大不相同。
    • 由于数据可能会被进程修改,保持数据的独立性变得尤为重要。如果父子进程共享相同的数据副本,任何一方对数据的修改都会影响到另一方,破坏了进程间的独立性。
    • 因此,操作系统采用了一种机制,如写时拷贝,在必要时将数据复制一份,确保子进程在需要修改数据时能够拥有自己的独立数据副本。这样既保证了进程间的独立性,又在不必要的复制操作上节省了资源。

观察下面程序:

#include <stdio.h>
#include <unistd.h>

int main()
{
    pid_t id=fork();
    if(id<0)
    {
        //创建失败
        perror("fork");
        return 1;
    }
    else if(id==0)
    {
        //child process
        while(1)
        {
            printf("I am child, pid:%d, ppid:%d\n",getpid(),getppid());
            sleep(1);
        }
    }
    else
    {
        //parent process
        while(1)
        {
            printf("I am father, pid:%d, ppid:%d\n",getpid(),getppid());
            sleep(1);
        }   
    }
    printf("you can see me\n");
    sleep(1);//进程退出顺序不一样
    return 0;
}

fork创建进程后,根据id的不同执行父子进程对应的代码,也就是说fork函数会有两个返回值? 

 

  • 使用fork()函数创建一个新的子进程,该子进程是调用进程的副本。fork()之后,程序会有两个不同的执行流,父进程和子进程将会从fork()调用之后的位置继续执行代码。(思考为什么?)
  • 由于fork()在父进程和子进程中都会进行返回,也就是fork()函数内部return会被执行两次:在父进程中返回新创建的子进程的PID(进程ID),在子进程中返回0,所以return的本质就是对变量id进行写入。

3、 为什么父子进程会从fork()调用之后的位置继续执行代码

fork()之后,程序会有两个不同的执行流,父进程和子进程将会从fork()调用之后的位置继续执行代码。

fork之后,父子进程代码共享,是after这部分共享,还是所有的代码都共享?

  • 答案:所有代码都会共享。

解释上面问题前,我们先来复习两个概念:

  • 程序计数器(PC):PC是一个特殊的寄存器,用来存储下一条要执行的指令的地址。在任意时刻,PC都指向程序中当前正在执行或即将执行的代码位置。
  • 进程上下文:当操作系统进行进程切换时,它保存当前进程的状态(包括PC和其他寄存器的值),以便该进程在未来某个时刻继续执行时,能从正确的位置恢复。这个保存的状态集合就是所谓的“进程上下文”。

我们现在来回答刚才的问题: 

  • fork()之后的行为:当fork()被调用时,操作系统会为子进程创建一个与父进程几乎完全相同的副本,包括代码、数据、堆和栈的复制(通过写时复制技术优化),但是父子进程会有各自独立的进程上下文。
  • 尽管它们可能共享相同的代码段,但是各自的PC值会指向fork()调用后的下一条指令,使得父子进程从fork()之后的代码位置继续各自的执行流。这意味着,虽然父子进程在物理上共享代码,但它们执行的路径可以完全不同,因为它们的执行流、栈、寄存器状态等是独立的。

因此,在fork()之后,子进程认为自己的程序计数器(PC)起始值就是fork()调用之后的代码位置,从这一点继续执行,而与父进程的执行流相互独立。这种设计使得父子进程能在共享代码的同时,执行各自独立的任务。

4、写时拷贝

Linux操作系统在使用写时拷贝(Copy-On-Write, COW)技术管理进程内存时,确实会使得父进程和子进程共享代码段,而只有在写操作尝试发生时才会拷贝数据段或者堆栈段。这种设计是为了提高内存使用效率和减少不必要的数据复制。

 

  • 共享代码段:当一个进程(父进程)创建一个子进程时,Linux操作系统会使子进程与父进程共享相同的代码段。这是因为代码是静态的、只读的,不会被进程修改,所以多个进程可以安全地共享同一份代码。这部分共享的代码通常存在于操作系统的物理内存中,在内存的某个位置。

  • 拷贝数据段和堆栈段:对于数据段和堆栈段,原则上是每个进程有自己的一份,以存储各自的全局变量、局部变量、堆分配的内存等。当使用写时拷贝机制时,只有当某个进程试图修改这些共享的数据段或堆栈段中的内容时,操作系统才会为该进程创建这部分内存的私有拷贝,以确保修改不会影响到其他共享这部分内存的进程。

  • 访问代码:当子进程访问代码段时,它通过自己的页表映射到相同的物理内存地址上,这块内存存储了父进程的代码段。由于代码是只读的,所以多个进程(无论是父进程还是子进程)都可以共享这一块内存,而不会造成数据的不一致。

  • 修改数据:当子进程需要修改数据时,操作系统利用写时拷贝机制来处理这一请求。如果子进程尝试写入一个共享的数据页,操作系统会首先为这个数据页创建一个拷贝,然后更新子进程的页表,使得这个被写入的地址映射到这个新的、属于子进程自己的物理内存页上。因此,这次写操作只会影响子进程自己的数据页,而不会影响到父进程或其他共享这个数据页的进程。

通过下面的例子来理解一下: 

#include <stdio.h>
#include <unistd.h>

int g_val=100;

int main()
{
    pid_t id=fork();
    if(id==0)
    {
        int n=0;
        while(1)
        {
            printf("I am child. pid: %d, ppid %d, g_val: %d, &g_val: %p\n",\
                    getpid(),getppid(),g_val,&g_val);
            sleep(1);
            n++;
            if(n==5)
            {
                g_val=200;
                printf("child change g_val 100 -> 200 success\n");
            }
        }
    }
    else
    {
        while(1)
        {
            printf("I am father. pid: %d, ppid %d, g_val: %d, &g_val: %p\n",\
                    getpid(),getppid(),g_val,&g_val);
            sleep(1);
        }   
    }
    return 0;
}

我们可以看到,一开始子进程和父进程的g_val的值相同,而且地址也相同。当子进程修改全局变量g_val的值之后,父子进程的g_val的值不同了,但父子进程中g_val的地址依然相同,这是为什么呢?

  • 我们观察到父进程和子进程在子进程修改全局变量g_val之前共享相同的变量值和地址。这是因为Linux采用了写时拷贝(Copy-On-Write, COW)机制来优化内存使用

    • fork()调用之后(子进程未修改g_val时),父进程和子进程实际上共享相同的物理内存页,包括全局变量g_val。这就是为什么一开始g_val的地址在父子进程中看起来相同的原因。

  • 当子进程尝试修改g_val的值时,操作系统侦测到这一写操作,并触发写时拷贝机制。

    • 这时,操作系统为子进程分配一个新的物理内存页,用于存储修改后的g_val值,而父进程继续使用原来的物理内存页。

    • 因此,尽管父子进程中g_val的虚拟地址未变,它们现在指向不同的物理地址,这就是为什么修改后子进程中g_val的值变为200,而父进程中g_val的值仍然是100的原因。

总结来说,

  1. 写时拷贝机制确保了在子进程尝试修改共享数据之前,父子进程可以高效地共享相同的物理内存页。
  2. 一旦发生写操作,操作系统会为子进程分配新的物理内存页,以保持父子进程数据的独立性。

这就解释了为什么在子进程修改g_val之后,父子进程的g_val值不同,但它们的虚拟地址仍然相同的现象。

5、为什么需要写时拷贝

字面意思理解:为何要选择写时拷贝的技术?对父子进程进行分离

  1. 用的时候,再分配,是高效使用内存的一种表现。
  2. 操作系统无法在代码执行前预知那些空间会被访问,所以不提前分配。

写时拷贝本质上是一种延迟分配策略。它允许操作系统延迟数据的复制直到实际需要修改数据时,从而优化内存使用和提高系统效率。

对于数据处理而言,进程创建时采用“写时拷贝”(Copy-On-Write, COW)技术的原因可以从几个方面来理解:

  1. 内存效率:当创建一个进程时,操作系统初期并不直接为子进程拷贝所有父进程的数据空间,因为这样做可能会拷贝大量子进程根本不会用到的数据。这不仅浪费了宝贵的内存资源,还增加了进程创建的时间。通过写时拷贝技术,只有当父进程或子进程尝试写入某个内存区域时,才会实际拷贝这部分内存。这样,只有被修改的数据才会被复制,未修改的数据则可以由父子进程共享,从而极大地提高了内存使用效率。

  2. 不可预知性:在进程运行之前,操作系统通常无法知道哪些具体的数据会被访问或修改。如果采用预先拷贝的方式,可能会拷贝很多最终未被使用或仅被读取的数据。写时拷贝技术允许系统延迟拷贝的决定,直到确实需要修改数据的那一刻,从而更加合理地利用资源。

  3. 优化资源利用:通过延迟数据拷贝,写时拷贝不仅减少了内存的占用,也减轻了操作系统的负担,因为只有在必要时才进行数据复制。这种策略意味着系统可以更有效地管理内存,同时也简化了进程创建和数据管理的过程。

6、写时拷贝的好处

  1. 因为写时拷贝的存在,父子进程得以彻底分离,这样就保证了进程独立性。
  2. 写时拷贝,是一种延时申请技术,可以提高整机内存的使用率。 

7、fork常规用法

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

8、fork调用失败的原因

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

9、查看系统最大进程数

#include <stdio.h>
#include <unistd.h>

int main()
{
    int max_cnt=0;
    while(1)
    {
        pid_t id=fork();
        if(id<0)
        {
            printf("fork error:%d\n",max_cnt);
            break;
        }
        if(id==0)
        {
            while(1)
            {
                sleep(1);
            }
        }
        else
        {

        }
        max_cnt++;
    }
}

输出结果:

删除刚刚用于统计所创建的进程

二、进程终止

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

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

1、进程退出场景

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

在程序设计和开发中,main函数的返回值扮演着重要的角色,它向操作系统或上一级进程传达了程序执行的最终状态。这个返回值,也被称为退出码(Exit Code)或退出状态(Exit Status),是进程终止时的一个标识,用于表示程序的执行结果。

  1. 代码执行完成,结果正确: 这是最理想的情况,程序按照预期执行完所有的操作,并且输出了正确的结果。在这种情况下,进程通常通过执行完毕其入口点(如C语言的main函数)中的所有代码自然终止。如果是在main函数中,通常会看到一个return 0;语句,表示程序正常退出。

  2. 代码执行完成,结果不正确: 程序虽然执行到了最后,但是由于逻辑错误、错误的输入等原因,其输出的结果并不是预期的。在这种情况下,进程也是通过执行完毕其入口点中的所有代码终止,但是可以通过main函数返回一个非零值来表示错误的发生。这个非零值可以是任意的,不同的值可以代表不同的错误类型,这样做可以帮助开发者快速定位问题。

  3. 代码没有执行完,程序崩溃: 这种情况通常是由于程序中存在严重的错误,如访问非法内存、除零操作等,导致操作系统强制终止了程序的执行。在程序崩溃的情况下,main函数中的return语句不会被执行,因此程序的退出码没有意义。程序崩溃通常需要开发者通过调试工具来分析程序的崩溃原因。

2、查看进程退出码

可以通过在终端中执行echo $?命令来查看上一个终止的进程的退出码。

我们看下面代码,可以用变量代替退出码。

#include <stdio.h>
#include <unistd.h>
int sum(int top)
{
    int s=0;
    for(int i=1;i<=top;i++)
    {
        s+=i;
    }
    return s;
}
int main()
{
    int ret=0;
    printf("hello world: pid: %d, ppid: %d\n",getpid(),getppid());
    int res=sum(100);
    if(res!=5050)
    {
        ret=1;
    }
    return ret;
}
使用echo $? 查看退出码为0,表示计算1到100的累加和结果等于5050,程序判断正确。
[hbr@VM-16-9-centos control]$ make
gcc -o myproc myproc.c
[hbr@VM-16-9-centos control]$ ./myproc 
hello world: pid: 7676, ppid: 4960
[hbr@VM-16-9-centos control]$ echo $?
0
将res修改为计算1到101的累加和,退出码为1,表示程序判断错误,累加和不等于5050。
[hbr@VM-16-9-centos control]$ vim myproc.c
[hbr@VM-16-9-centos control]$ make
gcc -o myproc myproc.c
[hbr@VM-16-9-centos control]$ ./myproc 
hello world: pid: 8304, ppid: 4960
[hbr@VM-16-9-centos control]$ echo $?
1

打印系统错误信息:

  • strerror() 函数会根据传入的错误号码返回相应的错误信息字符串。这些错误信息是系统定义的,可以帮助开发者诊断程序中出现的问题。

#include <stdio.h>
#include <string.h>

int main()
{
    for(int number=0;number<100;number++)
    {
        printf("%d: %s\n",number,strerror(number));
    }
}

使用ls 查看一个不存在的目录或文件,这时打印错误信息,得到错误码为2,由上图可知,2对应的就是下面程序的错误信息 No such file or directory。

[hbr@VM-16-9-centos control]$ ls file
ls: cannot access file: No such file or directory
[hbr@VM-16-9-centos control]$ echo $?
2

3、进程终止常见方式

正常终止(main,exit,_exit)

正常终止意味着程序按预期完成了其任务或者明确地由于某种原因结束了执行。这可以通过以下几种方式实现:

main函数返回:这是最常见的终止方式。程序执行完毕后,main函数返回一个整数值给操作系统。返回值0通常表示程序成功执行,没有错误。而返回非零值(如1)则表示程序遇到了某种错误或问题。例如:

int main() {
    // 程序代码
    return 0; // 正常退出
    return 1; // 出现错误
}

调用exit函数exit函数可以在程序的任何地方被调用,用来立即终止程序,并将退出码返回给操作系统。这对于在深层嵌套函数中终止程序非常有用。

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

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

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

调用_exit_Exit函数:这两个函数与exit类似,但是它们不会调用任何注册的退出处理函数(如由atexit注册的函数),也不会刷新标准I/O缓冲区。它们通常用在需要立即退出程序而不进行任何清理操作的情况下。

#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
  • 说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

异常退出

异常退出通常是由于程序运行时遇到了错误或者被外部信号强制终止。

信号终止:程序可以通过接收到特定的信号而被异常终止。

  • 例如,当你在终端中按Ctrl + C时,会向前台进程组发送SIGINT信号,这通常会导致程序异常终止。

处理这类信号通常需要在程序中显式地捕获和处理这些信号,以决定是否终止程序或执行某些特定的清理操作。

4、三种进程终止区别

库函数(如exit()

库函数是编程语言提供的,用于执行特定任务的函数,它们是应用层的接口。

  • 例如,exit()函数是C标准库的一部分用于结束当前进程并进行适当的清理操作,比如关闭文件描述符、清空标准输入输出的缓冲区等。
  • 库函数为开发者提供了一个高级的抽象,隐藏了底层操作系统的复杂性,使得开发者可以更容易地编写跨平台的应用程序。

系统接口(如_exit()

系统接口,或称为系统调用,是操作系统提供给应用程序的接口。

  • _exit()函数是一个系统调用,直接由操作系统提供,用于立即结束进程,不执行标准库的清理和缓冲区刷新操作。
  • 系统接口让应用程序能够请求操作系统的服务,如文件操作、进程控制等。

缓冲区

谈到printf函数,所提到的“缓冲区”指的是一个临时存储区,用于暂存输出数据。这个缓冲区是由C标准库维护的,而不是操作系统。

  • C库提供的输入输出函数(如printfscanf)通常使用这些缓冲区来提高数据处理的效率,通过减少对操作系统调用的次数来达到优化性能的目的。
  • 当调用exit()函数时,C标准库会负责刷新输出缓冲区,确保所有暂存的输出数据都被正确地写入其目标介质,如终端或文件。相反,如果直接调用_exit(),这种刷新操作不会发生,因为_exit()绕过了标准库,直接请求操作系统结束进程。

return 与进程退出

  • 在C程序中,从main函数执行return操作是结束进程的一种常见方法。
  • 实际上,执行return n;main函数中的效果等同于调用exit(n);,因为C程序的启动代码(运行时环境)会捕捉到main函数的return返回值,并将其作为参数传给exit函数。这样,即便是通过return退出,也能保证执行标准库的清理工作,如刷新缓冲区和关闭文件。

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

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

相关文章

redis 入门01

1.安装与配置 在官网下压缩包并传送给自己的虚拟机或者使用wget直接下载都可以 注意:redis是运行在linux下的基于内存的kv键值对数据库 安装与配置参考 2.经典Hello World 注意设置redis在后台运行,默认是前台进行的 我们配置完成之后首先启动服务器 redis-server 配置文件 这里…

MyBatis3源码深度解析(十二)MyBatis的核心组件(一)Configuration

文章目录 第四章 MyBatis的核心组件4.1 使用MyBatis操作数据库4.2 MyBatis核心组件4.3 Configuration组件4.3.1 属性4.3.2 设置4.3.3 类型别名4.3.3 类型处理器4.3.5 对象工厂4.3.6 插件4.3.7 配置环境4.3.8 映射器 第四章 MyBatis的核心组件 4.1 使用MyBatis操作数据库 在研…

Boyer Moore 算法介绍

1. Boyer Moore 算法介绍 Boyer Moore 算法&#xff1a;简称为 BM 算法&#xff0c;是由它的两位发明者 Robert S. Boyer 和 J Strother Moore 的名字来命名的。BM 算法是他们在 1977 年提出的高效字符串搜索算法。在实际应用中&#xff0c;比 KMP 算法要快 3~5 倍。 BM 算法思…

【Machine Learning】Suitable Learning Rate in Machine Learning

一、The cases of different learning rates: In the gradient descent algorithm model: is the learning rate of the demand, how to determine the learning rate, and what impact does it have if it is too large or too small? We will analyze it through the follow…

【安全类书籍-1】asp代码审计.pdf

目录 内容简介 作用 下载地址 内容简介 这个文档摘录片段主要讨论了ASP编程中的安全性审计,包括SQL注入漏洞、Cookie注入防范措施及文件上传安全问题,并给出了相关示例代码。 SQL注入漏洞与防范 - ASP代码中展示了如何通过`Request.QueryString`和`Request.Form`获取用户…

SpringBoot打造企业级进销存储系统 第五讲

package com.java1234.repository;import com.java1234.entity.Menu; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query;import java.util.List;/*** 菜单Repository接口*/ public interface MenuReposit…

spacy进行简单的自然语言处理的学习

自然语言处理基本概念 概念&#xff1a;自然语言处理&#xff0c;是让机器理解人的语言的过程。 作用&#xff1a;通过使用自然语言处理&#xff0c;机器可以理解人的语言&#xff0c;从而进行语义分析&#xff0c;例如&#xff1a;从一句话中判断喜怒哀乐&#xff1b;从一段文…

电大搜题:开启学习新时代

身处信息化时代&#xff0c;学习的方式已经发生了巨大的变革。在这个多元化的学习环境中&#xff0c;传统的学习模式已经无法满足现代学习者的需求。然而&#xff0c;电大搜题应运而生&#xff0c;为学习者提供了一个高效、便捷的学习途径。 电大搜题&#xff0c;作为黑龙江开…

阅读 - 二维码扫码登录原理

在日常生活中&#xff0c;二维码出现在很多场景&#xff0c;比如超市支付、系统登录、应用下载等等。了解二维码的原理&#xff0c;可以为技术人员在技术选型时提供新的思路。对于非技术人员呢&#xff0c;除了解惑&#xff0c;还可以引导他更好地辨别生活中遇到的各种二维码&a…

铁路订票平台小程序|基于微信小程序的铁路订票平台小程序设计与实现(源码+数据库+文档)

铁路订票平台小程序目录 目录 基于微信小程序的铁路订票平台小程序设计与实现 一、前言 二、系统设计 三、系统功能设计 1、用户信息管理 2、车次信息管理 3、公告信息管理 4、论坛信息管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐…

Transformer学习笔记(二)

一、文本嵌入层Embedding 1、作用&#xff1a; 无论是源文本嵌入还是目标文本嵌入&#xff0c;都是为了将文本中词汇的数字表示转变为向量表示&#xff0c;希望在这样的高维空间捕捉词汇间的关系。 二、位置编码器Positional Encoding 1、作用&#xff1a; 因为在Transformer…

冲动是魔鬼,工作不顺心时不要把坏脾气带给家人

今天与一个跟踪了很久的客户准备签合同了&#xff0c;客户突然反悔&#xff0c;为此与他周旋了一整天&#xff0c;忙碌得一口水都没有喝。回到小区坐在车里抽着烟&#xff0c;久久不愿回家&#xff0c;只想一个人坐着&#xff0c;疲惫、无奈。这个月的奖金似乎又将成为泡影。 …

Microsoft SQL Server2019占用大量磁盘空间的解决办法(占了我C盘120G的空间!!!)附SQL数据库定时清理代理作业

一、问题 安装Microsoft SQL Server2019后我的C盘在几天后少了100G&#xff0c;如图所示&#xff1a; 解决后&#xff1a; 出现这种情况&#xff0c;我在各种清理C盘后&#xff0c;空间还是没有太大变化 &#xff0c;且几乎每天都要少2个G&#xff0c;后来终于找见原因了&…

Postman接口测试:API 测试的必备技巧

在现代软件开发生命周期中&#xff0c;接口测试是一个至关重要的部分。使用 Postman 这一工具&#xff0c;可以轻松地进行 接口测试。以下是一份简单的使用教程&#xff0c;帮助你快速上手。 安装 Postman 首先&#xff0c;你需要在电脑上安装 Postman。你可以从官网上下载并…

虚拟机NAT模式配置

注意这里IP要和网关在同一网段&#xff0c;且虚拟机默认网关末尾为.2&#xff08;如果默认网关配置为.1会与宿主机冲突&#xff0c;导致无法ping通外网&#xff09; 点击NAT模式下的NAT设置即可查看默认网关 这里的网关可以理解为主机与虚拟机交互的入口

CSDN首发Chainlink(预言机)讲解:基础知识总结 到底什么是预言机本篇带你解析

苏泽 大家好 这里是苏泽 一个钟爱区块链技术的后端开发者 本篇专栏 ←持续记录本人自学两年走过无数弯路的智能合约学习笔记和经验总结 如果喜欢拜托三连支持~ 前面的专栏带大家熟悉了 区块链的基本组成 、共识机制、智能合约、最小信任机制 以及EVM等知识 如遇不懂的概念或名…

2024年【危险化学品经营单位主要负责人】新版试题及危险化学品经营单位主要负责人复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【危险化学品经营单位主要负责人】新版试题及危险化学品经营单位主要负责人复审考试&#xff0c;包含危险化学品经营单位主要负责人新版试题答案和解析及危险化学品经营单位主要负责人复审考试练习。安全生产模…

Kubernetes operator系列:webhook 知识学习

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 Kubernetes operator学习 系列文章&#xff0c;本节会对 kubernetes webhook 知识进行学习 本文的所有代码&#xff0c;都存储于github代码库&#xff1a;https://github.com/graham924/share-code-operator-st…

说下你对TCP以及TCP三次握手四次挥手的理解?

参考自简单理解TCP三次握手四次挥手 什么是TCP协议&#xff1f; TCP( Transmission control protocol )即传输控制协议&#xff0c;是一种面向连接、可靠的数据传输协议&#xff0c;它是为了在不可靠的互联网上提供可靠的端到端字节流而专门设计的一个传输协议。 面向连接&a…

【Python】进阶学习:基于Numpy实现按指定维度拼接两个数组

【Python】进阶学习&#xff1a;基于Numpy实现按指定维度拼接两个数组 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希…