关于计算机面试重难点 之 操作系统,字节架构师有话说

死锁:在许多应用中进程需要以独占的方式访问资源,当多个进程并发执行时可能会出现相互等待对方所占用的资源而都无法继续向下执行的现象,此现象称为死锁

死锁产生的四个必要条件(发生死锁时,一定会有以下条件成立):

  • 互斥条件:一个资源只能被一个进程占有,进程应互斥且排他的使用这些资源。

  • 请求与保持条件:进程在请求资源得不到满足而等待时,不释放已占有的资源。

  • 不可剥夺条件:进程已经占有的资源,除非进程自己释放,其他进程不能强行剥夺 。

  • 循环等待条件:若干进程之间形成一种首位尾相接的环形等待资源关系。

处理死锁的基本策略和常用方法?#

================

预防死锁(破坏四个必要条件):

  • 并不是所有应用场景都可以破坏互斥条件。案例:SPOOLing技术将一台独享打印机改造为可供多个用户共享的打印机。(破坏互斥条件 )

  • 当进程在运行前一次申请完他所需要的全部资源,在他的资源未满足前,不让他投入运行。(破坏请求和保持条件)

  • 给不同进程设置优先级,当某个高优先级进程需要的资源被其它进程占有的时候,可以由操作系统协助将想要的资源强行剥夺。(破坏不可剥夺条件)

  • 给资源编号,进程必须按照编号从小到大的顺序申请自己所需资源。(破坏循环等待条件)

避免死锁(银行家算法):

预防死锁的几种策略,会严重地损害系统性能。在避免死锁时,要施加较弱的限制,从而获得较为满意的系统性能。具有代表性的避免死锁算法是银行家算法

银行家算法的实质就是要设法保证系统动态分配资源后不会进入不安全状态,以避免可能产生的死锁。 即每当进程提出资源请求且系统的资源能够满足该请求时,系统将判断满足此次资源请求后系统状态是否安全,如果判断结果为安全,则给该进程分配资源,否则不分配资源,申请资源的进程将阻塞。

银行家算法所需数据结构

  1. Available[j] 向量:系统中可利用的各种资源数目

  2. Max[i, j] 矩阵:每个进程对每种资源的最大需求

  3. Allocation[i, j] 矩阵:每个进程已分配的各类资源的数目

  4. Need[i, j] 矩阵:每个进程还需要的各类资源数

其中三个矩阵间存在下述关系:

Need[i, j] = Max[i, j] - allocation[i, j]

银行家算法流程

设Request是第i个进程P的请求向量,如果Request[j] = K,表示进程P需要K个j类型的资源。当P发出资源请求后,系统按下述步骤进行检查:

  1. 若Request[j] <= Need[i, j],转向2,否则认为出错(因为它所需的资源数目已超过它所宣布的最大值)。

  2. 若Requesti[j] <= Available[j],转向3,否则须等待(表现为进程P受阻)。

  3. 系统尝试把资源分配给进程P,并修改下面数据结构中的数值:

Available[j] = Available[j] – Request[j]

Allocation[i, j] = Allocation[i, j] + Request[j]

Need[i, j] = Need[i, j] –Request[j]

  1. 试分配后,执行安全性算法,检查此次分配后系统是否处于安全状态。若安全,才正式分配;否则,此次试分配作废,进程P等待。

安全性算法步骤:

检査当前的剩余可用资源是否能满足某个进程的最大需求,如果可以,就把该进程加入安全序列,并把该进程持有的资源全部回收不断重复上述过程,看最终是否能让所有进程都加入安全序列。

注:只要能找出一个安全序列,系统处于安全状态。当然,安全序列可能有多个。如果分配了资源之后,系统中找不出任何安全序列,系统就进入了不安全状态。

详解操作系统之银行家算法(附流程图)

死锁检测和死锁解除:

如果系统中既不采用预防死锁也不采用避免死锁的措施,系统就很有可能发生死锁。这种情况下,系统应当提供两种算法。

  1. 死锁检测算法:用于检测系统状态,确定系统中是否已经发生了死锁。所需数据结构:资源分配图,又叫资源有向图,如下:圆圈代表一个进程,方框代表一类资源,方框内的圆圈代表该类资源的一个单位的资源。从进程到资源的有向边为请求边,表示该进程申请一个单位的该类资源;从资源到进程的边为分配边,表示该类资源已有一个资源分配给了该进程。图中,进程P1已经分得了两个R1资源,请求了一个R2资源;进程P2分得了一个R1资源和一个R2资源,并又请求了一个R1资源。算法流程:尝试将满足运行条件的进程有向边消去以简化资源分配图,如果能简化说明系统占时没有出现死锁。如果此时系统的资源分配图是不可简化的,那么此时发生了系统死锁(死锁定理)。资源分配图简化实例:按照死锁定理中,找出的进程为P1,因为它申请的资源可以被满足,说明(a)时刻没有发生死锁。

  2. 死锁解除算法:该算法可将系统从死锁中解脱出来。资源剥夺法:将一些死锁进程暂时挂起来,并且抢占它的资源,并将这些资源分配给其他的死锁进程 ,要注意的是应该防止被挂起的进程长时间得不到资源而处于饥饿状态。撤销进程法:强制撤销部分甚至全部死锁并剥夺这些进程的资源。撤销的原则可以按照进程优先级和撤销进程的代价高低进行。进程回退法:让一或多个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而非被剥夺。这个方法要求系统保持进程的历史信息,并设置还原点。

死锁的检测及解除

进程间通信(IPC)的方式有哪些?#

==================

  1. 管道(pipe)管道可用于具有亲缘关系的进程间的通信,通常指父子进程之间;同时,管道是一种半双工的通信方式,数据只能单向流动。所谓管道,实际是内核管理的一串缓存,生命周期随进程的创建而创建,随进程的结束而销毁。在 Linux系统(一种 UNIX系统)中,可以在C代码中调用pipe系统调用创建管道并通信:#include <unistd.h> int pipe(int pipedes[2]); // 创建管道 成功返回0,失败返回-1,pipedes[0]指向管道的读端,pipedes[1]指向管道的写端。使用时,先创建管道并得到两个分别指向管道两端的文件描述符,父进程通过fork函数创建子进程,然后子进程也有两个文件描述符分别指向同一管道的两端;父进程通过 close(pipedes[0]) 关闭管道读端,子进程通过close(pipedes[1])关闭管道写端;父进程通过write(pipedes[1], … )系统调用往管道里写,子进程通过read(pipedes[0], …)系统调用从管道里读。(这里是父写子读,也可以反过来)备注:头文件 unistd.h 意为:unix std,其提供了访问 POSIX 操作系统 API的功能。类似于 Windows系统提供的 windows.h。POSIX,Portable Operating System Interface of UNIX,可移植操作系统接口,是 IEEE为了方便在各种 UNIX系统之间移植软件而定制的标准。示例:#include <stdio.h> #include <unistd.h> #include <string.h> int main() { int pipedes[2]; if (pipe(pipedes)) { perror(“pipe() fail”); return -1; } pid_t pid = fork(); if (pid < 0) { perror(“fork() fail”); return -2; } // child process if (pid == 0) { close(pipedes[0]); char str[] = “Hello, parent!”; //这里用没有发送结束符’\0’ int len = write(pipedes[1], str, strlen(str)); printf(“write len : %d \n”, len); return 0; // parent process } else { close(pipedes[1]); char buf[1024]; int len = read(pipedes[0], buf, sizeof(buf)); //手动添加上结束符 buf[len] = ‘\0’; printf(“read message from child process : %s , length %d \n”, buf, len); return 0; } }

  2. 命名管道(named pipe)命名管道克服了管道没有名字的限制,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道提供一个路径名与之关联,以文件形式存储文件系统中,只要进程可以访问该路径,就可以通过该命名管道相互通信。在Linux系统中,可以在shell中通过mkfifo命令创建命名管道;也可以在C源代码中通过调用int mkfifo(const char * pathname, mode_t mode)系统调用创建,第一个参表示命名管道路径,第二个表示文件权限,通常为0666(可读可写)。要借助该命名管道通信的进程在源代码通过open、write、read、close系统调用配合进行通信。(命名管道也是半双工的)示例:shell中运行命令 mkfifo mypipe,会创建一个名为mypipe的命名管道。创建 namepipe_test_write.c 作为写端:#include <stdio.h>#include <string.h> #include <fcntl.h> #include <unistd.h> // 一次传输的字符个数不超过127个字符,最后一位存’\0’ #define LEN 128 int main() { int filedes = open(“./mypipe”, O_WRONLY); char buf[LEN] = {‘\0’}; while(1) { printf(“please input message: \n”); int i = 0; while (i < LEN - 1) { char ch = getchar(); if (ch == ‘\n’) { break; } buf[i++] = ch; } //手动添加字符串结束符 buf[i] = ‘\0’; if (strcmp(buf, “quit”) == 0) { break; } int len = write(filedes, buf, strlen(buf)); printf(“write len : %d \n”, len); printf(“-----------------------------------\n”); } close(filedes); return 0; } 创建namepipe_test_read.c作为读端:#include <stdio.h> #include <string.h> #include <fcntl.h> #include <unistd.h> int main() { intfiledes = open(“./mypipe”, O_RDONLY); char buf[128] = {‘\0’}; while(read(filedes, buf, sizeof(buf)) > 0) { printf(“read from pipe : \n%s\n”, buf); memset(buf, ‘\0’, sizeof(buf)); printf(“-----------------------------------\n”); } close(filedes); return 0; } 编译后分别在两个终端中运行,无论谁先启动都行。当两个进程都启动后,写端才会打印出提示符,表示可以开始通信。在写端输入字符串并回车后,读端会读取并打印。当写端输入quit字符串时,通信结束。详情如下图:(编译运行环境:w10下 wsl2(ubuntu 20.04))

  3. 信号(signal)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。信号用于通知接收进程有某种事件发生,接收到该信号的进程会相应的采取一些行动。在Linux系统中,信号在signal.h中定义,信号的名称都以SIG开头,如:SIGINT:终止信号 (ctrl + c)SIGQUIT:退出信号 (ctrl + \)SIGSTOP:暂停信号 (ctrl + z )SIGSCONT:继续信号 (ctrl + z)SIGALRM:闹钟信号,常用作定时器SIGCHLD:子进程状态改变,父进程收到信号SIGKILL:杀死信号( kill -9 pid)信号发送:通常,用户可以通过按键、调用kill命令、或在C源代码中调用int kill(pid_t pid, int sig)系统调用来向另一个进程发送信号。信号处理:接收进程可以通过signal、sigaction函数注册对信号的处理方式。如:忽略(SIGKILL,SIGSTOP不能被忽略)、默认处理、自定义处理。

  4. 消息队列(message queue)消息队列本质上是位于内核空间的链表,链表的每个节点都是一条消息,每一条消息都有自己的消息类型,这个消息类型是由发送方和接收方约定。不同于管道,从消息队列读取数据时不一定要以先进先出的次序读取,可以按消息的类型读取,借此可以实现提前查看紧急消息的功能。消息队列的生命周期是伴随着内核的,如果没有手动释放消息队列,他会直到操作系统关闭才释放。在Linux系统中,利用C语言,通过调用int msgget(key_t key, int msgflg)系统调用创建(不存在时)消息队列并得到消息队列的唯一标识符。两个需要通信的进程中,只要传入msgget中的key相同,他们得到的消息队列标识符就相同。key通常使用key_t ftok(const char *pathname, int proj_id)系统调用来获得。进程获得消息队列唯一标识符后可以使用int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)、ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)系统调用分别向消息队列发送消息和从消息队列接收消息。示例:msg.h:定义消息格式#ifndef _MSG_ #define _MSG_ typedefstruct msg { long msgType; //必须是long型且是结构体第一个变量 charmessage[128]; //类型任意 //…可以有更多数据 } Msg; #endifmsg_queue_test_write.c:发送端#include <stdio.h> #include <string.h>#include <sys/ipc.h> #include <sys/msg.h> #include <errno.h>#include “msg.h” int main() { key_t key = ftok(“./”, 2021); if (key == -1) { perror(“ftok() fail”); return -1; } //创建(不存在时)消息队列并得到消息队列的唯一标识符, 0666表示权限 int msgqid = msgget(key, IPC_CREAT | 0666); if (msgqid == -1) { perror(“msgget() fail”); return -2; } //发送类型 1 ~ 5 共 5 种类型的消息 for (int i = 1; i < 6; i++) { Msg msg; msg.msgType = i; strcpy(msg.message, “Hello : “); //四字节int型转化为字符串长度最多为12:一个符号 + 10个数字 + 一个’\0’结束符 char tmp[12] = {‘\0’}; sprintf(tmp, “%d”, i); _//将i转化为字符串并拼接至message_strcat(msg.message, tmp); //最后一个参数:阻塞方式发送消息,如果消息队列没有空间接收新发送的消息则阻塞 int flag = msgsnd(msgqid, &msg, sizeof(msg) - sizeof(long), 0); if (flag == -1) { printf(“msgsnd(): send type: %d failed: %s”, i, strerror(errno)); } } return 0; } msg_queue_test_read.c:接收端#include <stdio.h> #include <string.h>#include <sys/ipc.h> #include <sys/msg.h> #include <errno.h>#include “msg.h” int main() { key_t key = ftok(”./”, 2021); if (key == -1) { perror(“ftok() fail”); return -1; } //创建(不存在时)消息队列并得到消息队列的唯一标识符, 0666表示权限 int msgqid = msgget(key, IPC_CREAT | 0666); if (msgqid == -1) { perror(“msgget() fail”); return -2; } Msg msg; // 第二个参数:指明消息长度(不包含消息类型) // 倒数第二个参数: 大于0时表示获取制定类型的消息,如果等于0表示获取最前面的消息 // 最后一个参数:如果消息队列为空就阻塞,直到读取到消息 int len = msgrcv(msgqid, &msg, sizeof(Msg) - sizeof(long), 0, 0); if (len < 0) { perror(“msgrcv() fail”); } printf(“receive %d length: %s \n”, len, msg.message); return 0; } 编译后在两个终端中分别开启读端和写端(无论先后),消息队列开始为空,如果先启动读端,读端会被阻塞,直到写端写了数据后,读端读取到数据了才会退出。该例中,写端一次写入五个消息,读端可以成功读取五次,第六次读取会被阻塞,直到消息队列又有新的消息到达。如下:(编译运行环境:ubuntu 20.14。在wsl2(ubuntu 20.14)中编译可以通过,但运行时提示没有相应实现)以上代码并没有释放消息队列(msgctl(msgqid, IPC_RMID, 0)函数可以释放),因此,程序运行结束后,通过命令ipcs -q仍然可以看到我们创建的消息队列(系统重启后消失):最后,我们可以通过命令ipcrm -q msqid释放消息队列。消息队列

  5. 共享内存(shared memory)不同程序拥有各自独立的逻辑地址空间,不能相互访问。共享内存通过将一块物理内存映射到不同进程的逻辑地址空间,使得他们可以访问同一块物理内存,从而实现共享内存。访问共享内存区域和访问进程独有的内存区域一样快,读取和写入的过程中不用像管道和消息队列那在用户态与内核态之间拷贝信息,因此,共享内存是最快的进程间通信形式。由于多个进程共享同一块内存区域,为保证正确通信,需要借助同步机制,如:信号量,来进行同步。在Linux系统中,利用C语言,通过调用int shmget(key_t key, size_t size, int shmflg)系统调用创建(不存在时)共享内存并得到共享内存的唯一标识符,其中key和消息队列中提到的key相同。进程获得共享内存唯一标识符后通过调用void *shmat(int shm_id, const void *shm_addr, int shmflg)系统调用建立(attach)用户进程空间到共享内存的映射,得到指向共享内存的指针。根据该指针就可以利用系统读写函数向共享内存中写数据或者从共享内存读取数据。通信完成后,进程通过调用int shmdt(const void *shmaddr)函数解除(detach)映射关系,shmaddr参数是之前调用shmat时的返回值(指向共享内存的指针)。示例:shared_memory_test_write.c:写端#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> intmain() { //根据路径和指定的id生成唯一的key key_t key = ftok(“/”, 2021); if (key == -1) { perror(“ftok() fail”); } //创建(不存在时)大小为1024KB的共享内存,0666代表权限 int shmid = shmget(key, 1024, IPC_CREAT | 0666); if (shmid == -1) { perror(“shmget() fail”); } //attach,将共享内存映射到当前进程 //第二个参数:共享内存连接到当前进程中的地址,通常为0,表示让系统来选择 //第三个参数:shm_flg是一组标志位,通常为0 //返回共享内存地址 void *shm = shmat(shmid, 0, 0); if (shm == (void *)-1) { perror(“shmat() fail”); } //将键盘输入的数据写入共享内存 fgets(shm, 1024, stdin); //detach,把共享内存从当前进程中分离出去 int flag = shmdt(shm); if (flag == -1) { perror(“shmdt() fail”); } return 0; } shared_memory_test_read.c:读端#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> int main() { //根据路径和指定的id生成唯一的key key_t key = ftok(“/”, 2021); if (key == -1) { perror(“ftok() fail”); } //创建(不存在时)大小为1024KB的共享内存,0666代表权限 intshmid = shmget(key, 1024, IPC_CREAT | 0666); if (shmid == -1) { perror(“shmget() fail”); } //attach,将共享内存映射到当前进程 //第二个参数:共享内存连接到当前进程中的地址,通常为0,表示让系统来选择 //第三个参数:shm_flg是一组标志位,通常为0 //返回共享内存地址 void *shm = shmat(shmid, 0, 0); if (shm == (void *)-1) { perror(“shmat() fail”); } //将键盘输入的数据写入共享内存 fputs(shm, stdout); //detach,把共享内存从当前进程中分离出去 int flag = shmdt(shm); if (flag == -1) { perror(“shmdt() fail”); } return 0; } 编译后先运行写端,输入hello world回车后程序退出;然后运行读端,程序读取并打印出hello world。如下:(编译运行环境:ubuntu 20.14)以上代码并没有释放共享内存(shmctl(shmid, IPC_RMID, 0)可以释放),因此,程序运行结束后,通过命令ipcs -m仍然可以看到我们创建的共享内存(系统重启后消失)。最后,我们可以通过命令ipcrm -m shmid释放共享内存。进程间的7种通信方式(含例程代码)Linux下进程间通信方式——共享内存

  6. 信号量(semaphore)信号量的原理是一种数据操作锁的概念,可用于多个进程间的同步。它本身并不具备数据交换的功能,而是通过控制其他的通信资源来实现进程间通信。我们可以将其理解成一个具有原子性的计数器,每当有进程申请使用信号量,通过一个P操作来对信号量进行-1操作,当计数器减到0的时候就说明没有资源了,其他进程继续访问就会被阻塞,当该进程执行完这段工作释放临界资源之后,就会执行V操作来对信号量进行+1操作,被阻塞的进程就会被唤醒。在Linux系统中,利用C语言,通过调用int semget(key_t key, int nsems, int semflg)系统调用创建(不存在时)信号量并得到信号量的唯一标识符,其中key和前述key相同。可以通过命令ipcs -s查看系统当前存在的信号量,通过命令ipcrm -s semid可以释放信号量。进程间的通信之信号量用信号量为共享内存添加同步机制

  7. 套接字(socket)更为一般的进程间通信机制,可用于运行在不同主机上的进程之间通信。他通过IP地址和端口号确定一台主机上一个进程,常用于网络编程。

备注:以上这些通信方式并不是所有的操作系统都提供了实现,即使操作系统提供了实现,编程语言也不一定提供了访问的接口。以上消息队列、共享内存和信号量都是基于System V规范的(另一种实现是基于POSIX规范的)。

常见内存管理方式有哪些?#

=============

内存管理机制分为连续分配管理方式和非连续分配管理方式。前者为进程分配一个连续的内存空间,是古老的内存管理方式,常见的有单一连续分配固定分区分配等。后者是充分利用离散的内存,将进程分散的装入内存分区中;根据分区大小是否固定可以分为页式管理(固定分区大小)和段式管理(不固定分区大小);还可以二者混用成段页式管理

什么是分页存储管理?什么是分段存储管理?区别是什么?#

===========================

分页存储管理:

分页存储管理极大的提高了内存利用率,他将实际的物理内存分为大小相等的块,通常被称为页帧。页式管理将用户程序所需内存以离散的页帧形式分配给他们。每个用户程序都有自己的逻辑地址空间,逻辑空间也被划分为与页帧大小相同的页面,逻辑页面和物理页帧是一一对应的关系。

那么CPU寻址时,是如何完成逻辑地址到实际物内存地址的转换呢?首先要知道,逻辑地址被划分为高位和低位,高位代表当前逻辑地址所在页面对应的页号,低位代表的是页内偏移,以32位操作系统来说,他的逻辑地址共有32位,如果页面(由页帧大小决定)大小为4KB(4 * 1024 = 212,注:地址单位为B,字节。)则需要占用低12位来表示页内偏移。显然,CPU仅仅借助逻辑地址是无法完成寻址的,还需要借助进程页表才能完成逻辑地址到物理地址的转换,页表中记录的是页面和页帧的对应关系。开始寻址时,CPU根据逻辑地址得到页号和页内偏移,查询页表可得到页号对应页帧在物理内存中的起始地址,页帧起始地址加上页内偏移即可得到实际的物理地址。如下图:

关于计算机面试重难点 之 操作系统,字节架构师有话说

分段存储管理

分页存储是从计算机的角度进行设计的,目的是为了提高内存的利用率,每一页面并没有实际意义。段式存储管理从程序员和用户角度出发,把程序分割成具有逻辑意义的段,例如:主程序段、子程序段、数据段等等,每一段的大小不定。段式管理将用户程序所需内存以离散的内存段的形式分配给她们。借助段式管理容易实现数据的共享与保护。

那么分段存储管理中,CPU又是如何完成寻址的呢?分段存储管理中,逻辑地址同样被划分为高位和低位,高位表示段号,低位表示段内偏移。仅仅根据段号和段内偏移尚无法完成寻址,还需要借助进程段表,段表记录了逻辑段的大小(段长)以及逻辑段在内存中的起始地址(基址)。开始寻址时,CPU先拿到指明的段号和段内偏移(由于段长不定,段号和段内偏移无法像分页管理那样根据逻辑地址和页面大小直接计算出来页号和页内偏移,需要指明逻辑地址中哪部分表示段号,哪部分表示段内偏移,这也是段式管理中逻辑地址是二维的原因),继续查询段表可以得到逻辑段的基址(段表中的段长是用来检查当前段的段内偏移是否超过段长而发生越界),基址加上段内偏移即可得到实际的物理地址。如下图:

关于计算机面试重难点 之 操作系统,字节架构师有话说

分页管理和分段管理区别:

  • 分页管理是站在计算机角度进行设计,每一页并无逻辑意义,目的是减少外部碎片,提高内存的利用率,对用户不可见;段式管理站在程序员和用户角度,是一种逻辑上的划分,是为了满足用户的需要,对用户是可见的,编程时需要指明段名和段内地址(汇编语言中指明了段名和段内地址就指明了逻辑地址的段号和段内偏移)。

  • 分页管理中,页面大小是固定的;而分段管理中段的大小取决于具体程序代码段,是变化的。

  • 分页管理中,逻辑地址是一维的;而分段管理中逻辑地址是二维的。

  • 在实现对程序和数据的共享与保护时,分段管理中,程序和数据本就按逻辑段存储在内存段中,容易实现对程序段、数据段的共享控制以及保护控制;而分页管理中,逻辑上的代码段或数据段是被分散的存储在各个离散的内存页帧当中,很难实现对逻辑程序段或逻辑数据段的共享与保护。

注:进程页表和进程段表都存放于物理内存中,且一个页表或段表是占用的连续空间。以上图中为了方便表达没有将页表或段表画在物理内存中。

操作系统之页式管理

段页式存储管理了解吗?#

============

在分页存储管理中,内存利用率高,不会产生外部碎片,但不方便实现数据的共享与保护;而分段存储管理则刚好相反。段页式存储管理就是为了结合两者的优点。简单来说,段页式存储管理将逻辑空间划分为逻辑段,逻辑段再划分为逻辑页面;而物理内存划分为大小相同的页帧,逻辑页面和物理页帧一一对应,并装入物理页帧当中。

在段页式存储管理中,逻辑地址被划分为三段,由高到低依次代表段号页号页内偏移。CPU寻址时需要借助段表和页表,段表记录了各个逻辑段对应页表的物理内存起始地址,以及页表长度;页表则记录了各个逻辑页面对应物理页帧的起始地址。 寻址开始时,CPU首先拿到指明的段号并根据页面(页帧)大小计算出页号和页内偏移,CPU根据段号和段表可以找到该逻辑段对应页表的起始物理内存地址,再根据页号和页表找到对应页帧首地址,该首地址加上页内偏移即可得到实际的物理地址。(段表中的页表长度用来检查页号是否越界)。如下图:

关于计算机面试重难点 之 操作系统,字节架构师有话说

操作系统之段式管理及段页式管理

虚拟存储器(虚拟内存)了解吗?#

================

基于局部性原理,在程序装入时,可以先装入当前运行需要的部分,然后就可以开始启动程序,而将其他的部分暂时留在外存。在程序执行时,如果访问的信息不在内存中,由操作系统将所需要的部分调入内存;如果此时内存已经没有空间给新调入的部分,那么操作系统按照某种淘汰策略将一部分旧的内容暂时换到外存上存放,然后再将新需要的部分调入内存,接着继续执行程序。这样,操作系统就可以执行比实际内存大的多的程序,就好像为用户提供了一个比实际内存大的多的存储器。

虚拟存储器种类:

总结

阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了

image

1、JAVA面试核心知识整理(PDF):包含JVMJAVA集合JAVA多线程并发,JAVA基础,Spring原理微服务,Netty与RPC,网络,日志,ZookeeperKafkaRabbitMQ,Hbase,MongoDB,Cassandra,设计模式负载均衡数据库一致性哈希JAVA算法数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。

image

2、Redis学习笔记及学习思维脑图

image

3、数据面试必备20题+数据库性能优化的21个最佳实践

image

衡**,数据库一致性哈希JAVA算法数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。

[外链图片转存中…(img-YRqtOXNi-1714582925262)]

2、Redis学习笔记及学习思维脑图

[外链图片转存中…(img-hK0NsISv-1714582925262)]

3、数据面试必备20题+数据库性能优化的21个最佳实践

[外链图片转存中…(img-yqjjfxdf-1714582925263)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值