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

零基础Linux_21(多线程)页表详解+轻量级进程+pthread_create

目录

1. 页表详解

1.1 权限+条目+页框

1.2 页目录+页表项

2. 线程的概念

2.1 轻量级进程

2.2 Linux的线程

2.3 pthread_create

2.4 原生线程库+LWP和PID

3. 线程的公有资源和私有资源

3.1 线程的公有资源

3.2 线程的私有资源

4. 线程的优缺点

4.1 线程的优点

4.2 线程的缺点

本篇完。


1. 页表详解

我们在之前一直都提到页表,知道它的作用是将虚拟地址映射到物理地址,但是它具体怎么映射的,它的结构是什么样的,并没有提及过。

char* str = "hello world";
*str = 'H';

上诉代码,会在运行时报错,原因是str指向的地址在字符常量区,字符常量区的内容是不允许用户去修改的。

代码在运行起来以后,操作系统是怎么知道用户在修改字符常量区的呢?

1.1 权限+条目+页框

如上图所示的页表示意图,页表中不仅右虚拟地址和物理地址的映射关系,还有是否命中,RWX权限,U/K权限等等内容。

  • U/K权限:U表示用户(user),K表示内核(kernal)。
  • RWX权限:当前身份(用户或者内核)对当前地址的读,写执行权限。

上面代码在对srt指向的地址写内容时,先会经过页表映射到物理地址。但是在页表中发现这是一个写操作,并且该地址是不允许被写的,此时MMU就发送信号,导致程序报错。

虚拟地址,物理地址以及属性所在的一行,称为条目

依然是这张图,需要将这张图分解进行讲解。

物理内存空间划分:

以32位系统为例,它的物理内存理论上有4GB大小,但是这4GB又被分成了多块小空间。

被分割的小空间,每一块大小是4KB,被叫做页框

物理内存中会又很多个页框,并且这些页框也需要操作系统管理起来,采用的方式同样是先描述,再组织

通过一个结构体来描述页框

struct_Page
{//内存属性--4KB
}

代码形式如上所示,每一个页框都会有这样一个结构体对象,将多个结构体对象放在一个数组中:

struct_Page mem[];

此时就完成了先描述再组织的过程。操作系统中有专门的内存管理算法,叫做伙伴系统,有兴趣的可以自行查阅资料了解。

可执行文件:

写好的代码会经过编译器的处理形成二进制可执行文件放在磁盘中,在运行的时候加载到内存中。

编译器在处理源文件生成的二进制可执行文件,同样是以4KB为单位的,这4KB的数据块被叫做页桢

这一切都是设计好的,所以可执行程序在加载到内存中的时候是以4KB为单位的,正好一个页帧来填充一个页框。当页框被填充了以后,就会创建对应的struct_Page结构体对象,并且放在数组中,让操作系统来管理。

1.2 页目录+页表项

再回到页表,我们知道,每个进程对应的虚拟地址空间大小都是4GB的,也就是有2^32个地址,如果每个虚拟地址在页表中都对应着一个物理地址

那么页表就会有2^32行,每一行都是一个条目,每个条目中不仅有物理地址,虚拟地址,还有其他属性,假设一个条目的大小是10B,那么光页表就有10*2^32=40GB,已经超过了物理内存的大小,所以页表肯定不是这样的。

  • 实际上,页表是由页目录和页表项组成的。

在32位机器上,地址的大小是4G字节,也就是有32个比特位,

将32个比特位分为10个比特位,10个比特位,12个比特位,共3组:

 之前的的页表 = 页目录 + 页表项,如上图所示。

  • 32个比特位的高10位,作为页目录的下标,如上图所示的0000 0000,通过这个下标0可以访问到页目录中的第一个条目。
  • 页目录中存放的是页表项的地址,可以通过下标找到对应的页表项。

10个比特位,意味着页目录的下标范围是0~1023,最多是210也就是1KB个条目,大大减少了对内存的消耗。

  • 32个比特位的中间10位,作为页表项的下标,同样可以访问页表项中的条目。
  • 页表项中存放的是物理内存中页框的起始地址,可以通过下标找到物理内存中对应的页框。

同样,一个页表项最多有1KB个条目,指向1KB个页框。

  • 32个比特位中的低12位,作为偏移量,在物理内存中页框的起始地址基础上进行偏移,此时就可以得到具体数据在内存中的地址。
  • 这也是为什么页框和页帧的大小设置为4KB的原因,因为最低的12个比特位是2^12=4KB,偏移量最大就是4KB。

32位虚拟地址->物理地址的映射过程:

  1. 根据高10位下标找到页目录中对应页表项的地址
  2. 再根据中间10位下标找到页表相中对应页框的物理地址
  3. 再根据低12位偏移量进行偏移找到具体的物理地址。

页目录和页表项同样是采用先描述再组织的方式被操作系统管理起来的,每创建一个进程就会有一个页目录,只有在目录中存在的页表项才会被建立

采用这种方式,大大减少了对内存的消耗。

如何看到地址空间和页表:

  1. 地址空间是进程看到的资源窗口,每个进程都认为自己有4GB的资源。
  2. 页表决定进程真正拥有的资源。
  3. 合理对地址空间和页表进行资源划分,就可以对一个进程所有的资源进行分类。

2. 线程的概念

线程:是进程中的一个执行流。

Linux线程是CPU调度的基本单位。

回忆一下,之前我们对进程的定义是:内核数据结构 + 进程对应的代码和数据。

如上图所示,此时我们创建了多个“子进程”。

新创建的“子进程”中的mm_struct* mm都指向父进程的虚拟地址空间。也就是说,所有“子进程”和父进程共用一块虚拟地址空间。

父进程+和父进程共用一块虚拟地址空间的“子进程”,就叫做线程

此时开始,我们就将带引号的"子进程",叫做线程,因为以前的父子进程在这里都不这么叫了。

线程的作用:执行进程中的一部分代码。

线程在执行进程的部分代码时,有点像父子进程执行同一份代码中不同部分,区别在于线程和父进程使用的是同一份虚拟地址空间,而父子进程使用的是两个独立的虚拟地址空间。

2.1 轻量级进程

从图中可以看到,每个线程都也有一个task_struct结构体对象,用来描述线程的属性(id,状态,优先级,上下文,栈等等)。那么线程要不要被操作系系统管理起来呢?

答案是要的,而且采用的方式同样是先描述再组织,描述线程的task_struct结构体被叫做TCB–线程控制块,是英文Thread Contral Block的首字母。

描述好了以后同样像PCB一样,需要用链表组织起来进行管理,并也和PCB一样,有自己的管理算法。

但是,TCB中的属性和PCB几乎一样,管理TCB的数据结构和算法也和PCB的一样。
此时不仅会导致代码上的冗余,而且还会增加系统的开销,所以Linux并不是使用TCB管理线程的,因为这种方式比较复杂,维护起来不方便,而且运行也不是很稳定。

 Linux中,线程是直接复用PCB的数据结构和管理方法。
 所以在Liux操作系统中,进程和线程的描述结构体都是task_struct。

站在CPU的角度,它不关注被定义出来的进程还是线程这样的概念,它只关注task_struct。

CPU是一个被动的硬件,给它什么它就执行什么,所以它并不会区分当前执行的task_struct是一个进程还是一个线程,在它看来,都是进程

之前VS现在:

  • 之前:CPU执行的task_struct是一个进程。
  • 现在:CPU执行的task_struct是一个执行流。

所以说,今天CPU处理的task_struct 小于或等于 之前task_struct的含义。

站在内核的角度,称今天学习的task_struct为轻量级进程

我们可以通过虚拟地址空间 + 页表的方式对进程进行资源划分,让不同的“轻量级进程”同时执行不同部分的代码,所以单个“轻量级进程”的执行粒度,一定要比之前的进程细。

Linux内核中并没有线程的概念,线程是用进程PCB来模拟的。

由于Linux中,线程也是使用的PCB结构,是一种轻量化的进程,所以在Linux内核中并不存在线程的概念,也不存在线程的结构。

站在CPU的角度,每一个PCB都被称为轻量级进程。

CPU每次都是调度一个task_struct结构体,而这些PCB都是轻量级进程,有可能看作进程,也有可能看作线程,即使是看作进程,也可以看作是一个线程,因为无论是进程还是线程,都是一个个的执行流,CPU每次调度的都是一个执行流。

进程的重新定义:进程是承担分配系统资源的基本实体。

每创建一个进程,都会创建一个PCB,一个虚拟地址空间,一个页表,一块物理空间,而线程是属于这个进程中的执行流,它使用的是这个进程的资源。

所以此时的进程就包括因为创建它而产生的一系列开销(PCB,虚拟地址空间,页表,物理空间),这些都是属于这个进程的。

当这个进程中的某个线程申请新资源的时候,也是以该进程的名义去申请,而不是也这个线程的名义。

讲到这里,是不是觉得和之前学习的进程概念有冲突了?其实是自洽的。

之前我们学习的进程,每个进程只有一个执行流。

而现在每个进程中有多个执行流,每个执行流都是一个线程。

一个进程内可以有多个执行流,这些执行流都共用一个虚拟地址,一个页表。

最初的进程执行流被叫做主线程,之后创建的执行流被叫做新线程

主线程和新线程都属于一个进程,都是一体的,就像一个家庭中,有不同的成员,他们的工作是不同的,但是总目的都是一样的。

同样,多个线程同时工作的总目的也是相同的–为了完成这个进程的任务。

2.2 Linux的线程

通过上面介绍,我们知道,在Linux内核中是不存在线程这一个概念的,因为没有TCB数据结构以及管理算法,而我们所说的线程,都是在宏观层面,代指所有操作系统。

Linux操作系统中也没有提供创建线程的系统调用。

无论是宏观操作系统,还是用户(程序员)都只认线程的概念,但是Linux内核中并没有线程的概念。

我们(程序员)在编程的时候,仍然会使用线程的概念,那么我们在创建线程的时候,Linux内核中是怎么创建出轻量级进程的呢?

我们在创建进程的时候,会调用一个线程库,库中再通过一些系统调用创建出轻量级进程。

这样一来,程序员创建线程,Linux中创建轻量级进程,双方的要求就都满足了。

这个线程库是所有Linux操作系统必须自带的,所以也叫做原生线程库。

2.3 pthread_create

看看创建线程使用到的库函数接口,man pthread_create

  • pthread_t* thread:线程标识符tid,是一个输出型参数。
  • const pthread_attr_t* attr:线程属性,当前阶段一律设成nullptr。
  • void* (*start_routine)(void *):是一个函数指针,线程执行的就是该函数中的代码。
  • void* arg:传给线程启动函数的参数,是上面函数指针指向函数的形参。
  • 返回值:线程创建成功返回0,失败返回错误码。

先看一段pthread_create使用代码,Makefile:

mythread:mythread.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f mythread

mythread.cc:

#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;void *threadRun(void *args)
{const string name = (char*)args;while(true){cout << name << ", pid: " << getpid() << endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");while(true){cout << "main pthread pid: " << getpid() << endl;sleep(1);}return 0;
}

将上诉代码进行编译的时候,我们发现报错了,编译器不认识pthread_create函数。

我们创建新线程只能通过原生线程库去创建,此时编译器找不到原生线程库。

我们要使用-l选项指定原生线程库pthread,之前在动态静态库的时候详细介绍过如果指定动态库。

Makefile:

mythread:mythread.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f mythread

再次编译运行上面代码:

此时就证明pid是一样的,再看下创建几个新线程的代码:

#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;void* threadRun(void *args)
{const string name = (char *)args;while (true){cout << name << ", pid: " << getpid() << endl;sleep(1);}
}int main()
{pthread_t tid[5];char name[64];for (int i = 0; i < 5; i++){snprintf(name, sizeof(name), "%s-%d", "thread", i); // 特定内容格式化到name中pthread_create(tid + i, nullptr, threadRun, (void*)name);sleep(1); // 缓解传参的bug}while (true){cout << "main thread, pid: " << getpid() << endl;sleep(3);}return 0;
}

编译运行:

新线程和主线程都在同时运行,并没有陷入某一个死循环中。

2.4 原生线程库+LWP和PID

再前面基础上,ldd pthread(查看链接属性),再ll /lib64/libpthread.*(查看原生线程库)

查看可执行程序的链接属性。可以看到是动态链接,链接的库是原生线程库,如上图绿色框中所示。

根据线程库的路径去查看该路径下的所有文件,可以看到还有静态库,我们使用的线程库是一个软链接文件,它所链接的库才是真正的原生线程库。

在前面程序运行的时候打开一个窗口输入ps ajx | head -1 && ps ajx | grep mythread

主线程和新线程在同时运行,此时存在两个执行流。

但是在查看该进程的时候,发现mythread进程只有一个,pid,ppid等值也只有一个。

这也证明,线程是进程中的一个执行流,线程属于进程的一部分。

给线程发现kill -9 信号

给mythread进程发送9号信号,主线程和新线程都结束了。

  • 所有信号针对的都是进程,而线程属于进程。
  • 当一个进程结束以后,它的所有资源都会被回收,所以线程也就不存在了。

那我们想看到线程该怎么办呢?

再运行程序,使用指令ps -aL来查看线程。L必须大写,输入ps | aL

此时名字为mythread的线程有6个,它们的PID值相同,LWP不同。

  • PID:进程标识符
  • LWP:轻量级进程表示符,LWP是英文Light Weight Process的首字母。

可以看到,第一个线程的LWP和PID是一样的,这个线程就被叫做主线程

其它线程的LWP和PID不一样,这些线程就被叫做新线程

那么CPU在调度PCB的时候,根据的是LWP呢还是PID呢?

  • CPU在调度PCB的时候是根据LWP为标识符表示一个特点的执行流的。

因为CPU调度的都是轻量级进程,而每个轻量级进程也就线程的根本区别就在于LWP不同,但是不同线程的PID却有可能相同。

我们之前学习的进程,它只有一个执行流,也就是主线程,所以它的PID和LWP是相同的,即PID = LWP,我们使用哪个都无所谓。而现在我们学习了线程,就不能再只使用PID了,而是使用LWP。

3. 线程的公有资源和私有资源

3.1 线程的公有资源

所有线程都共享一个虚拟地址空间,一个页表,所以进程中的绝大部分资源都是所有线程共享的,先来看看共享的情况,写了一个公有函数,分别在主线程和新线程中调用这个函数:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void show()
{cout << "show , pid: " << getpid() << " " <<  endl;
}void* threadRun(void *args)
{const string name = (char *)args;while (true){cout << name << ", pid: " << getpid() << endl;show();sleep(1);}
}int main()
{// pthread_t tid[5];// char name[64];// for (int i = 0; i < 5; i++)// {//     snprintf(name, sizeof(name), "%s-%d", "thread", i); // 特定内容格式化到name中//     pthread_create(tid + i, nullptr, threadRun, (void*)name);//     sleep(1); // 缓解传参的bug// }pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");while (true){cout << "main thread, pid: " << getpid() << endl;show();sleep(1);}return 0;
}

可以看到,主线程和新线程都可以调用这个函数。

  • 该进程中只有一份虚拟地址空间,该函数放在代码段中。
  • 所有线程共享一个代码段。

创建一个全局变量,在主线程和新线程中都打印,并且使用后置++。

将全局变量的地址在主线程和新线程中打印出来。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;int g_val = 100;void show()
{cout << "show , pid: " << getpid() << " " << "g_val: " << g_val++ << " &g_val: " << &g_val <<  endl;
}void* threadRun(void *args)
{const string name = (char *)args;while (true){cout << name << ", pid: " << getpid() << "g_val: " << g_val++ << " &g_val: " << &g_val <<  endl;show();sleep(1);}
}int main()
{// pthread_t tid[5];// char name[64];// for (int i = 0; i < 5; i++)// {//     snprintf(name, sizeof(name), "%s-%d", "thread", i); // 特定内容格式化到name中//     pthread_create(tid + i, nullptr, threadRun, (void*)name);//     sleep(1); // 缓解传参的bug// }pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");while (true){cout << "main thread, pid: " << getpid() << "g_val: " << g_val++ << " &g_val: " << &g_val <<  endl;show();sleep(1);}return 0;
}

  • 新线程和主线程看到的全局变量是一个,当任意一个线程改变这个变量的值时,都会影响另一个线程使用这个值。
  • 主线程和新线程中,全局变量的地址是相同的,说明它们使用的是同一个全局变量。

根据上面现象以及分析,可以知道,数据段也是被所有线程共享的。

进程中的绝大部分资源都是和所有线程共享的。


3.2 线程的私有资源

因为所有线程都共享一个虚拟地址空间以及页表,线程之间有私有资源吗?答案肯定是有的。

  • PCB属性私有

所有线程都有各自的PCB,所以PCB中的属性肯定是私有的,属于各自线程。

  • 上下文数据私有

CPU在调度PCB的时候,采用轮转时间片的方式,当一个线程被换下时,该线程的上下文一定是私有的,防止被其他线程修改而导致恢复上下文的时候出现错误。

  • 栈结构私有

不同线程各自的临时变量一定是私有的,而临时变量存放在栈结构中,所有栈也是私有的。

都是同一块虚拟地址空间,怎么就让不同线程的栈结构私有了呢?这就涉及到了原生线程库的实现:man clone:

系统调用clone是用来创建子进程的,这里的子进程是轻量级进程,也就是没有独立的虚拟地址空间。

clone中有一个参数:void* child_stack,该参数就是用来自定这个子进程的栈空间的。

所以我们在使用pthread_create创建新线程的时候,底层会调用clone,并且会指定属于该线程的私有栈结构。

4. 线程的优缺点

4.1 线程的优点

与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多

  • 进程切换:PCB切换 + 上下文切换 + 虚拟地址空间切换 + 页表切换
  • 线程切换:PCB切换 + 上下文切换

可以看到,线程切换比进程切换少了两项。 除此之外,线程切换时硬件缓冲区不用太多地更新。

cache:硬件缓冲区,其实就是我们所说的高速缓存。

它存在于CPU中,速度只是比CPU慢一点,但是比内存快很多。

cache会根据局部性原理从内存中拿数据,尤其是使用频率高的热点数据会一直放在cache中。CPU在使用数据时,不是直接去内存中拿,而是先去cache中拿,如果不命中,也就是不存在,cache就会将CPU所需要的数据从内存中缓存到cache中。

  •  进程间切换:不仅上面提到的四项内容需要切换,而且cache中的内容也需要重新缓存。
  •  线程间却换:切换PCB和上下文,但是cache中缓存的数据不需要切换。

所以线程都共用一个虚拟地址空间和一个页表,而cache中的内容也是根据虚拟地址和页表缓存进来的,所以不同进程之间是可以共用的。

这样一来,大大节省了cache从内存中缓存数据的时间,并且也节省了操作系统的大量工作。

当然还有很多其他的优点,比如:

  • 创建一个新线程的代价要比创建一个新进程小得多,因线程不创建新的虚拟地址空间和页表。
  • 线程占用的资源要比进程少很多。
  • 能充分利用多处理器的可并行数量。
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

计算密集型应用:主要体现在CPU的高频工作,如加密,解密,算法等。

I/O密集型应用:主要体现在和外设的交互上,如访问磁盘,显示器,网卡等。

上面很多线程的优点,进程也是拥有的。

4.2 线程的缺点

  • 健壮性或者鲁棒性较差

编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

还有比如一个线程里出现了异常退出,其它进程也会退出:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;int g_val = 100;void show()
{cout << "show , pid: " << getpid() << " "<< "g_val: " << g_val++ << " &g_val: " << &g_val << endl;
}void *threadRun(void *args)
{const string name = (char *)args;while (true){cout << name << ", pid: " << getpid() << "g_val: " << g_val++ << " &g_val: " << &g_val << endl;show();sleep(1);static int cnt = 0;if (cnt == 7){int *p = nullptr;*p = 777;}cnt++;}
}int main()
{pthread_t tid[5];char name[64];for (int i = 0; i < 5; i++){snprintf(name, sizeof(name), "%s-%d", "thread", i); // 特定内容格式化到name中pthread_create(tid + i, nullptr, threadRun, (void *)name);sleep(1); // 缓解传参的bug}// pthread_t tid;// pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");while (true){cout << "main thread, pid: " << getpid() << "g_val: " << g_val++ << " &g_val: " << &g_val << endl;show();sleep(1);}return 0;
}

​​​​​​​

新线程中发送端错误异常,收到了11号信号SIGSEGV。

但是不仅这个新线程结束了,主线程和其它线程也结束了。

线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。而多进程就不存在,一个进程的退出并不会影响另一个进程。

  • 性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销(线程切换),而可用的资源不变。

  • 缺乏访问控制

进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

  • 编程难度提高

编写与调试一个多线程程序比单线程程序困难得多、

一般情况下,CPU有几个核就创建几个线程。

核:只的是一个CPU中的运算器个数,即使是多核,而控制器也是一个CPU只有一个。

本篇完。

下一篇:零基础Linux_22(多线程)线程控制和和C++的多线程和笔试选择题。

相关文章:

零基础Linux_21(多线程)页表详解+轻量级进程+pthread_create

目录 1. 页表详解 1.1 权限条目页框 1.2 页目录页表项 2. 线程的概念 2.1 轻量级进程 2.2 Linux的线程 2.3 pthread_create 2.4 原生线程库LWP和PID 3. 线程的公有资源和私有资源 3.1 线程的公有资源 3.2 线程的私有资源 4. 线程的优缺点 4.1 线程的优点 4.2 线程…...

nodejs+wasm+rust debug及性能分析

文章目录 背景v8引擎自带的profilelinux的perf采集wasm三方库性能分析编译debug版本wasmrust程序debug调试异常模型正常模型结论优化 参考 Node使用火焰图优化CPU爆涨 - 掘金 【Node.js丨主题周】理解perf 与火焰图-腾讯云开发者社区-腾讯云 Easy profiling for Node.js Applic…...

IP证书针对公网IP签发

很多项目应用需要采用IP地址数据桥接访问&#xff0c;这种情况下需要确保数据安全性及信任不被劫持的情况下&#xff0c;需要使用给IP地址增加数字证书进行保护。针对这种情况下我们对公网IP地址申请SSL证书做了详细的介绍&#xff0c;让我们可以更快地了解如何用IP地址去申请S…...

SpringBoot-集成Minio

官方文档&#xff1a;Kubernetes 的 MinIO 对象存储 — MinIO Object Storage for Kubernetes 一、简介 Minio 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频…...

【ML】cheatsheet

LR 原理与面试题目DT, Adaboost, GBDT, xgboost 原理 细节 与 例子 https://www.cnblogs.com/createMoMo/p/12635709.html xgboost挺详细的算法原理与例子 https://zhuanlan.zhihu.com/p/660468945 着重lightgbm就xgboost的改善方向 https://zhuanlan.zhihu.com/p/366952043机器…...

【字符串】【将字符数组转为字符串】Leetcode 122 路径加密

【将字符数组转为字符串】Leetcode 122 路径加密 解法1 在Java中&#xff0c;char数组没有直接的toString()方法来将其转换为字符串。如果你想将char数组转换为字符串&#xff0c;可以使用String类的构造函数来实现&#xff1a; ⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐…...

网络基础知识100问

1.什么是链接? 链接是指两个设备之间的连接。它包括用于一个设备能够与另一个设备通信的电缆类型和协议。 2.OSI 参考模型的层次是什么? 有 7 个 OSI 层&#xff1a;物理层&#xff0c;数据链路层&#xff0c;网络层&#xff0c;传输层&#xff0c;会话层&#xff0c;表示…...

女孩子就是要打扮漂亮,让童年不留遗憾

好的衣服当然要分享给好看的人啦&#xff01; 百搭圆领卫衣&#xff0c;经典版型不挑人穿 复合奥利绒面料&#xff0c;罗纹收口设计 时尚百搭怎么穿都好看 单穿内搭都可以 卡通鹅真的好可爱 宝贝穿上去真的元气满满哦...

实现目录数据的上移(up)、下移(down)、置顶(top)、置底(bottom)的操作

ApiOperation("8-15 交接班-标签设置排序")ApiImplicitParams({ApiImplicitParam(name "id", value "id", dataType "string", required true),ApiImplicitParam(name "orgnCode", value "机构代码", dataT…...

Ubuntu 常用命令

文章目录 Linux 目录结构常用命令ls&#xff1a;查看目录内容pwd&#xff1a;查看当前目录绝对路径cd&#xff1a;切换目录mkdir&#xff1a;创建目录rm&#xff1a;删除文件/目录touch&#xff1a;创建空文件mv&#xff1a;移动和重命名文件/目录cp&#xff1a;复制粘贴cat&am…...

如何空手套白狼?一口气省7K再抓住一个7K起步的工作?

今日话题&#xff0c;教你如何省七千再得到一个七千起步的技能&#xff01;现在网络行业已经是全世界重点发展的目标&#xff0c;开发行业更是各个企业重点培养&#xff0c;但是在学校教的网络知识太基础太老掉牙&#xff1f;报班随便就是小一万该如何是好呢&#xff1f;解决方…...

电脑主机如何选择内存条

选择计算机主机的内存模块&#xff08;内存条&#xff09;通常需要考虑以下因素&#xff1a; 类型和代数&#xff08;DDR3、DDR4、DDR5等&#xff09;&#xff1a;您的主板和处理器支持的内存类型非常重要。确保内存条的类型与您的主板和处理器兼容。 容量&#xff1a;内存容量…...

计算机考研自命题(5)

1、C语言–求和 1、展开式求和。输入一个实数x&#xff0c;计算并输出下式的和&#xff0c;直到最后一项的绝对值小于0.00001.计算结果保留2位小数&#xff0c;试编程。 S x x/2&#xff01; x/3&#xff01; … /* 算法思想&#xff1a;定义一个求阶乘的函数fact(), 头文件调…...

【原创】c语言4种字符串函数的代码测试

c语言4种字符串函数的代码测试 1.字符串拼接strcat [contact] 2.字符串复制strcpy 3.带参数的字符串复制strncpy 4.字符串比较strcmp&#xff1a;比第一个不同字母的ascii码&#xff0c;如acb>abc #include<stdio.h> #include<string.h>int main() {char s1[]&…...

扩散模型学习——代码学习

文章目录 引言正文UNet网络结构训练方法DDPM采样方法讲解Context上下文信息添加DDIM的方法详解 总结参考 引言 这是第一次接触扩散模型&#xff0c;为了学习&#xff0c;这里好好分析一下他的代码 正文 UNet网络结构 这部分主要是定义一下网络结构&#xff0c;以及相关的网…...

redis 数据结构

一、为什么要扒一下底层技术 首先我是一个解决方案工程师&#xff0c;为什么要看redis底层的设计呢&#xff1f;总结下来分几点&#xff1a; 1. 让系统跑起来更放心 2. 面试中可以对跟对面的牛马侃大山、吹&#x1f42e; 3. 虚一点&#xff0c;举一反三&#xff0c;学习一下…...

node.js中express框架cookie-parser包设置cookie的问题

后端使用node.js express cookie-parser技术栈设置cookie的时候出现了无法成功设置的问题 前端发送axios请求部分代码&#xff1a; axios({method: "post",data: {content: remark,relatedArticles: relatedArticleId,userId: userId,userEmail: userEmail,topRema…...

Docker命令手册

大家好&#xff0c;我叫徐锦桐&#xff0c;个人博客地址为www.xujintong.com。平时记录一下学习计算机过程中获取的知识&#xff0c;还有日常折腾的经验&#xff0c;欢迎大家访问。 记录平时用的比较多的Docker命令。 docker学习地址 1、docker停止并删除运行的容器 首先查看…...

Selenium+Pytest自动化测试框架详解

前言 selenium自动化 pytest测试框架 本章你需要 一定的python基础——至少明白类与对象&#xff0c;封装继承&#xff1b;一定的selenium基础——本篇不讲selenium&#xff0c;不会的可以自己去看selenium中文翻译网 一、测试框架简介 测试框架有什么优点 代码复用率高&…...

CentOS7安装部署CDH6.2.1

文章目录 CentOS7安装部署CDH6.2.1一、前言1.简介2.架构3.环境 二、环境准备1.部署服务器2.安装包准备3.修改机器名4.关闭防火墙5.关闭 SELinux6.Hosts文件7.limits文件8.设置swap空间9.关闭透明巨页内存10.免密登录 三、安装CM管理端1.安装第三方依赖包2.安装Oracle的JDK3.安装…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看

文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)

题目 做法 启动靶机&#xff0c;点进去 点进去 查看URL&#xff0c;有 ?fileflag.php说明存在文件包含&#xff0c;原理是php://filter 协议 当它与包含函数结合时&#xff0c;php://filter流会被当作php文件执行。 用php://filter加编码&#xff0c;能让PHP把文件内容…...

windows系统MySQL安装文档

概览&#xff1a;本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容&#xff0c;为学习者提供全面的操作指导。关键要点包括&#xff1a; 解压 &#xff1a;下载完成后解压压缩包&#xff0c;得到MySQL 8.…...

什么是VR全景技术

VR全景技术&#xff0c;全称为虚拟现实全景技术&#xff0c;是通过计算机图像模拟生成三维空间中的虚拟世界&#xff0c;使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验&#xff0c;结合图文、3D、音视频等多媒体元素…...