一篇文章学习什么是进程(万字解析,超多知识点)

news2024/9/30 19:30:32

目录

  • 进程概念
  • 进程控制块-PCB
    • PCB的内容分类
      • 标识符
        • 查看进程信息的方法
      • 状态
        • fork函数
        • 进程状态
          • R运行状态(running)
          • S睡眠状态(sleeping)
          • D磁盘休眠状态(Disk sleep)
          • T停止状态(stopped)
          • X死亡状态(dead)
      • 优先级
        • 进程优先级
          • 基本概念
          • PRI和NI
          • top-查看进程优先级的命令
      • 程序计数器
      • 内存指针
      • 上下文数据
      • I/O状态信息
      • 记账信息
  • 特殊进程
    • Z(zombie)-僵尸进程
      • 僵尸进程的作用
      • 僵尸进程的危害
    • 孤儿进程
  • 关于进程的其他概念
    • 并行和并发的区别
  • 程序地址空间
    • 实验环境
    • c语言角度的内存空间
    • 进程地址空间
      • mm_struct
        • mm_struct的理解
      • 页表
        • 页表的作用
      • 写时拷贝
        • 概念
        • 解释代码现象
        • 为什么需要写时拷贝
      • 总结

进程概念

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

进程控制块-PCB

进程信息都是放在一个叫进程控制块即PCB(process control block),可以简单地理解为进程数据集合。

在操作系统的学习中,我们知道了操作系统要管理进程,就要做到先描述,再使用,在Linux中内核描述进程的结构体叫做task_struct。

PCB的内容分类

标识符

标识符:描述进程的唯一标识符,用来区别其他进程。

首先我们来认识一个命令叫做ps-显示进程状态
语法:ps [参数]
常用参数参考详情
在这里插入图片描述
我们可以看到有PPID和PID,PID是本进程的标识符,PPID是父进程的标识符。

查看进程信息的方法

  • 上面的ps命令
  • 通过/proc系统文件夹来查看
    如获取PID为1的进程信息
  • 通过系统调用来获取进程标识符,getpid()和getppid()
    在这里插入图片描述
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
 printf("pid: %d\n", getpid());
 printf("ppid: %d\n", getppid());
 return 0;
}

状态

状态:任务状态、退出代码,退出信号等等

fork函数

首先我们认识一下系统调用接口fork-创建一个进程
在这里插入图片描述

  #include<stdio.h>
  #include<unistd.h>
  #include<sys/types.h>
                                                                                                                                           
  int main()
  {
    pid_t id=fork();
    if(id > 0)
    {
      //father
      printf("pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(5);
    }
    else if(id == 0)
    {
      //child
      printf("pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(5);
    }
    else
    {
      perror("fork");
      return 1;
    }
    return 0;
  }

在这里插入图片描述
在fork创建一个新进程后,子进程执行和父进程相同的代码,我们往往通过它的返回值进行分流,使父子进程执行不同的代码。

进程状态

首先我们看看Linux的源代码
在这里插入图片描述
我们可以通过源代码看出task_struct中通过一个字符指针数组来进行进程状态分类。
我着重介绍R、S、D、t、T、Z这几个状态。

R运行状态(running)

R运行状态:表示进程在运行中或者在运行队列中(可被cpu调度),不一定正在运作。

我们写一个死循环看看。

#include<stdio.h>
int main()
{
	while(1)
	{}
	return 0;
}

在这里插入图片描述
一定要记住,进程是R状态不一定在运行!!!

S睡眠状态(sleeping)

S睡眠状态:表示进程正在休眠不会被cpu调度直到被唤醒,此状态可以被杀掉。

#include<stdio.h>
#include<unistd.h>
int main()
{
	sleep(10);
	return 0;
}

在这里插入图片描述
此状态的意义是在进程需要某种资源才能继续跑下去时,我们将进程进行睡眠,节约cpu资源,在条件满足时将进程唤醒。

D磁盘休眠状态(Disk sleep)

D磁盘休眠状态:也叫不可中断睡眠状态,相比较S状态,在D状态时不能被杀掉(OS也不行)。

此状态不好实现,我们就讲讲为什么会有一个和S状态如此相像的D状态。
在一个进程向硬盘写入数据时,进程应该进入睡眠模式等待硬盘回应,如果此时内存空间严重不足时,OS可能会干掉一些S状态的进程分配给活跃的进程,此时就导致了一个问题,那就是如果硬盘在写入数据出现错误时它向进程询问该怎么办,但是此时进程已经被OS干掉了,就会陷入一个尴尬的场景。
那么上面描述中,是谁的错?
答案是都没错
进程:我与硬盘进行IO时进入睡眠模式节约cpu资源结果被cpu干掉了,我能怎么办?
硬盘:我在存入数据本来就有几率出现错误,但是我回去问进程该怎么办,它一直不回答,我能怎么办?
OS:内存资源已经非常少了,我干掉一些偷懒的进程把资源分配给活跃的进程,我有什么错。
所以为了避免上面这种情况,我们需要一个在睡眠状态不能被干掉的状态,那就是D状态。

T停止状态(stopped)

T停止状态:可以通过发送SIGSTOP信号让进程停止,可以通过发送SIGCONT信号让进程继续运行。

这里借用R状态的代码
在这里插入图片描述
首先进程处于R状态,kill命令可以给指定进程发信号,在我们向进程发送SIGSTOP时,我们可以看到进程的STAT从R+变成了T,在发送SIGCONT信号后STAT又变成了R。

信号有关的命令以后细嗦,先放个图了解一下。
在这里插入图片描述

X死亡状态(dead)

X死亡状态:这个状态只是一个返回状态,我们不能从ps中的STAT列表中看到此状态。

优先级

优先级:代表进程被cpu调度的优先级。

进程优先级

基本概念
  • cpu分配资源给进程的先后顺序,就是进程的优先级
  • 进程的优先级高,优先被cpu调度。
  • 将不重要的进程优先级降低可以提高cpu资源利用。

我们来看看ps -l中的信息
在这里插入图片描述
其中与进程优先级有关的数据就是PRI和NI

PRI和NI

PRI:代表进程的优先级,越低优先级越高。
NI:代表进程的nice值

PRI很好理解,那NI所代表的nice值是什么呢?

  • nice值我们可以简单地理解为PRI的修正数据
  • PRI越低优先级越高,那引入nice值后:(new)PRI=(old)PRI+NI
  • nice值的范围为-20~19。
  • 我们通过修改NI调整一个进程的优先级。
top-查看进程优先级的命令
  • top(有点类似Windows下的任务管理器)
    在这里插入图片描述
  • 进入top输入“r”->输入修改进程的pid->输入nice值(不管是降低还是增加nice值,都需要sudo提高权限)

修改前
在这里插入图片描述
输入“r”
在这里插入图片描述
输入nice值
在这里插入图片描述
修改成功
在这里插入图片描述
我们可以看出**(new)PRI=(old)PRI+NI**

程序计数器

程序计数器:程序中即将被执行的下一行代码的地址

这就是为什么程序知道自己的该怎样执行代码。

内存指针

内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。

进程可以通过内存指针找到自己的代码和数据以及共享内存块。

上下文数据

上下文数据:进程执行时寄存器中的数据

我们如何来理解这个上下文数据呢?
首先我们要明白cpu运行进程1执行到i++时,cpu需要将i取到寄存器中进行自加一,然后再写入到内存中,Linux中进程是通过时间片算法切换运行,达到一个并发的效果,但是如果cpu刚将i++完成准备从寄存器中把i写入到内存时进程1的时间片到了,cpu就会切换下一个进程,而cpu的寄存器是有限的,所以进程1在寄存器中的数据就会被覆盖,如果下次轮到进程1的时间片时,cpu又会重复上述过程导致cpu资源浪费,所以为了避免这种情况,我们需要将cpu寄存器中的数据在切片前保存到上下文数据中,这样的话,下次cpu跑该进程时先把上下文数据读取到寄存器中再执行下一步命令。

I/O状态信息

I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。

记账信息

记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

特殊进程

Z(zombie)-僵尸进程

  • 僵尸进程是一种特殊的状态。当进程退出时并且父进程没有读取进程的退出信息时,就会一直保持Z僵尸模式直到父进程读取。
  • 僵尸进程的生命周期为进程退出,父进程存在并读取前。

不多bb,上代码

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
	pid_t id=fork();
	if(id == 0)
	{
		//child
		printf("i am child,pid:%d,ppid:%d\n",getpid(),getppid());
		sleep(5);
	}
	else if(id > 0)
	{
		//father
		printf("i am father,pid:%d,ppid:%d\n",getpid(),getppid());
		sleep(30);
	}
	else 
	{
		perror("fork");
		return 1;
	}
	return 0;
}

在这里插入图片描述

在这里插入图片描述

僵尸进程的作用

帮助父进程了解子进程的任务完成情况以及退出信息。,比如,一个人突然死了,有人发现后选择报警肯定不会乱动尸体(除了凶手),警察发现尸体后也会保留案发现场,这些都是因为尸体和现场会留下信息让警察去侦察这个人是怎么死的。

僵尸进程的危害

进程的退出信息是保存在PCB中,如果父进程一直不读取子进程退出信息,这个PCB就会一直存在浪费内存资源。

孤儿进程

  • 在子进程退出父进程未读取时,会产生僵尸进程,那么如果父进程比子进程先退出,那子进程的退出信息又改由谁来回收呢?
  • 父进程先退出,子进程就会变成孤儿进程,然后被1号进程领养,从而1号进程负责回收子进程的退出信息。

这个代码与僵尸进程的代码相似。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
	pid_t id=fork();
	if(id == 0)
	{
		//child
		printf("i am child,pid:%d,ppid:%d\n",getpid(),getppid());
		sleep(30);
	}
	else if(id > 0)
	{
		//father
		printf("i am father,pid:%d,ppid:%d\n",getpid(),getppid());
		sleep(5);
	}
	else 
	{
		perror("fork");
		return 1;
	}
	return 0;
}

在这里插入图片描述

关于进程的其他概念

  • 竞争性:系统中进程数目众多,cpu资源只有少量,所以进程会竞争cpu资源的使用权,操作系统为了使进程合理使用cpu资源,便有了优先级。
  • 独立性:多进程运行会独享很多资源,各个进程运行中互不干扰。
  • 并行:多个进程在多个cpu下同时运行。
  • 并发:多个进程在单个cpu下采用进程切换的方式,在一个时间段中,使多个进程得到推进。

并行和并发的区别

并行是每个进程都有自己的cpu,在每一刻多个进程同时运行,而并发是很多进程轮流使用一个cpu资源,例如Linux采用时间片切换的算法,使多个进程在一个时间段中看起来像是同时进行,系统下进程数目往往远大于cpu资源,所以并发在大多数场景下更为合理。

程序地址空间

实验环境

kernel 2.6.32
32位平台

c语言角度的内存空间

相信大家在学习c语言的时候一定听过什么内存分为栈区堆区等等栈区向下生长、堆区向上生长,也看过下面这一副图。
在这里插入图片描述
我们可以通过一段代码来验证一下是不是如此。

#include<stdio.h>
#include<stdlib.h>
static int init_value=10;  //static修饰的全局变量只能在本文件中访问
static int uninit_value;

int main(int argc)
{
	int i=0;
	int* pi=malloc(sizeof(int));
	printf("%p\n",&argc);
	printf("%p\n",&i);
	printf("%p\n",pi);
	printf("%p\n",&uninit_value);
	printf("%p\n",&init_value);
	printf("%p\n",main);
	
	return 0;
}


我们可以看出确实如此,接下来我们看看另一段代码

 #include<stdio.h>
 #include<unistd.h>
 #include<sys/types.h>
 int global_val=10;
                                                                                                                                           
  int main()
  {
    pid_t id=fork();
    if(id > 0)
    {
      //father
      printf("i am father,%p,%p\n",&global_val,main);

    }
    else if(id == 0)
    {
      //child
      printf("i am child,%p,%p\n",&global_val,main);

    }
    else
    {
      perror("fork");
      return 1;
    }
    return 0;
  }

在这里插入图片描述
通过运行结果,我们可以得到一个结论,那就是在父子进程未对数据进行修改时,子进程的代码和数据是和父进程共享的。

接下来,我们在上述代码的基础上进行一些小小的改变。

 #include<stdio.h>
 #include<unistd.h>
 #include<sys/types.h>
 int global_val=10;
                                                                                                                                           
  int main()
  {
    pid_t id=fork();
    if(id > 0)
    {
      //father
	  sleep(5)
      printf("i am father,%p,%d\n",&global_val,global_val);

    }
    else if(id == 0)
    {
      //child
	  global_val=20;
      printf("i am child,%p,%d\n",&global_val,global_val);

    }
    else
    {
      perror("fork");
      return 1;
    }
    return 0;
  }

注意这里让父进程休眠了5秒,子进程将global_val的值改成了20,见证奇迹的时候到了。
在这里插入图片描述

我们可以看到父子进程的val不一样,但是他们的地址却是一样的,这到底是为什么呢?

  • 首先,他们的val不一样说明父子进程输出的val绝对不是同一个变量。
  • 其次,他们的地址一样说明这个打印出来的地址并不是真正的物理地址。

那这个地址到底是什么东西呢?

  • 在Linux下,这个地址叫做虚拟地址,听名字我们就知道了它不是真正的物理地址。
  • 注意我们通过C/C++打印看到的地址统统都是虚拟地址!用户看不到物理地址,物理地址由操作系统管理。

进程地址空间

有人看到这个标题肯定会有疑问,你怎么一会一个程序地址空间,一会一个进程地址空间呢?其实严格上讲,这个应该叫做进程地址空间。

那进程地址空间到底是什么样子的呢?其实它也是一个结构体,话不多说上源码。

mm_struct

struct mm_rss_stat {
	atomic_long_t count[NR_MM_COUNTERS];
};
/* per-thread cached information, */
struct task_rss_stat {
	int events;	/* for synchronization threshold */
	int count[NR_MM_COUNTERS];
};
#else  /* !USE_SPLIT_PTLOCKS */
struct mm_rss_stat {
	unsigned long count[NR_MM_COUNTERS];
};
#endif /* !USE_SPLIT_PTLOCKS */

struct mm_struct {
	struct vm_area_struct * mmap;		/* list of VMAs */
	struct rb_root mm_rb;
	struct vm_area_struct * mmap_cache;	/* last find_vma result */
#ifdef CONFIG_MMU
	unsigned long (*get_unmapped_area) (struct file *filp,
				unsigned long addr, unsigned long len,
				unsigned long pgoff, unsigned long flags);
	void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
#endif
	unsigned long mmap_base;		/* base of mmap area */
	unsigned long task_size;		/* size of task vm space */
	unsigned long cached_hole_size; 	/* if non-zero, the largest hole below free_area_cache */
	unsigned long free_area_cache;		/* first hole of size cached_hole_size or larger */
	pgd_t * pgd;
	atomic_t mm_users;			/* How many users with user space? */
	atomic_t mm_count;			/* How many references to "struct mm_struct" (users count as 1) */
	int map_count;				/* number of VMAs */

	spinlock_t page_table_lock;		/* Protects page tables and some counters */
	struct rw_semaphore mmap_sem;

	struct list_head mmlist;		/* List of maybe swapped mm's.	These are globally strung
						 * together off init_mm.mmlist, and are protected
						 * by mmlist_lock
						 */


	unsigned long hiwater_rss;	/* High-watermark of RSS usage */
	unsigned long hiwater_vm;	/* High-water virtual memory usage */

	unsigned long total_vm, locked_vm, shared_vm, exec_vm;
	unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
	unsigned long start_code, end_code, start_data, end_data;
	unsigned long start_brk, brk, start_stack;
	unsigned long arg_start, arg_end, env_start, env_end;

	unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

	/*
	 * Special counters, in some configurations protected by the
	 * page_table_lock, in other configurations by being atomic.
	 */
	struct mm_rss_stat rss_stat;

	struct linux_binfmt *binfmt;

	cpumask_t cpu_vm_mask;

	/* Architecture-specific MM context */
	mm_context_t context;

	/* Swap token stuff */
	/*
	 * Last value of global fault stamp as seen by this process.
	 * In other words, this value gives an indication of how long
	 * it has been since this task got the token.
	 * Look at mm/thrash.c
	 */
	unsigned int faultstamp;
	unsigned int token_priority;
	unsigned int last_interval;

	/* How many tasks sharing this mm are OOM_DISABLE */
	atomic_t oom_disable_count;

	unsigned long flags; /* Must use atomic bitops to access the bits */

	struct core_state *core_state; /* coredumping support */

我们之前提到操作系统要进行管理就要**“先描述,再组织”**,内存管理也不例外,mm_struct就是操作系统用来进行内存管理的结构体。

那么结构体里面的变量都是什么意思为什么操作系统可以通过这些变量来进行内存管理呢?

首先,我们要认识一个问题,那就是我们是如何管理地球上的土地(内存)的,各个国家通过划分边界线的方式来确定自己可以使用的土地,而这些国家通过气候环境等等因素将自己的土地划分成住宅区、农耕区、工业区,我们往往通过尺度单位(记录起始位置+结束位置)来划分土地,而只要边界确定好了,我们就可以通过边界来确认自己使用土地是否合法。

mm_struct的理解

对应到操作系统来说,划分区域是为了更好地管理内存,提高管理内存效率,而mm_struct中的一个个unsigned long变量就是每个区域的起始位置和结束位置,操作系统可以采用基地址+偏移量的方法来判断内存访问和申请是否合法。

页表

我们都知道mm_struct中的地址都是虚拟地址不是真正的物理地址,那么操作系统通过mm_struct来管理真正的物理内存呢?
接下来我们要引出一个概念叫做页表。

页表是一种特殊的数据结构,放在系统空间的页表区,存放逻辑页与物理页帧的对应关系。 每一个进程都拥有一个自己的页表,PCB表中有指针指向页表。

页表的作用

  • 存储虚拟地址和物理地址的映射,进程可以通过页表将虚拟地址找到物理地址并进行数据的增删查改。
  • 不会将真正的物理地址暴露给用户,保护内存。

写时拷贝

概念

写时拷贝是一种可以推迟甚至免除拷贝数据的技术。 内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。 只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。

解释代码现象

现在可以来解释这个神奇的结果了。
在这里插入图片描述

我们通过图来说明上面的代码问题。
在这里插入图片描述

  • 在父子进程fork子进程时,子进程复制父进程的数据形成task_struct和mm_struct以及页表,此时父子进程的global_val指向同一个物理地址。(子进程拥有自己的task_struct和mm_struct以及页表,但是内容与父进程相同)
  • 当子进程执行到global_val=20时就会发生写时拷贝,子进程会在物理内存上申请一个新的空间用来存放自己的global_val变量,接着子进程只需把页表中对应的物理内存修改成新的物理内存地址不需要修改虚拟内存地址,所以我们打印出来的地址一样。

为什么需要写时拷贝

因为进程之间具有独立性,就算是父子进程也不能影响其他进程的数据,并且在创建子进程时不开辟新的物理内存,等到需要时在申请空间,可以高效地使用内存空间。

总结

  • 子进程的创建实质就是task_struct和mm_struct以及页表的创建,拷贝父进程的数据。
  • 进程地址空间(mm_struct和页表)避免了系统级别的越界问题即错误访问物理内存。
  • 每个进程对待内存的看法都一样(因为mm_struct一样)。

写在最后:文中Linux源码来自Linux源码,创作不易,如果这篇文章对你有帮助,希望大佬们三连支持。

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

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

相关文章

【数据结构】最小生成树(Prim算法,普里姆算法,普利姆)、最短路径(Dijkstra算法,迪杰斯特拉算法,单源最短路径)

文章目录前置问题问题解答一、基础概念&#xff1a;最小生成树的定义和性质&#xff08;1&#xff09;最小生成树&#xff08;Minimal Spanning Tree&#xff09;的定义&#xff08;2&#xff09;最小生成树&#xff08;MST&#xff09;的性质二、如何利用MST性质寻找最小生成树…

1.2(完结)C语言进阶易忘点速记

1.大端存储&#xff1a;高权位数字放在低地址处&#xff0c;低权位数字放在高地指处。(以字节为单位) 2.小端存储&#xff1a;低权位数字放在低地址处&#xff0c;高权位数字放在高地址处。(以字节为单位) 3.变量(char类型)进行运算的时候一定要注意整形提升与截断&#xff0…

五、Java框架之Maven进阶

黑马课程 文章目录1. 分模块开发1.1 分模块开发入门案例示例&#xff1a;抽取domain层示例&#xff1a;抽取dao层1.2 依赖管理2. 聚合和继承2.1 聚合概述聚合实现步骤2.2 继承 dependencyManagement3. 属性管理3.1 依赖版本属性管理3.2 配置文件属性管理&#xff08;了解&#…

Easy-Es框架实践测试整理 基于ElasticSearch的ORM框架

文章目录介绍&#xff08;1&#xff09;Elasticsearch java 客户端种类&#xff08;2&#xff09;优势和特性分析&#xff08;3&#xff09;性能、安全、拓展、社区&#xff08;2&#xff09;ES版本及SpringBoot版本说明索引处理&#xff08;一&#xff09;索引别名策略&#x…

leaflet 解决marker呈现灰色边框的问题

第052个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet示例中处理marker外面有灰色边框的问题,请看未处理会后的图片。 处理后的结果非常满意,不再显示灰色边框。处理方法参考源代码。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果; 注意…

华为认证常见技术问答整理:什么是Datacom认证?

一、关于Datacom认证Q&#xff1a;什么是Datacom认证&#xff1f;A&#xff1a;Datacom&#xff0c;即DatacomCommunication的缩写&#xff0c;中文为“数据通信”&#xff0c;属于ICT技术架构认证类别&#xff08;华为认证包含ICT技术架构认证、平台与服务认证和行业ICT认证三…

内网渗透(十七)之内网信息收集-powershell收集域内信息和敏感数据定位

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

不停服更新应用的方案:蓝绿发布、滚动发布、灰度发布

原文网址&#xff1a;不停服更新应用的方案&#xff1a;蓝绿发布、滚动发布、灰度发布_IT利刃出鞘的博客-CSDN博客 简介 本文介绍不停服更新应用的方案&#xff1a;蓝绿发布、滚动发布、灰度发布。 升级服务器的应用时&#xff0c;要停止掉老版本服务&#xff0c;将程序上传…

(深度学习快速入门)第五章第三节:DCGAN人脸图像生成

文章目录一&#xff1a;CelebFaces Attribute&#xff08;CelebA&#xff09;数据集介绍二&#xff1a;模型结构三&#xff1a;代码编写&#xff08;1&#xff09;参数配置&#xff08;2&#xff09;数据集加载脚本&#xff08;3&#xff09;模型脚本&#xff08;4&#xff09;…

Kafka 副本

kafka 副本的基本信息 kafka副本作用提高数据可靠性kafka副本个数默认1个&#xff0c;生产环境中一般配置为2个&#xff0c;保证数据可靠性&#xff1b;但是过多的副本会增加磁盘存储空间、增加网络数据传输、降低kafka效率。kafka副本角色副本角色分为Leader和Follower。kafk…

【算法基础】前缀和与差分

&#x1f63d;PREFACE&#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐ 评论&#x1f4dd;&#x1f4e2;系列专栏&#xff1a;算法&#x1f4aa;种一棵树最好是十年前其次是现在1.什么是前缀和前缀和指一个数组的某下标之前的所有数组元素的和&#xff08;包含其自身&#x…

初学Docker

Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。 容器是完全使用沙箱机制&…

Linux内核启动(3,0.11版本)内核启动完成与进入内核main函数

这一部分是在讲解head.s代码&#xff0c;这个代码与bootsect.s和setup.s在同一目录下&#xff0c;但是head.s程序在被编译生成目标文件后会与内核其他程序一起被链接成system模块&#xff0c;位于system模块的最前面开始部分。system模块将被放置在磁盘上setup模块之后开始的扇…

论文阅读:《Evidence for a fundamental property of steering》

文章目录1 背景2 方法2.1 方向盘修正行为标识2.2 数据2.3 数据拟合3 结果3.1 速率曲线3.2 恒定的转向时间3.3 基本运动元素的叠加3.4 其他实验4 讨论5 总结&#xff08;个人&#xff09;1 背景 这篇短文的主要目的是去阐述“转方向盘”这一行为的基本性质&#xff1a;方向盘修正…

人大金仓数据库索引的应用与日常运维

索引的应用 一、常见索引及适应场景 BTREE索引 是KES默认索引&#xff0c;采用B树实现。 适用场景 范围查询和优化排序操作。 不支持特别长的字段。 HASH索引 先对索引列计算一个散列值&#xff08;类似md5、sha1、crc32&#xff09;&#xff0c;然后对这个散列值以顺序…

SpringBoot+Vue实现师生健康信息管理系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏…

第二章-线程(3)

线程一、线程的定义二、线程的实现一、线程的定义 线程&#xff1a; 线程是进程中的一个实体&#xff0c;是系统独立调度和分派的基本单位。 进程是资源的拥有者&#xff0c;线程是系统独立调度和分配的基本单位。 进程与线程的比较&#xff1a; 调度&#xff1a;线程调度快…

Python 的Tkinter包系列之七:好例子补充2

Python 的Tkinter包系列之七&#xff1a;好例子补充2 英汉字典&#xff08;使用文本文件记录英语单词和解释&#xff09;、简单的通信录&#xff08;使用SQLite数据库记录人员信息&#xff09; 一、tkinter编写英汉字典 先看效果图&#xff1a; 词典文件是一个文本文件&…

python自动发送邮件实现

目录1 前言2 准备工作2.1 电子邮件的基础知识。2.2 python邮件库2.3 邮箱设置3 python实现邮件自动发送3.1 SMTP()和send()方法介绍3.2 python实现实例参考信息1 前言 python功能强大&#xff0c;可以实现我们日常办公的很多任务。诸如批量处理word,excel,pdf等等文件&#xf…

事务基础知识与执行计划

事务基础知识 数据库事务的概念 数据库事务是什么&#xff1f; 事务是一组原子性的SQL操作。事务由事务开始与事务结束之间执行的全部数据库操作组成。A&#xff08;原子性&#xff09;、&#xff08;C一致性&#xff09;、I&#xff08;隔离性&#xff09;、D&#xff08;持久…