【Liunx】进程地址空间

news2025/1/17 23:25:37

文章目录

  • 📖 前言
  • 1. 环境变量收尾
    • 1.1 认识bash进程:
    • 1.2 环境变量具有全局属性:
    • 1.3 内建命令:
  • 2. 进程地址空间
    • 2.1 Liunx — 地址空间验证:
    • 2.2 感知地址空间的存在:
    • 2.3 认识地址空间:
      • 2.3 - 1:究竟什么是进程地址空间
      • 2.3 - 2:程序如何变成进程的
    • 2.4 写实拷贝:
    • 2.5 fork()函数遗留问题:
    • 2.6 为什么要有虚拟地址空间(三大理由):
      • 保护内存:
      • Linux内存管理:
      • 让进程统一视角看内存:
  • 3. 虚拟地址空间和进程地址空间一样吗

📖 前言

上节我们讲完了环境变量,本章我们来给环境变量收个尾,讲解一下进程优先级🙋🙋🙋……

本文实验系统:CentOS 7.6~


1. 环境变量收尾

1.1 认识bash进程:

在我们之前将进程状态的时候讲过,当一个进程将其杀死再重启时,进程的id是在变化的,但是它们的父进程的id是一直不变的。

前景复习~

一旦通过指令kill -9bash进程给干掉之后,整个命令行就挂掉了。

在这里插入图片描述
命令行中启动的进程,父进程全部都是BASH。

  • bash是个进程,它是如何启动一个进程的呢?它底层就是用的fork创建子进程的。

正常使用命令行是因为这些命令本身是先被bash进程先获得了,bash也是进程也有自己的代码在/usr/bin路径底下。

在这里插入图片描述

  • 当系统登录的时候,用shell等登录的时候,系统就会给用户创建bash进程,该进程是用C/C++写的。
  • bash内的代码cin scanf可以获取输入的命令行。

1.2 环境变量具有全局属性:

本地变量和全局变量,在上一节我们已经讲过了,复习传送~

  • 所谓得本地变量,本质就是在bash内部定义的变量。不会被子进程继承下去!
  • 不带export就是本地变量
  • 一旦带上export就会被子进程继承下去

在这里插入图片描述

我们来看一下本地变量和环境变量的区别:

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

int main()
{
    while(1)
    {
        printf("Hello World, pid: %d, ppid: %d, myenv=%s\n", getpid(), getppid(), getenv("hehe"));
        sleep(1);
    }

    return 0;
}

在这里插入图片描述
将本地变量导成环境变量:
在这里插入图片描述
环境变量是会被子进程继承下去的!!

环境变量被其后的所有子进程都能看得到。

1.3 内建命令:

在这里插入图片描述
命令行中启动的所有程序,基本上都是要创建子进程,echo也是一条命令也是子进程。

那么问题来了:

  • bash内定义的local_val变量怎么可能被于进程读到呢?
  • export叫导出环境变量,export也是子进程,那么它导出的环境变量只能在子进程的上下文当中,怎么能在bash上下文呢?(因为要先创建子进程)

Linux下大部分命令都是通过子进程的方式执行的!但是,还有一部分命令,不通过子进程的方式执行,而是由bash自己执行(调用自己的对应的函数来完成特定的功能),我们把这种命令叫做【内建命令】!!

信任度非常高的命令,bash不会让子进程去帮它执行,而是自己去执行。

例如cd指令也是如此,如果这里不理解我们在以后的手写bash中再来理解感受一遍(其实我也不太理解😅😅😅)


2. 进程地址空间

  • 首先先要明确,我们之前学的程序地址空间是内存吗?
  • 答案是否定的,不是内存。
  • 程序地址空间,不是内存!
  • 它叫程序地址空间都不准确,应该叫进程地址空间。
  • 进程地址空间根本就不是C/C++的概念,而是操作系统的概念。

在这里插入图片描述

  • 一般在4G空间当中,0到3G空间是给用户的,还有1G空间给操作系统的。
  • 低地址全0,高地址全F

2.1 Liunx — 地址空间验证:

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

int un_g_val;
int g_val = 100;

int main(int argc, char* argv[], char* env[])
{
    printf("code addr             : %p\n", main);
    printf("init global val addr  : %p\n", &g_val);
    printf("uninit global addr    : %p\n", &un_g_val);

    char* m1 = (char*)malloc(sizeof(char) * 100);
    char* m2 = (char*)malloc(sizeof(char) * 100);
    char* m3 = (char*)malloc(sizeof(char) * 100);
    char* m4 = (char*)malloc(sizeof(char) * 100);

    static int s = 100;

    printf("heap addr             : %p\n", m1);
    printf("heap addr             : %p\n", m2);
    printf("heap addr             : %p\n", m3);
    printf("heap addr             : %p\n", m4);

    printf("stack addr            : %p\n", &m1);
    printf("stack addr            : %p\n", &m2);
    printf("stack addr            : %p\n", &m3);
    printf("stack addr            : %p\n", &m4);

    printf("s stack addr          : %p\n", &s);

    int i = 0;
    for(i = 0; i < argc; i++)
    {
        printf("argv addr             : %p\n", argv[i]);
    }

    for(i = 0; env[i]; i++)
    {
        printf("env addr              : %p\n", env[i]);
    }

    return 0;
}

运行结果:

在这里插入图片描述
我们发现堆和栈之间有非常大的地址镂空,我们来看看chatgpt是怎么回答这个问题的吧:

  • Linux中的堆和栈是两个不同的内存区域,它们的地址空间是分开的。堆是动态分配的内存区域,由程序员手动申请和释放,而栈是由系统自动分配和释放的内存区域,用于存储函数调用时的局部变量和函数调用的返回地址等信息。
  • 由于堆和栈的使用方式不同,它们的地址空间也需要分开,以避免相互干扰。在Linux中,堆和栈之间留有一定的地址空间,这个地址空间被称为“地址空洞”或“地址镂空”。这个地址空洞的大小取决于操作系统的实现和硬件架构,通常是几百MB到几GB不等。
  • 这个地址空洞的存在是为了保证堆和栈之间的内存不会互相覆盖,从而保证程序的正常运行。如果堆和栈之间没有地址空洞,那么当堆和栈的内存使用量增加时,它们就会相互覆盖,导致程序崩溃或出现不可预测的错误。因此,为了保证程序的稳定性和安全性,Linux中的堆和栈之间必须留有一定的地址空洞。

静态变量:

  • 生命周期一直存在是因为他本来就是全局变量。
  • 一个被写在函数内的全局变量。

函数内定义的变量用static修饰,本质是编译器会把该变量编译进全局数据区!

在这里插入图片描述

  • 堆区空间是从低地址向高地址使用的
  • 栈区空间是从高地址向低地址使用的
  • 堆,栈相对而生

我们一般在C函数中定义的变量,通常在栈上保存,那么先定义的一定是地址比较高的!

2.2 感知地址空间的存在:

当父子进程没有人修改全局数据的时候,父子是共享该数据的!

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

int g_val = 100;

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        int flag = 0;
        while(1)
        {
            printf("我是子进程:%d, ppid: %d, g_val: %d, &g_val: %p\n\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
            flag++;
            if(flag == 5)
            {
                g_val = 200;
                printf("我是子进程,全局数据我已经改了,用户你注意查看!\n");
            }
        }
    }
    else 
    {
        //parent
        while(1)
        {
            printf("我是父进程:%d, ppid: %d, g_val: %d, &g_val: %p\n\n", getpid(), getppid(), g_val, &g_val);
            sleep(2);
        }
    }
}

在运行上述代码时,我们会遇到一个非常奇怪的现象:

在这里插入图片描述

  • 父子进程读取同一个变量(因为地址一样),但是后续没有人修改的情况下,父子进程读取到的内容却不一样!!
  • 这就说明了,我们在C/C++中使用的地址,绝对不是物理地址!
  • 如果是物理地址,这种现象不可能产生!
  • 那是不是物理地址是什么呢??
  • 虚拟地址
  • 线性地址
  • 逻辑地址

三个概念完全不一样,但是在Liunx下是一样的。

  • 为什么我的操作系统不让我直接看到物理内存呢??
  • 内存就是一个硬件,不能阻拦你访问!
  • 只能被动的进行读取和写入!

操作系统在程序和内存之前加了一层软件层,它来帮我做控制。

2.3 认识地址空间:

每一个进程在启动的时候,都会让操作系统给他创建一个地址空间,该地址空间就是进程地址空间。

  • 每一个进程都会有一个自己的进程地址空间!!
  • 操作系统要不要管理这些进程地址空间呢??
  • 既然是管理,那么就是:先描述,在组织
  • 进程地址空间,其实是内核的一个数据结构,struct mm_struct

进程和内存中间构建一层软件层,叫做地址空间。

在这里插入图片描述

2.3 - 1:究竟什么是进程地址空间

我们之前讲过进程的独立性:

  • 独立性多进程运行,需要独享各种资源,多进程运行期间互不干扰。
  • 进程相关的数据结构是独立的,进程的代码和数据是独立的。

进程地址空间不是物理上存在的概念而是,逻辑上抽象的概念。

进程地址空间,就是操作系统给进程画的大饼,为了更好的维护进程彼此之间的独立性(每个进程都认为内存是从全0到全F的)。

  • 地址空间存在的最大意义:
  • 让每一个进程都认为自己是独占系统中的所有资源的!!

每次要都是要一点,不是要完的,所以一直都这么认为,操作系统骗了进程。

  • 为什么操作系统让每一个进程都认为自己是独占系统中的所有资源?我们来看看chatgpt的回答。
  • 操作系统让每一个进程都认为自己是独占系统中的所有资源的,是因为这种抽象方式可以简化程序员的编程工作,同时也可以提高系统的安全性和稳定性。
  • 如果每个进程都知道自己只是系统中的一个小部分,那么程序员需要考虑如何与其他进程共享资源,如何避免资源竞争等问题。这将使编程变得更加复杂,容易出错。
  • 另一方面,如果每个进程都认为自己是独占系统中的所有资源的,那么操作系统可以更好地控制和管理资源的分配和使用。操作系统可以使用各种技术,如时间片轮转、优先级调度等,来确保每个进程都能够获得足够的资源,并且不会对其他进程造成影响。
  • 总之,操作系统让每一个进程都认为自己是独占系统中的所有资源的,是为了简化编程工作,提高系统的安全性和稳定性。

所谓的地址空间:其实就是OS通过软件的方式,给进程提供一个软件视角,认为自己会独占系统的所有资源(内存)。

每个进程都会创建一个task struct,每个进程都会维护一个mm_struct
在这里插入图片描述

页表:将程序加载到内存,由程序变成进程之后,由操作系统会给每一个进程构建一个页表结构(程序加载到内存时自动构建的,构建虚拟地址到物理地址的映射关系)。

我们看到的各种地址,是在虚拟地址空间当中给我们分配好的地址,这样的地址经过虚拟地址,然后转化成物理地址到达物理内存。

区域:既然我们之前学习了内存中各种区域,区域是如何划分的呢?

  • 是通过两个指针,start和end指针来维护一块区域
  • 每块区域用之间用链表联系到一起,如上图所示

2.3 - 2:程序如何变成进程的

  • 程序被编译出来,没有被加载的时候,程序内部,有地址吗??有的!!
  • 程序被编译出来,没有被加载的时候,程序内部,有没有区域??有的!!

在这里插入图片描述
虚拟地址:

  • 把代码区的所有地址都加一个偏移量
  • 此时就已经把可执行程序的部分地址加载到内存里了
  • 在内存里就形成了全新的地址叫做虚拟地址

相对地址(相对于程序的起始地址),又叫做:逻辑地址。

详解第一阶段:

  • 可执行程序在编译好之后,它本身就有了自己对应的一套地址,可执行程序内部是有区域的,所有的区域最终都在磁盘中已经划分好了。
  • 所以所谓加载到内存,无非就是按照区域,将其加载到物理内存当中。
  • 加载之后,在内存里,可以把所有的可执行程序内部的代码、数据等,所对应的地址全部转化成,我认为我是从0地址开始占内存的。

详解第二阶段:

  • 加载完之后,程序到内存里了,操作系统会给每一个对应的进程创建一个PCB。
  • PCB指向地址空间,地址空间再构建所对应的页表,再经过页表映射到物理内存。程序就开始读取对应的代码当中的数据了。
  • 此时代码的地址已经被转化成虚拟地址,所以CPU读到的都是虚拟地址。
  • 再进行寻址的时候,自动的会做页表转化,一定会找到它对应的物理地址的。

详解第三阶段:

  • 加载到内存的时候我们就可以认为它的起始偏移量是从0开始。
  • 至于加载到内存当中的什么位置,由页表映射,但是程序内部的地址全部已经是以地址空间的方式排好的,可以在磁盘的任意位置去加,因为页表可以随便去映射,但是程序内部所有代码当中的函数跳转、变量的地址, 全部已经以线性地址或虚拟地址的方式呈现好。
  • 当CPU通过页表找到对应的代码之后,读取到的指令里面包含的地址就是虛拟地址,然后经过页表再转化,找到在内存当中对应的地址,进而找到代码继续执行。

详解第四阶段(总结):

  • 以下内容了解,只需要记住,每个进程会创建一个task_struct,每个进程都会维护一个mm_ struct有自己对应的区域。
  • 当程序加载到内存时,程序有加载到物理内存的物理地址,将虚拟地址和物理地址建立映射关系,最终进程访问的是某个区域当中的地址的时候,直接经过页表映射到物理内存,找到对应的代码,当找到对应的代码和数据时,要将其加载到CPU里面。
  • 加载到CPU里面,这个代码里面也有地址,该地址早已经在加载的时候被转换成了线性地址或虚拟地址,所以CPU可以继续照着这个逻辑继续向后运行。
  • 虚拟地址空间是个体系工程,不仅仅在操作系统层面上替我们考虑,它也在编译阶段替我们考虑好了。
  • 编译程序的时候,就认为程序是按照0000~FFFF进行编址的。
  • 虚拟地址空间,不仅仅是操作系统会考虑,编译器也会考虑。

举个例子:

  • 程序的内部找printf的地址是用相对地址的方案,而加载到内存,整个代码在物理内存上有地址
    而这个地址需要在页表的右侧维护起来,能够找到就可以。
  • 实际CPU读到的地址,在加载的时候就转化成了虚拟地址或逻辑地址或线性地址。
  • 然后读到的就全都是虚拟地址,所以此时才能够再进行页表转化找到物理内存。

2.4 写实拷贝:

有了上述知识,我们这里来解决之前的问题:为什么相同的地址在没修改子进程之前是一样的值,在修改过子进程之后却能相同的地址打印出两个不同的值??

  • 此时就能体现出来,父子进程fork之后,代码是共享的。
  • 因为他们的虚拟地址到物理地址映射的页表其实是一样的。
  • 所以指向的代码和数据都是一样的。
  • 所以在没人写入的时候,这两个打印的虚拟地址和内容都一样。

为什么在子进程写入值之后,相同的地址打印出两个不同的值:

在这里插入图片描述

  • 进程也有自己的代码,父进程和子进程指向的也是同一块代码。
  • 创建子进程的时候,子进程的相关数据结构内容初始化以父进程为模版。
  • 子进程的页表大部分情况下也是以父进程为模板的。
  • 这个物理地址依旧映射到的是上面的物理地址。

这是为什么没写入值之前是一样的值的原因。

重点:因为进程具有独立性!!要做到互不影响!

  • 所以操作系统是不允许一个进程将另一个进程的数据给修改了!

因为进程具有独立性,如果子进程把变量改了,就可能导致父进程识别这个变量有问题,子进程影响了父进程。

  • 写时拷贝引入:
  • 当识别到子进程修改了,操作系统会重新给子进程开辟一块空间
  • 并且把刚刚的值(100)拷贝下来,并且重新对这个进程建立映射关系。
  • 所以子进程的页表就不再指向父进程对应的变量(100),而子进程指向的就是新的空间了
  • 所以我们在改的时候改的永远是页表的右侧,也就是映射关系,而左侧不变
  • 所以最终看到虚拟地址一样,但是经过页表映射已经被映射到不同的物理内存
  • 所以读到了两个不同的值,虚拟地址却是一样的。

总结:

  • 没修改时用的是用一物理地址,一旦父子有任何一个进程尝试修改对应变量的时候,此时就发生了写时拷贝。
  • 只是从新构建页表的映射关系,虚拟地址是不发生任何变化的。
  • 当父子进程对数据做修改的时候,操作系统会给修改的一方重新开辟空间,并且把原始数据拷贝到新空间当中,这种行为叫做写时拷贝。

通过页表,将父子进程的数据就可以通过写时拷贝的方式,进行了分离!!!

每个进程的地址空间页表是互相独立的,每个进程的数据也是独立的,代码因为不可被修改,即便是共享也不影响,这样就做到了父子进程具有独立性!!!

补充:

代码也是共享的,父好进程一般不会对代码进行写入。万一有一天代码发生变化,也要写实拷贝的。

所以之前说 “ 程序的地址空间 ” 是不准确的,准确的应该说成 “进程地址空间 ” ,那该如何理解呢?看图:
在这里插入图片描述

2.5 fork()函数遗留问题:

  • fork有两个返回值,pid_t id,同一个变量,怎么会有不同的值??
  • pid_t id是属于父进程栈空间中定义的变量,fork内部,return会被执行两次。
  • return的本质,就是通过寄存器将返回值写入到接受返回值的变量中!!
  • id = fork()的时候,谁先返回,谁就要发生写时拷贝,所以,同一个变量,会有不同的内容值,本质是因为大家的虚拟地址是一样的,但是大家对应的物理地址是不一样的!!

补充:

  • 这里所说的 “ 谁先返回,谁就要发生写实拷贝 ” ,(一开始我也很疑惑,后来询问了老师)这里解释一下:
    • 因为这个变量的值一开始和先返回的值不同,所以第一次返回的时候就要改值,这时就发生了写时拷贝。
    • 比如一开始id-1,调用fork后,第一个返回的不管父子进程都要去改这个id-1现在两个进程使用,是不是就发生写时拷贝了。
    • 抽象说:一开始只有一个,谁修改谁新创建一个,剩下一个用最开始的。
    • 详细说:一开始父程修改-1这块空间,发生了写时拷贝,新开辟了一块空间(父进程用),然后子进程用的是-1那块空间。
    • 内核就是这样去实现的,已经到我知识边界了,硬记吧~

2.6 为什么要有虚拟地址空间(三大理由):

保护内存:

如果不存在虚拟地址空间的话(非常不安全):

  • 那就可以随便访问物理内存了
  • 一旦出现野指针的行为,我们写的代码又bug,野指针越界了
  • 就有可能将别的进程内容给改了
  • 还有可能直接将操作系统内核改了

在这里插入图片描述
虚拟地址保护内存:

  • 因为页表的存在,如果是野指针没有映射关系就访问不到物理内存。
  • 页表转化失败,进程想访问内存也就不可访问,操作系统就会直接将进程杀掉。
  • 此时物理内存没有被进行任何写入操作,因为有虚拟地址空间的存在。

坊问内存添加了一层软硬件层,可以对转化过程进行审核,非法的访问,就可以直接拦截了。

Linux内存管理:

  • 一个进程在申请内存的时候,本质只需要在地址空间中将空间开辟好。
  • 后半部分内存申请可以暂时不申请。
  • 当真正用到这块内存的时候再在内存上对内存进行申请。
  • 这样做的好处是,需要时立马就可以访问,不会出现占着茅坑不拉屎的现象(即便是现在申请,但是不用,内存中的一些空间并没有被拿走,此时这块空间可用作他用)。
  • 无形当中提高了Linux操作系统运行的效率。

Linux只要有虚拟地址空间的存在,它可以把对进程的调度,执行代码和Linux内存管理通过页表就彻进行了解耦!!

补充:

  • 调度进程,运行进程,正常执行进程代码都叫做操作系统中的进程管理。
  • 进程当中代码申请空间时,操作系统只是将地址空间扩大了。
  • 当你真正需要时,系统才会做内存管理的申请,填充页表。
  • 进程管理和内存管理,这两者就通过地址空间,进行功能模块的解耦!!

什么是没有解耦?

如果一个进程想malloc一段空间,不解耦就必须立马调用由进程调度模块,必须调度malloc底层代码,在物理内存上真正的把物理内存malloc出来。可以正在调度调度进程,突然跑过去就要执行内存管理的代码,这就叫做这两者没有解耦。

什么是解耦?

现在是有了地址空间,需要malloc内存,直接在地址空间上在堆区将end指针扩大一点,这个区域就相当于允许访问了,当不访问时不分配内存,当访问时再触发内存管理模块,来访问。内存管理压根就不关心是什么原因要申请空间的,只关心要空间要多大,然后申请好,再建立好映射关系,此时维护好,上层再进行访问就可以了。这叫做将操作系统中的模块通过地址空间进行解耦。

让进程统一视角看内存:

让进程或者程序可以以统一视角看待内存:

  • 如果没有虚拟地址空间, 如果要将可执行程序加载到内存里,代码内部也有代码和数据,每一个都有地址。
  • 加载到内存时就要想办法将这些地址变成物理地址,每次加载的时候这个程序的地址都要发生变化,因为程序会被加载到不同物理内存处。
  • 如果以统一的视角的话,将来进程加载到物理内存的任何位置都不怕,因为看到的虚拟地址一点都不变。
  • 但是可以被加载到物理内存的任意地址,因为可以通过虚拟地址映射到物理内存,改的永远是页表右侧(物理地址),而页表左侧(虚拟地址)没有任何变化。
  • 例如代码区永远不变(永远在那个区域),如果将可执行程序加载到物理内存的一个区域,当以后加载到物理内存的其他区域时候,代码区就变了,这就让CPU调度和操作系统管理都会增加很大成本。

方便以统一的方式,来编译和加载所有的可执行程序。


3. 虚拟地址空间和进程地址空间一样吗

cahtgpt的回答:

  1. 虚拟地址空间和进程地址空间不完全相同,但它们之间有密切的关系。

  2. 进程地址空间是指进程在运行时所使用的地址空间,包括代码段、数据段、堆栈等。而虚拟地址空间是指操作系统为每个进程分配的一段虚拟地址范围,它是由操作系统管理的,不同的进程拥有不同的虚拟地址空间。

  3. 虚拟地址空间和进程地址空间之间的关系是,进程地址空间中的地址是虚拟地址,它需要通过地址映射机制转换成物理地址才能被处理器访问。操作系统负责管理虚拟地址空间和物理地址空间之间的映射关系,以及进程之间的地址隔离,保证每个进程只能访问自己的虚拟地址空间,从而保证系统的安全和稳定性。

本章很不好理解,需要时间的沉淀~

至此进程概念到此全部结束,第一座大山已经翻过去,可以歇口气了,进程控制见~

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

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

相关文章

C++右值引用(左值表达式、右值表达式)(移动语义、完美转发(右值引用+std::forward))(有问题悬而未决)

文章目录 什么是右值&#xff1f;是什么是右值引用&#xff1f;什么是移动语义&#xff1f;什么是完美转发&#xff1f;&#xff08;右值引用std::forward&#xff09; 什么是右值&#xff1f; 在 C 中&#xff0c;表达式可以分为左值表达式和右值表达式。左值表达式指的是可以…

《常规脉搏传输时间作为人体血压变化标志》阅读笔记

目录 一、论文摘要 二、论文十问 Q1: 论文试图解决什么问题&#xff1f; Q2: 这是否是一个新的问题&#xff1f; Q3: 这篇文章要验证一个什么科学假设&#xff1f; Q4: 有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课题在领域内值得关注的研究员&#xff1f; …

OpenCV教程——加载、修改、保存图像

1.颜色空间 颜色空间&#xff1a;也称彩色模型&#xff08;又称彩色空间或彩色系统&#xff09;。本质上&#xff0c;彩色模型是坐标系统和子空间的阐述。位于系统的每种颜色都有单个点表示。RGB&#xff08;红绿蓝&#xff09;是依据人眼识别的颜色定义出的空间&#xff0c;可…

「二线豪华」或成历史,理想反超沃尔沃再树「里程碑」

今年的上海车展&#xff0c;除了占据C位的新能源汽车&#xff0c;还有传统车企。 上海车展开幕前&#xff0c;沃尔沃汽车大中华区销售公司总裁钦培吉在新车发布会上直言&#xff1a;“新势力会的&#xff0c;我们三年就学会了&#xff1b;我们会的&#xff0c;新势力十年都学不…

SQL事务与存储引擎

索引回顾&#xff1a; 索引是一个排序的列表&#xff0c;包含字段的值和值所在行数据的物理地址 事务是一个机制&#xff0c;一个操作序列&#xff08;一组操作命令&#xff09;&#xff0c;事务会把所有命令当做一个整体向系统提交或撤销操作&#xff0c;要么都执行&#xf…

MySQL中的Join 的算法(NLJ、BNL、BKA)

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 文章目录 摘要什么是JoinIndex Nested-Loop JoinBlock Nested-Loop JoinMRR & BKA总结 摘要 Join是MySQL中最常见的查询操作之一&#xff0c;用于从多个表中获取数据并将它们组合在一起。Join算法通常使…

【概念大全(关系,码,选择,投影,连接,运算)】第二章 关系数据库

第二章 关系数据库 1. 关系的基本概念1. 什么是域2. 笛卡尔积3. 笛卡尔积中 有意义的子集 就是关系4. 候选码 &#xff08;是唯一标识符 并不是用 只有一个进行判断&#xff09;5. 全码&#xff08;一行中都不重复&#xff09;6. 主码&#xff08;候选码选一个就是主码&#xf…

手术麻醉临床信息系统源码,实时自动采集麻醉和监护设备的数据

手术麻醉临床信息系统源码 手术麻醉临床信息系统实时采集麻醉和监护设备的数据&#xff0c;实现术前、术中、术后全手术过程的数字化管理&#xff0c;为手术室提供全数字化的业务管理、临床管理、费用管理、材料管理等。同时通过与 HIS、EMR、PACS、LIS 等系统无缝集成&#x…

Linux中信号的基础知识

信号的概念 Linux操作系统中&#xff0c;信号是一种进程间通信&#xff08;Inter-Process Communication, IPC&#xff09;机制&#xff0c;用于向其他进程发送通知或指示&#xff0c;通常是为了通知特定事件的发生&#xff0c;如程序终止、用户按下特定按键等。信号提供了一种…

java获取输入内容的方法

Java中的对象类型可以有多种&#xff0c;比如 Object、 StringBuilder等&#xff0c;其中 Object和 String是最常用的对象类型&#xff0c;而 StringBuilder类是一种特殊的类&#xff0c;它能通过继承来创建其他的对象。 我们在平时的工作中经常会遇到需要获取输入内容的情况&a…

界面控件DevExpress Blazor UI v22.2亮点:全新的Window组件

DevExpress拥有.NET开发需要的所有平台控件&#xff0c;包含600多个UI控件、报表平台、DevExpress Dashboard eXpressApp 框架、适用于 Visual Studio的CodeRush等一系列辅助工具&#xff0c;该组件拥有众多新产品和数十个具有高影响力的功能&#xff0c;可为桌面、Web和移动应…

详解async 与 await,带您理解Playwright使用异步方法的正确姿势!

大家在使用python做playwright自动化测试的过程中&#xff0c;一定会发现下面这种异步用法 async def func():await apiawait api 很多同学可能只是按照这种写法来编写项目的自动化测试代码&#xff0c;对于具体细节可能并不了解&#xff0c;今天我就来讲一下playwright异步用…

基于fNIRS的脑功能连接分析:图论方法

导读 背景&#xff1a;fNIRS是一种利用近红外光谱进行功能神经成像的光学脑监测技术。它使用近红外光来测量大脑活动&#xff0c;并估计由于运动活动而引起的大脑皮层血流动力学活动。fNIRS通过光学吸收来测量含氧和脱氧血红蛋白中氧水平的变化。多源噪声和伪影干扰导致的信号…

【P6】JMeter HTTP Cookie管理器

文章目录 一、测试网站二、Cookie 设置规则2.1、无配置元件时&#xff0c;Cookie 不会自动设置&#xff08;与线程组设置无关&#xff09;2.2、有配置元件&#xff0c;不选任何参数时&#xff0c;Cookie 自动设置&#xff08;与线程组设置无关&#xff09;2.3、有配置元件&…

Java——二叉搜索树中第k小的元素

题目链接 leetcode在线oj题——二叉搜索树中第k小的元素 题目描述 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 个最小元素&#xff08;从 1 开始计数&#xff09;。 题目示例 示例1 输入&#xff1a;root [3,1…

软件工程本科生毕业论文中常见问题总结

文章目录 目录结构不合理 绪论&#xff08;引言&#xff09;研究内容 表格表格首行不要加粗表格能不跨页的就不要跨页 其他常见格式问题专有名词要用统一写法 首先先仔细阅读&#xff1a; 本科生毕业论文&#xff08;设计&#xff09;写作与排版打印规范 目录 结构不合理 2.…

Ubuntu 增加swap交换内存

一、创建虚拟内存 在实际开发中发现swap交换分区不够用了&#xff0c;于是需要创建虚拟内存来增加交换分区的大小。 在系统空闲空间位置创建swap虚拟内存专用文件夹 cd /data //切到你想要创建交换分区的目录 mkdir swap //新建文件夹swap cd swap //进入swap文件夹 备…

Fastjson<1.2.48远程代码执行漏洞(CNVD-2019-22238)

漏洞存在原因 在fastjson<1.2.24版本中&#xff0c;在解析json的过程中&#xff0c;支持使用autoType来实例化某一个具体的类&#xff0c;并调用该类的set/get方法来访问属性。而在1.24<fastjson<1.2.48版本中后增加了反序列化白名单&#xff0c;而在1.2.48以前的版本…

【容器化应用程序设计和开发】2.4 容器网络和存储

往期回顾&#xff1a; 第一章&#xff1a;【云原生概念和技术】 第二章&#xff1a;2.1 容器化基础知识和Docker容器 第二章&#xff1a;2.2 Dockerfile 的编写和最佳实践 第二章&#xff1a;2.3 容器编排和Kubernetes调度 2.4 容器网络和存储 容器网络和存储是容器化应用…

操作系统第二章——进程与线程(下)

东风夜放花千树&#xff0c;更吹落&#xff0c;星如雨 文章目录 2.3.1 进程同步&#xff0c;进程互斥知识总览什么是进程同步什么是进程互斥知识回顾 2.3.2 进程互斥的软件实现方法知识总览如果没有进程互斥单标志法双标志先检查法双标志后检查法Peterson算法知识回顾 2.3.3进程…