什么是进程、线程
进程是资源分配的基本单位,它是程序执行时的一个实例,在程序运行时创建
线程是程序执行的最小单位,是进程的一个执行流,一个进程了包含多个线程
进程、线程、协程的区别
协程是微线程,在子程序内部执行,可在子程序内部中断,转而执行别的子程序,在适当的时候再返回来接着执行。
进程和线程
- 进程是资源分配的最小单位
- 线程是最小的执行单位,也是处理器调度的基本单位
- 进程拥有自己的独立地址空间,每启动一个进程,系统就会分配地址空间,建立数据表来维护代码段,数据段,进程的全局变量是不共用的,这种开销非常大,而线程是共享进程的数据,使用相同的地址空间,因此,cpu切换一个线程的开销远小于进程
- 线程间通信更加方便,同一进程下的线程共享全局变量、静态数据、而进程间通信需要以通信的方式IPC进行,但是线程的缺点是同步或互斥是编写多线程的难点,多线程的优点是一个进程死掉不会对另一个进程有影响,而多线程只要一个线程死掉进程就会死掉
- 每个线程拥有自己的栈段
gpt
- 定义:
- 进程:进程是程序的一次执行过程,是操作系统进行资源分配和调度的基本单位。每个进程都有独立的内存空间,包括代码、数据、堆栈等。
- 线程:线程是进程中的一个执行流,是CPU调度的基本单位。同一进程中的多个线程共享相同的内存空间,包括代码段、数据段、堆和栈。
- 资源占用:
- 进程:每个进程有独立的内存空间和系统资源,进程之间相互独立,需要进行进程间通信才能共享数据。
- 线程:线程共享所属进程的内存空间和系统资源,线程之间可以直接访问共享数据,因此线程间通信更加方便。
- 切换开销:
- 进程:进程切换的开销较大,需要保存和恢复整个进程的上下文信息,包括内存空间、寄存器状态等。
- 线程:线程切换的开销较小,因为线程共享相同的内存空间,切换时只需保存和恢复线程的上下文信息。
- 并发性:
- 进程:进程之间是相互独立的,进程间通信需要额外的机制,如管道、消息队列、共享内存等。
- 线程:线程共享相同的内存空间,可以直接访问共享数据,因此线程间通信更加方便,实现并发编程更容易。
- 稳定性:
- 进程:由于进程之间相互独立,一个进程崩溃不会影响其他进程。
- 线程:线程共享相同的内存空间,一个线程的崩溃可能会影响整个进程的稳定性。
何时使用多进程?何时使用多线程?(考虑优缺点)
多进程
适用场景:
- 需要大量独立计算任务:当需要执行多个独立的计算任务,且这些任务之间不需要频繁交互或共享数据时,多进程是一个很好的选择。
- 利用多核CPU:如果你的系统有多个CPU核心,并且你希望充分利用这些核心,多进程是一个好的选择,因为每个进程都可以独立地在一个CPU核心上运行。
- 提高稳定性:当某个进程崩溃时,其他进程不会受到影响,这有助于增加系统的稳定性。
优点:
- 稳定性高:每个进程相互独立,一个进程的崩溃不会影响其他进程。
- 资源隔离:每个进程都有自己的地址空间和资源,这有助于避免资源竞争和数据安全问题。
缺点:
- 开销大:创建和管理进程的开销通常比线程大,因为每个进程都需要分配独立的内存空间和资源。
- 通信复杂:进程间的通信(IPC)相对复杂,可能需要使用消息队列、共享内存等机制。
多线程
适用场景:
- 共享数据和资源:当多个任务需要共享数据或资源时,使用多线程更为方便。
- I/O密集型任务:对于涉及大量输入/输出操作的任务(如网络请求、文件读写等),多线程可以更有效地利用CPU时间,因为在等待I/O操作完成时,线程可以被切换到其他任务上。
- GUI应用:在图形用户界面(GUI)应用中,多线程常用于处理后台任务,以保持界面的响应性。
优点:
- 开销小:线程的创建和管理开销相对较小,因为多个线程共享同一个进程的地址空间。
- 通信简单:线程间共享数据相对简单,可以通过共享内存直接访问。
缺点:
- 资源竞争:多个线程可能同时访问共享资源,导致资源竞争和同步问题。
- 稳定性问题:一个线程的崩溃可能影响到整个进程,导致所有线程都停止运行。
创建进程的方式?
- 系统初始化,像后台进程,守护进程
- 一个进程开启另个进程:(如 nginx开启多进程,os. fork, subprocess Popen等)
- 用户的交互式请求:鼠标点击
- —个批处理作业的初始化:(只在大型机的批处理系统中应用)无论哪-种,新进程的创建都是由—个已 经存在的进程执行了—个用于创建进程的系统调用而创建的
系统初始化、一个进程开启另一个、用户交互式请求
进程有几种状态?
有五种状态:创建、就绪、运行、阻塞、终止
创建状态:一个应用程序从系统上启动,首先就是进入创建状态,需要获取系统资源创建进程管理块(PCB: Process Control Block)完成资源分配。
就绪状态:在创建状态完成之后,进程已经准备好,但是还未获得处理器资源,无法运行。
运行状态:获取处理器资源,被系统调度,开始进入运行状态。如果进程的时间片用完了就进入就绪状态。
阻塞状态:在运行状态期间,如果进行了阻塞的操作,如耗时的I/O操作,此时进程暂时无法操作就进入到了阻塞状态,在这些操作完成后就进入就绪状态。
终止状态:进程结束或者被系统终止,进入终止状态
创建、就绪、运行、阻塞、终止
进程间通信方式有哪些?
1.管道(pipe)
2.信号量(semophore)
3.消息队列(messge queue)
4.信号(signal)
5.共享内存(Shared memory)
6.套接字(socket)
- 管道(Pipe):管道是一种半双工的通信方式,数据只能单向流动。它通常用于具有亲缘关系的进程间的通信,如父子进程或兄弟进程。管道可以被视为一种特殊的文件,可以使用普通的读写函数进行数据的读写操作。在Linux中,管道分为无名管道和命名管道,其中命名管道允许无亲缘关系的进程间进行通信。
- 消息队列(Message Queue):消息队列是一种通过操作系统内核来传递消息的机制。进程可以将消息发送到消息队列中,其他进程则可以从消息队列中接收这些消息。消息队列在传递消息时具有独立性,即不依赖于发送进程和接收进程的存在。
- 共享内存(Shared Memory):共享内存允许多个进程访问同一块内存区域,从而实现数据的共享。这种方式通常需要使用同步机制,如信号量或互斥锁,来确保对共享内存的正确访问。
- 信号量(Semaphore):信号量是一个计数器,主要用于控制多个进程对共享资源的访问。它可以看作是一种锁机制,当进程想要访问共享资源时,需要先获取信号量;当访问完成后,再释放信号量。这样可以防止多个进程同时访问共享资源,造成数据冲突或不一致。
- 套接字(Socket):套接字是一种通过网络进行进程通信的方式,常用于不同主机之间的进程通信。套接字通信可以通过TCP或UDP协议来实现,具有跨平台、可靠性高等特点。
注意:信号量、信号、消息队列可以用来同步
进程间通信的6种方式 - 敲什么敲 - 博客园 (cnblogs.com)
各通信方式的比较和优缺点
管道:简单、有序,单向、速度慢,容量有限,只有父子进程能通讯。
FIFO:任何进程间都能通讯,但速度慢。
消息队列: 容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题,消息队列可以不再 局限于父子进程,而允许任意进程通过共享消息队列来实现进程间通信,并由系统调用函数来实现消息 发送和接收之间的同步,从而使得用户在使用消息缓冲进行通信时不再需要考虑同步问题,使用方便, 但是信息的复制需要额外消耗CPU的时间,不适宜于信息量大或操作频繁的场合。此种方法不太常用。
速度快、支持异步、开销低,同步方式工作,一旦有请求到达不能马上返回
信号量:不能用来传递复杂消息,只能用来同步。
共享内存:利用内存缓冲区直接交换信息,无须复制,快捷、信息量大是其优点。共享内存块提供了在任意数量的 进程之间进行高效双向通信的机制。每个使用者都可以读取写入数据,但是所有程序之间必须达成并遵 守一定的协议,以防止诸如在读取信息之前覆写内存空间等竞争状态的出现。
最高效的方式,直接访问内存,没有提供同步的机制
进程间通信方式的选择
PIPE和FIFO(有名管道)用来实现进程间相互发送非常短小的、频率很高的消息,这两种方式通常适用于 两个进程间的通信。
共享内存用来实现进程间共享的、非常庞大的、读写操作频率很高的数据;这种方法适用于多进程间的 通信。
其他考虑用socket。主要应用在分布式开发中。
!线程间通信方法有哪些?
1、 共享内存
多个线程共享同一块内存区域,通过读写共享内存来实现信息交流和数据共享。需要考虑线程安全问题,可以使用互斥锁、信号量等机制来保证数据的一致性。
2、信号量
通过信号量来实现线程之间的同步和互斥。通过P操作和V操作来改变信号量的值,当信号量的值为0时,线程需要等待;当信号量的值大于0时,线程可以继续执行。
3、互斥锁
通过互斥锁来实现线程之间的互斥访问共享资源。当一个线程获取到互斥锁时,其他线程需要等待;当一个线程释放互斥锁时,其他线程可以竞争获取锁。
4、条件变量
通过条件变量来实现线程之间的等待和唤醒。当线程需要等待某个条件满足时,可以调用条件变量的等待函数使自己进入等待状态;当条件满足时,可以调用条件变量的唤醒函数唤醒等待的线程。
5、管道
通过管道来实现线程之间的通信。一个线程可以将数据写入管道,另一个线程可以从管道中读取数据。需要注意管道的大小限制和线程安全问题。
区别:线程和进程之间的通信方式
线程:临界区、互斥量、信号量、事件、条件变量、读写锁
进程:管道、信号量、信号、消息队列、共享内存、socket
线程间同步方式
临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意 时刻只允许一个线程访问共享资源,如果有多个线程试图访问共享资源,那么当有一个线程进入 后,其他试图访问共享资源的线程将会被挂起,并一直等到进入临界区的线程离开,临界在被释放 后,其他线程才可以抢占。
互斥量:为协调对一个共享资源的单独访问而设计,只有拥有互斥量的线程,才有权限去访问系统 的公共资源,因为互斥量只有一个,所以能够保证资源不会同时被多个线程访问。互斥不仅能实现 同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。
信号量:为控制一个具有有限数量的用户资源而设计。它允许多个线程在同一个时刻去访问同一个 资源,但一般需要限制同一时刻访问此资源的最大线程数目
事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
什么是内核线程和用户线程?
用户线程:是由用户进行管理,用户线程的创建、调度、同步和销毁全由库函数在用户空间完成,不需要内核的帮助,这种线程开销是比较小的。
内核线程:由操作系统创建和销毁。内核维护进程及线程的上下文信息以及线程切换。一个内核线程由于I/O操作而阻塞,不会影响其它线程的运行。
内核线程和用户线程的区别
- 内核支持线程是内核可感知的,而用户线程是内核不可感知的
- 用户线程的创建、撤销、调度不需要内核的支持,在语言一级处理;而内核线程都需要内核支持,与进程的创建、撤销、调度大体相同
- 用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时, 只导致该线程被中断。
- 在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由 用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线 程调度程序负责线程的调度
- 用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何 状态下的程序
内核线程和用户线程有什么优缺点
内核线程的优点: 1. 当有多个处理机时,一个进程的多个线程可以同时执行。
缺点: 1. 由内核进行调度。
用户线程的优点:
- 线程的调度不需要内核直接参与,控制简单。
- 可以在不支持线程的操作系统中实现。
- 创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多。
- 允许每个进程定制自己的调度算法,线程管理比较灵活。这就是必须自己写管理程序,与内核线程 的区别
- 线程能够利用的表空间和堆栈空间比内核级线程多。
- 同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都 会被挂起。另外,页面失效也会产生同样的问题。
缺点: 4. 资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用
什么是僵尸进程、孤儿进程、守护进程
僵尸进程:一个进程使用fork出一个子进程,如果子进程退出,但是父进程没有使用wait或者waitpid函数回收子进程的资源,那么该进程就是僵尸进程。(fork出的子进程没有回收资源)
孤儿进程:是指父进程异常退出,而子进程还没退出,那么子进程就会被1号进程(init)收养。(父进程异常退出,被一号进程收养)
守护进程:是指后台进程,有意把父进程先结束,然后被1号进程收养。(有意把父进程退出)
僵尸进程:(fork出的子进程没有回收资源)
守护进程:(父进程异常退出,被一号进程收养)
孤儿进程:(有意把父进程退出,被一号进程收养)
僵尸进程有什么危害
在进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留 一定的信息(包括进程号 PID,退出状态 the termination status of the process,运行时间 the amount of CPU time taken by the process 等)。直到父进程通过 wait / waitpid 来取时才释放。 如果进程不调用 wait / waitpid 的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但 是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不 能产 生新的进程
如何清理僵尸进程
僵尸进程的产生是因为父进程没有 wait() 子进程。所以如果我们自己写程序的话一定要在父进程中通过 wait() 来避免僵尸进程的产生。 当系统中出现了僵尸进程时,我们是无法通过 kill 命令把它清除掉的。但是我们可以杀死它的父进程, 让它变成孤儿进程,并进一步被系统中管理孤儿进程的进程收养并清理。
僵尸进程是由没有执行wait等回收操作而发生,最好是杀死父进程,交给一号进程收养并清理
如何实现守护进程?
- 创建子进程,终止父进程;
- 调用setsid创建一个新会话;
- 将当前目录更改为根目录;
- 重设文件权限掩码,文件权限掩码是指屏蔽掉文件权限的对应位;
- 关闭不再需要的文件描述符。
如何确定当前线程是繁忙还是阻塞
使用ps命令查看
空闲的进程和阻塞的进程状态会不会在唤醒的时候误判
不会。每个进程有个进程控制块PCB,两种状态的进程分别处于两种队列。唤醒应该是找阻塞队列的进 程。
请问就绪状态的进程在等待什么
被调度使用cpu的运行权
请你回答一下fork和vfork的区别
- fork( )的子进程拷贝父进程的数据段和代码段;vfork( )的子进程与父进程共享数据段
- fork( )的父子进程的执行次序不确定;vfork( )保证子进程先运行,在调用exec或exit之前与父进程 数据是共享的,在它调用exec或exit之后父进程才可能被调度运行。
- vfork( )保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个 函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
- 当需要改变共享数据段中变量的值,则拷贝父进程
父进程、子进程的关系以及区别
fork之后:
父子相同处: 全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式…
父子不同处: 1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集
似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但pid不同。真的每fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后在映射至物理内存吗?
当然不是!父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
1、fork函数时调用一次,返回两次。在父进程和子进程中各调用一次。子进程中返回值为0,父进程中返回值为子进程的PID。程序员可以根据返回值的不同让父进程和子进程执行不同的代码。子进程是父进程的副本,获得了父进程数据空间、堆和栈的副本;父子进程并不共享这些存储空间,共享正文段(即代码段);因此子进程对变量的所做的改变并不会影响父进程。一般来说,fork之后父、子进程执行顺序是不确定的,这取决于内核调度算法。进程之间实现同步需要进行进程通信。
一个进程可以创建多少线程,和什么有关
进程的虚拟内存空间上限,因为创建一个线程,操作系统需要为其分配一个栈空间,如果线程数量越多,所需的栈空间就要越大,那么虚拟内存就会占用的越多。
系统参数限制,虽然 Linux 并没有内核参数来控制单个进程创建的最大线程个数,但是有系统级别的参数来控制整个系统的最大线程个数。
在1G内存的计算机中能否malloc(1.2G)?为什么?
是有可能申请1.2G的内存的。
回答这个问题前需要知道malloc的作用和原理,应用程序通过malloc函数可以向程序的虚拟空间申请一块虚拟地址空间,与物理内存没有直接关系,得到的是在虚拟地址空间中的地址,之后程序运行所提供的物理内存是由操作系统完成的。
堆与栈
什么是代码段,数据段,bss段,堆,栈
代码段:代码段通常用来存放程序执行代码的一块区域。这部分区域的大小在程序运行前就已经确定 了,通常这块内存区域属于只读,有些架构也允许可写,在代码段中也有可能包含以下只读的常数变量,例如字符串常量等。程序段为程序代码在内存中映射一个程序可以在内存中有多个副本。
数据段:数据段通常用来存放程序中已初始化的全局变量和已初始化为非0的静态变量的一块内存区域,属于静态内存分配。直观理解就是C语言程序中的全局变量(注意:全局变量才算是程序的数据, 局部变量不算程序的数据,只能算是函数的数据)
BSS段:bss段通常是指用来存放程序中未初始化的全局变量和未初始化的静态变量或者初始化为0的静态变量一块区域。bss英文Block started by symbol,bss属于静态内存分配。bss段的特点就是被初始 化为0,bss段本质上也是属于数据段,bss段就是被初始化为0的数据段。 注意区分。
数据段(.data)和bss段的区别和联系:二者本来没有本质区别,都是用来存放C程序中的全局变 量的。区别在于把显示初始化为非零的全局变量存在.data段中,而把显式初始化为0或者并未显式 初始化(C语言规定未显式初始化的全局变量值默认为0)的全局变量存在bss段。
堆:堆是用来存放进程中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用 malloc等函数分配内存时,新分配的内存就被动态分配到堆上,当利用free等函数释放内存时,被释放 的内存从堆中被剔除。
栈:栈又称堆栈,是用户存放程序临时创建的变量,也就是我们函数{}中定义的变量,但不包括static声明的变量,static意味着在数据段中存放变量。除此之外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中,由于栈的先进后出特点,所以栈特别方便用来保存、恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存,交换临时数据的 内存区。
堆:存放被动态分配的内存段
栈:存放程序临时创建的变量
为什么堆的空间是不连续的
堆包含一个链表来维护已用和空闲的内存块。在堆上新分配(用 new 或者 malloc)内存是从空闲的内 存块中找到一些满足要求的合适块。所以可能让人觉得只要有很多不连续的零散的小区域,只要总数达 到申请的内存块,就可以分配。
但事实上是不行的,这又让人觉得是不是零散的内存块不能连接成一个大的空间,而必须要一整块连续 的内存空间才能申请成功呢。
申请和释放许多小的块可能会产生如下状态:在已用块之间存在很多小的空闲块。进而申请大块内存失 败,虽然空闲块的总和足够,但是空闲的小块是零散的,不连续的,不能满足申请的大小,这叫做“堆碎 片”。
当旁边有空闲块的已用块被释放时,新的空闲块会与相连的空闲块合并成一个大的空闲块,这样就可以 有效的减少"堆碎片"的产生。
堆分配的空间在逻辑地址(虚拟地址)上是连续的,但在物理地址上是不连续的(因为采用了页式内存 管理,windows下有段机制、分页机制),如果逻辑地址空间上已经没有一段连续且足够大的空间,则分 配内存失败。
用户栈和内核栈
内核栈
内存中属于操作系统空间的一块区域。
作用
- 保存中断现场,对于嵌套中断,被中断程序的现场信息一次压入系统栈,中断返回时逆序弹出。
- 保存操作系统程序相互调用的参数,返回值,以及函数的局部变量。
用户栈
用户进程空间的一块区域,用于保存用户空间子程序间调用的参数,返回值以及局部变量。
用户栈和内核栈,为什么不能共用一个栈
- 如果只用系统栈,系统栈一般大小有限,用户程序调用次数可能很多。如果中断有16个优先级,那 么系统栈一般大小为15(只需保存15个低优先级中断,另一个高优先级中断在运行)用户程序调用 次数很多,那样15次子程序调用以后的子程序的参数,返回值,局部变量就不能保存,用户程序也 就不能正常运行。
- 如果只用用户栈,系统程序需要在某种保护下运行,而用户栈在用户空间不能提供相应的保护措 施。
解耦的思想
用户程序调用次数多,系统栈一般大小有限
系统栈需要在某种保护,用户栈无法满足
线程是否具有相同的堆栈
真正的程序执行都是线程来完成的,程序启动的时候操作系统就帮你创建了一个主线程。 每个线程有自己的堆栈。
每个线程都有自己独立的执行路径和执行环境,因此需要有自己的堆栈来管理这些执行时的状态信息。如果多个线程共用同一个堆栈,就会导致互相干扰,无法正确地管理各自的执行状态,从而造成严重的并发问题。
有抢占式和非抢占式的区别
非抢占式优先权算法:在这种方式下,系统一旦把处理机分配给就绪队列中优先权最高的进程后,该进程便一直执行下去,直至完成。
抢占式优先权调度算法:在这种方式下,系统同样是把处理机分配给优先权最高的进程,使之执行。但在其执行期间,只要又出现了另一个其优先权更高的进程,进程调度程序就立即停止当前进程(原优先权最高的进程)的执行,重新将处理机分配给新到的优先权最高的进程。
字母意思,抢占可以使高优先级先执行
并发和并行
并发:是对于单个cpu来说,在一个时刻只能一个进程运行,但是线程的切换时间则是减少到纳秒数量级,多个任务不停的来回切换。
并行:是对于多个CPU来说,多个进程可以同时运行。
顾名思义,「并发」强调的是可以一起「出『发』」,「并行」强调的是可以一起「执『行』」。
区别:
并行的"同时"是同一时刻可以多个任务在运行(处于running),并发的"同时"是经过不同线程快速切换。
驱动里面为什么要有并发、互斥的控制?如何实现?讲个例子?
并发,指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态。
解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问就是指一个执行单元在访问共享资源 的时候,其他的执行单元都被禁止访问。
访问共享资源的代码区域被称为临界区,临界区需要以某种互斥机制加以保护。中断屏蔽,原子操作, 自旋锁,和信号量都是linux设备驱动中可采用的互斥途径。
进程间的通信中的管道通信实现原理是什么
管道通信是一种简单有效的进程间通信方式,通过内核缓冲区实现数据的传递。父子进程或兄弟进程可以通过管道进行数据交换,实现进程间的通信。管道通信的实现原理基于文件描述符的传递和内核缓冲区的存储,确保数据的安全传输和顺序读取。
基于文件描述符的传递和内核缓冲区的存储
自旋锁是什么?信号量是什么?二者有什么区别?
自旋锁:---->不会进入睡眠
自旋锁只有两个状态,锁定和解锁,在锁定期间其他进程我是不能进行访问资源,比如B想访问只能在外面等到A解锁之后才能访问,如果B等待的时间过长那么就要考虑使用互斥锁。
信号量:------>会进入睡眠
信号量是个计数器,用来统计资源的可用次数,比如B进程想使用资源,当资源可用的是时候就会去通知B,而不是让B在哪里等着,不会一直占用CPU,这样就可以提高系统的执行效率。
自旋锁是一种互斥锁的实现方式而已,相比一般的互斥锁会在等待期间放弃cpu,自旋锁(spinlock)则是**不断循环(自旋的含义)**并测试锁的状态,这样就一直占着cpu。
互斥锁:用于保护临界区,确保同一时间只有一个线程访问数据。对共享资源的访问,先对互斥量进行加锁,如果互斥量已经上锁,调用线程会阻塞,直到互斥量被解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。
区别:
- 信号量会让等待信号的的进程进入睡眠,所以信号量适用于锁会被长时间持有的情况。
- 相反,锁被短时间持有时,使用信号量就不太适宜了,因为睡眠引起的耗时可能比锁被占用的全部 时间还要长。
- 自旋锁会一直在那里循环判断锁是否可用,占用极高CPU所以不适用长时间持有所的情况。
- 自旋锁禁止处理器抢占,信号量允许,这也就是为什么自旋锁不能睡眠的原因,如果自旋锁睡眠,那么就无法通过抢占唤醒睡眠的自旋锁。
- 由于执行线程在锁被争用时会睡眠,所以只能在进程上下文中才能获取信号量锁,因为在中断上下 文中(使用自旋锁)是不能进行调度的。
互斥锁是一把公共厕所的钥匙。一个人使用厕所的时候可以拿到这把钥匙,用完之后把这把钥匙交给排队的下一个人。
信号量是没有人使用的厕所的钥匙数量,所有厕所的钥匙都一样。比如有4个厕所有相同的钥匙和锁。信号量的值就是钥匙的数量,一开始是4。当进来一个人的时候数量就是少一个,如果4个厕所都满了,信号量就成0了,出去一个人就增加1,并把钥匙交给排队的下一个人。
信号量(semaphore)和互斥锁(mutex)的区别 - 天涯海角路 - 博客园 (cnblogs.com)
自旋锁和信号量可以睡眠吗?为什么?
自旋锁不能睡眠,信号量可以。
原因
自旋锁禁止处理器抢占;而信号量不禁止处理器抢占。
基于这个原因,如果自旋锁在锁住以后进入睡眠,由于不能进行处理器抢占,其他系统进程将都不能获得CPU而运行,因此不能唤醒睡眠的自旋锁,因此系统将不响应任何操作(除了中断或多核的情况,下面会讨论)。而信号量在临界区睡眠后,其他进程可以用抢占的方式继续运行,从而可以实现内存拷贝等功能而使得睡眠的信号量程序由于获得了等待的资源而被唤醒,从而恢复了正常的代码运行。 当然,自旋锁的睡眠的情况包含考虑多核CPU和中断的因素。自旋锁睡眠时,只是当前CPU的睡眠以及 当前CPU的禁止处理器抢占,所以,如果存在多个CPU,那么其他活动的CPU可以继续运行使操作系统 功能正常,并有可能完成相应工作而唤醒睡眠了的自旋锁,从而没有造成系统死机;自旋锁睡眠时,如果允许中断处理,那么中断的代码是可以正常运行的,但是中断通常不会唤醒睡眠的自旋锁,因此系统仍然运行不正常。
一个大公厕,若干个位置,外面有个牌子”已满“和”可用“,进去一个人翻成”已满“,出去的翻成”可用“, 信号量之所以翻译成“信号”,还是有道理的,因为它(厕所门口的牌子)标示的是资源(厕所空位)的状态,而互斥锁就是锁,它实实在在地锁住了资源。
自旋锁和信号量可以用于中断中吗
信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
自旋锁可以用于中断。在获取锁之前一定要先禁止本地中断(也就是本CPU中断,对于多核SOC来说会 有多个CPU核),否则可能导致锁死现象的发生。
读写锁是什么
当临界区的一个文件可以被同时读取,但是并不能被同时读和写。如果一个线程在读,另一个线程在写,那么很可能会读取到错误的不完整的数据。读写自旋锁是可以允许对临界区的共享资源进行并发读 操作的。但是并不允许多个线程并发读写操作。
不能同同时读写,可以同时读,不能同时写
说说什么是死锁,产生的条件,如何解决?
死锁:是指多个进程在执行过程中,因争夺资源而造成互相等待,此时系统产生了死锁。
本质原因:
- 系统资源有限
- 进程推荐顺序不合理
产生条件:
- **互斥条件:**进程对所分配的资源不允许其他进程访问,若其他进程需要访问,只能等待,知道该进程使用完毕后释放资源。
- 请求保持条件:进程获得一定资源后,有对其他资源发出请求,但该资源被其他进程占用,此时请求阻塞,而且这个进程不会释放自己已经占有的资源。
- **不可剥夺条件:**进程获得资源,只能自己释放,不可剥夺。
- 环路等待条件:若干进程之间形成一种头尾相接等待资源关系
解决:
- 资源一次性分配,从而解决请求保持的问题
- 可剥夺资源:当进程新的资源未得到满足时,释放已有的资源;
- 资源有序分配:资源按序号递增,进程请求按递增请求,释放则相反
互斥条件、不可剥夺条件、请求保持、环路等待
某系统中有3个并发进程竞争资源R,每个进程都需要5个R,那么至少有(22)个R,才能保证系统不会发生死锁。
假设系统为每个进程分配了4个资源,系统剩余数为0,导致这3个进程互相都要求对方占用的资源无法继续运行,产生死锁。对于13系统为每个进程分配了4个资源,还剩余1个,能保证3个进程中的一个进程运行完毕。
死锁的处理方式
预防死锁、避免死锁、检测与解除死锁
- 预防死锁
- 资源一次性分配
- 可剥夺资源
- 资源有序分配
- 避免死锁
- 由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则, 进程等待。其中最具有代表性的避免死锁算法是银行家算法
- 检测死锁
- 首先为每个进程和每个资源指定一个唯一的号码;然后建立资源分配表和进程等待表
- 解除死锁
- 当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
- 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
- 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除 为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。
- 当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
单核机械上写多线程程序,是否考虑加锁,为什么?
需要。
因为线程锁主要是用来实现线程的同步和通信,在抢占是操作系统中,通常为每个线程分配一个时间片,当某个线程时间片耗尽时,操作系统会将其挂起,然后运行另一个线程。如果这两个线程共享某些数据,不使用线程锁的前提下,可能会导致共享数据修改引起冲突。
!互斥锁机制,互斥锁和读写的区别?
**互斥锁机制:**mutex,用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒。
互斥锁和读写锁:
(1)读写锁区分读者和写者,而互斥锁不区分;
(2)互斥锁同一时间只允许一个线程访问该对象,无论读写;读写锁同一时间内只允许一个写者,但是允许多个读者同时读对象;
函数acquire()获取锁,而函数release()释放锁
区分互斥锁和读写锁
介绍一下几种典型的锁?
读写锁
- 多个读者可以同时进行读。
- 写者必须互斥(只允许一个写者写,也不能读者写者同时进行)。
- 写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)。
互斥锁
- 一次只能一个线程拥有互斥锁,其他线程只有等待。
- 互斥锁是在抢锁失败的情况下主动放弃CPU进入睡眠状态直到锁的状态改变时再唤醒,而操作系统负责线程调度,为了实现锁的状态发生改变时唤醒阻塞的线程或者进程,需要把锁交给操作系统管理,所以互斥锁在加锁操作时涉及上下文的切换。互斥锁实际的效率还是可以让人接受的,加锁的时间大概100ns左右,而实际上互斥锁的一种可能的实现是先自旋一段时间,当自旋的时间超过阀值之后再将线程投入睡眠中,因此在并发运算中使用互斥锁(每次占用锁的时间很短)。
自旋锁
如果进线程无法取得锁,进线程不会立刻放弃CPU时间片,而是一直循环尝试获取锁,直到获取为止。如果别的线程长时期占有锁,那么自旋就是在浪费CPU做无用功,但是自旋锁一般应用于加锁时间很短的场景,这个时候效率比较高。
条件变量
互斥锁一个明显的缺点是他只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个 线程发送信号的方法弥补了互斥锁的不足,他常和互斥锁一起使用,以免出现竞态条件。当条件不满足 时,线程往往解开相应的互斥锁并阻塞线程然后等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。总的来说互斥锁是线程间互****斥的机制,条件变量则是同步机制。
说说什么是信号量,有什么作用?
由于信号量只能进行两种操作,等待和发送信号,即P(sv)和V(sv),
(1)P(sv)操作:如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行(信号量的值为正,进程获得该资源的使用权,进程将信号量减1,表示它使用了一个资源单位)。
(2)V(sv)操作:如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1(若此时信号量的值为0,则进程进入挂起状态,直到信号量的值大于0,若进程被唤醒则返回至第一步)。
信号量(Semaphore)是一种同步原语,通常用于控制多个线程对共享资源的访问。信号量本质上是一个非负的整数计数器,用来表示可用资源的数量。线程可以通过对信号量进行P(等待)和V(信号)操作来实现对共享资源的互斥访问和同步。
作用:用于多进程对共享数据对象的读取,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。
同步和互斥
同步和互斥是并发编程中的两个重要概念,它们用于控制多个线程或进程对共享资源的访问,以确保数据的完整性和系统的一致性。
**互斥(Mutex)**主要指的是某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。也就是说,**当一个线程或进程正在访问某个共享资源时,其他线程或进程无法同时访问该资源。**这样可以防止多个线程或进程同时修改数据,导致数据的不一致和冲突。
而同步(Synchronization)则是在互斥的基础上(在大多数情况),通过其他机制实现访问者对资源的有序访问。同步的目的不仅仅是防止冲突,更重要的是确保线程或进程按照预定的顺序和速度执行,以实现更为复杂的协调和控制。例如,在进程同步中,异步环境下的一组并发进程因直接制约而互相发送消息,进行互相合作、互相等待,使得各自进程按一定的速度执行。
总的来说,互斥和同步都是用于管理并发访问共享资源的机制,但它们的侧重点不同。互斥主要关注资源的唯一性和排他性访问,而同步则更注重线程或进程的有序访问和执行顺序。在实际应用中,需要根据具体的并发需求和资源访问模式来选择合适的同步和互斥机制。
重点:资源数量有限和程序执行顺序,也就是导致死锁的原因
线程的同步和互斥的区别和联系
互斥是指一个资源只能出现一个进程进行访问,但是互斥没办法按照顺序执行,是无序的。
同步是指按照一定顺序执行,同步里面包含了互斥;
进程、线程的中断切换的过程是怎样的?
- 进程中断切换过程:
- 当发生中断事件时,CPU会保存当前进程的上下文信息(包括程序计数器、寄存器状态、栈指针等)到进程控制块(PCB)中。
- CPU会根据中断类型跳转到相应的中断处理程序(中断服务例程)执行,处理完中断后,会根据调度算法选择下一个要执行的进程。
- CPU会加载下一个进程的上下文信息(从其对应的PCB中恢复),并开始执行该进程的代码。
- 线程中断切换过程:
- 线程的中断切换与进程类似,不同之处在于线程共享同一进程的地址空间和资源,因此线程切换时不需要切换地址空间。
- 当发生中断事件时,CPU会保存当前线程的上下文信息到线程控制块(TCB)中。
- CPU会根据中断类型跳转到相应的中断处理程序执行,处理完中断后,会根据调度算法选择下一个要执行的线程。
- CPU会加载下一个线程的上下文信息(从其对应的TCB中恢复),并开始执行该线程的代码。
进程与线程
进程
概念: 程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位
特征: 动态性 并发性 独立性 制约性
结构: 程序块,数据块,进程控制块(PCB)
基本状态: 就绪态 运行态 阻塞态
进程上下文: 操作系统把进程物理实体和支持进程运行的环境称为进程上下文
进程切换步骤: ①保护被中断进程的处理器现场信息
②修改被中断进程的进程控制块有关信息,如进程状态等
③把被中断进程的进程控制块加入有关队列
④选择下一个占有处理器运行的进程
⑤根据被选中进程设置操作系统用到的地址转换和存储保护信息
⑥根据被选中进程恢复处理器现场
进程的控制和管理: 创建,撤销,阻塞,唤醒,挂起,激活
线程
概念: 操作系统进程中能够并发执行的实体,是处理器调度和分派的基本单位
目的: 为了减少并发执行时所付出的时空开销,使得并发粒度更细,并发性更好
实现: ①用户级线程:线程管理由用户实现,内核不知道用户级线程
②内核级线程:线程管理由内核实现,开销大
③混合级线程:既支持用户级线程,又支持内核级线程
处理机调度:
①高级调度:作业调度,将外存上处于后备状态的作业调入内存,并分配资源创建进程
②中级调度:涉及进程在内外存的切换
③低级调度:进程调度,从就绪队列中选择一个进程获得cpu时间
多线程和单线程有什么区别,多线程编程要注意什么,多线程加锁需要注意什么?
区别:
- 单线程:程序按照顺序执行,一次只能执行一个任务。当某个任务需要等待I/O操作或者其他耗时操作时,整个程序会停止执行,直到该任务完成。
- 多线程:程序可以同时执行多个任务,每个任务对应一个线程。多线程可以实现并发执行,提高程序的响应速度和效率。
注意事项:
- 线程同步:多线程编程需要特别注意线程同步,即协调不同线程访问共享资源的问题。如果多个线程同时读写共享数据,可能导致数据不一致或冲突。可以使用锁、信号量等机制来进行线程同步。
- 竞态条件(Race Condition):当多个线程同时竞争访问和修改共享数据时,可能会出现竞态条件。为避免竞态条件,需要使用互斥锁、条件变量等机制来保护共享资源的访问。
- 死锁:在多线程编程中,如果多个线程相互等待对方释放资源,而导致程序无法继续执行,就会发生死锁。避免死锁的方法包括避免循环等待、按序申请资源等。
- 资源消耗:多线程编程需要更多的系统资源,包括内存和处理器资源。因此,在设计多线程程序时需要考虑系统的资源限制,并合理利用线程池等技术来管理线程。
多线程加锁注意事项:
-
- 锁粒度:锁的粒度应尽量小,只保护必要的共享资源,以减少锁的竞争和影响程序性能。
- 死锁风险:过多的锁使用可能导致死锁问题,因此需要仔细设计锁的获取和释放顺序,避免出现循环等待的情况。
- 锁的性能:锁的过多使用也会影响程序的性能,因为获取和释放锁会引入额外的开销。因此,需要权衡锁的使用频率和性能之间的平衡。
单线程一个任务,多线程同时多个任务并发;需要注意线程同步,死锁,资源消耗等问题;
为什么要创建线程池?线程池的设计思路,线程池中线程的数量由什么确定?
原因:
创建和销毁线程的花销是比较大的,这些时间可能比处理业务时间还要长,这样频繁的创建和销毁线程,再加上业务工作的线程,消耗系统资源的时间,可能导致系统资源不足,线程池可以提升系统效率。
实现线程池步骤:
- 设置一个生产者消费者队列,作为临时资源;
- 初始化n个线程,并让其运行起来,加锁去队列里去任务运行;
- 当任务队列为空时,所有线程阻塞;
- 当生产者队列来了一个任务后,先对队列加锁,把任务挂到队列上,然后使用条件变量通知阻塞中的线程来处理。
线程的数量:
与CPU、IO/并发、并行有关。
如果是CPU密集型应用,则线程池大小设置为:CPU数目+1;
如果是IO密集型应用,则线程池大小设置为:2*CPU数目+1;
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目;
硬链接与软链接
链接操作实际上是给系统中已有的某个文件指定另外一个可用于访问它的名称。对于这个新的文件名,我们可以为之指定不同的访问权限,以控制对信息的共享和安全性的问题。如果链接指向目录,用户就可以利用该链接直接进入被链接的目录而不用打一大堆的路径名。而且,即使我们删除这个链接,也不会破坏原来的目录。
1>硬链接
硬链接只能引用同一文件系统中的文件。**它引用的是文件在文件系统中的物理索引(也称为inode)。**当您移动或删除原始文件时,硬链接不会被破坏,因为它所引用的是文件的物理数据而不是文件在文件结构中的位置。硬链接的文件不需要用户有访问原始文件的权限,也不会显示原始文件的位置,这样有助于文件的安全。如果您删除的文件有相应的硬链接,那么这个文件依然会保留,直到所有对它的引用都被删除。
2>软链接(符号链接)
软连接,其实就是新建立一个文件,这个文件就是专门用来指向别的文件的。软连接产生的是一个新的文件,但这个文件的作用就是专门指向某个文件的,删了这个软连接文件,那就等于不需要这个连接,和原来的存在的实体原文件没有任何关系,但删除原来的文件,则相应的软连接不可用。
一句话总结:硬链接是复制,软链接是快捷方式
什么是页表,为什么要有?
页表是一种特殊的数据结构,用于计算机操作系统中的虚拟内存系统,其存储了虚拟地址到物理地址间的映射。
每一个进程都拥有一个自己的页表,PCB表中有指针指向页表。在页式存储管理中,主存被分成大小相等的若干块,同时程序逻辑地址也分成与块大小一致的若干页。这样,作业的信息就可以按页面为单位放入主存,并且可以不连续存放。为了在作业执行过程中准确地查找逻辑地址与绝对地址的对应关系,就需要为每个作业建立一张页表,表示逻辑地址中的页号与主存中块号的对应关系。
页表的主要作用是进行地址变换。**当要按给定的逻辑地址访问主存时,需要先访问页表,计算出绝对地址。**这种机制使得程序可以认为自己拥有连续的内存(一个连续完整的地址空间),尽管物理内存可能是分散的,甚至部分数据可能暂时存储在外部磁盘上。这种技术使得大型程序编写更容易,对真正的物理内存使用也更有效。
此外,为了提高查找速度和指令执行速度,系统通常会设置一个高速缓冲寄存器,将页表中的一部分内容(即快表)存放进去。当访问主存时,快表和主存会同时进行查找,由于快表存放的是经常使用的页表内容,因此可以大大加快查找速度。
总的来说,页表是计算机操作系统中管理虚拟内存和物理内存之间映射关系的重要数据结构,它使得程序能够以一种连续的内存视图来运行,同时优化了内存的使用和访问效率。
简述操作系统中malloc的实现原理
malloc()
是 C 语言中用于动态内存分配的函数,它允许程序在运行时动态申请内存。在操作系统中,malloc()
函数的实现通常是基于堆(Heap)管理的。下面是 malloc()
的简单实现原理:
-
初始化堆: 首先,操作系统会在进程的地址空间中分配一块内存区域作为堆,用于存储动态分配的内存块。通常,堆的大小是可调整的,可以根据程序的需求进行动态扩展或收缩。
-
分配内存: 当程序调用
malloc(size)
函数时,操作系统会在堆中找到一块足够大的空闲内存块,然后将其分配给程序,并返回内存块的起始地址。为了管理已分配和未分配的内存块,通常使用一种数据结构(如链表或二叉树)来记录堆中每个内存块的状态(已分配或未分配)和大小。 -
内存对齐: 分配的内存块通常会按照特定的对齐方式对齐,以提高内存访问的效率和性能。对齐的方式可以是字节对齐、字对齐或其他自定义的对齐方式。
-
释放内存: 当程序调用
free(ptr)
函数释放先前分配的内存块时,操作系统会将该内存块标记为未分配状态,并将其添加到空闲内存块列表中,以便后续的malloc()
调用可以重新使用该内存块。释放内存后,程序应该避免继续使用已释放的内存块,以防止内存泄漏和悬空指针。 -
内存管理策略: 操作系统可能会采用不同的内存管理策略来优化内存分配和释放的性能,如首次适配、最佳适配、最坏适配等。这些策略主要影响了堆中内存块的分配和释放效率,以及堆的碎片化程度。
总的来说,malloc()
函数的实现原理涉及到堆的管理、内存分配、对齐方式、内存释放和内存管理策略等方面,这些都是操作系统中内存管理的重要内容。
?简述操作系统中的缺页中断。
在操作系统中,缺页中断(Page Fault)是一种硬件异常,发生在程序访问虚拟内存时,但所需的页面未加载到物理内存中。当程序试图访问某个虚拟地址对应的页面时,如果该页面尚未加载到物理内存中,则会触发缺页中断,操作系统会通过处理缺页中断来将所需页面加载到物理内存中,并更新页表。
以下是缺页中断的简要过程:
-
访问虚拟内存: 当程序执行时,如果试图访问某个虚拟地址对应的数据或指令时,操作系统会检查该虚拟地址是否已经映射到物理内存中的某个页面。
-
缺页中断触发: 如果虚拟地址对应的页面未加载到物理内存中,则会触发缺页中断。此时,操作系统暂停程序的执行,并将控制权转移到缺页中断处理程序(Page Fault Handler)。
-
处理缺页中断: 缺页中断处理程序负责将所需页面从磁盘或其他存储介质加载到物理内存中的空闲页面框中。如果物理内存中没有空闲页面框,则操作系统需要选择一个页面进行替换,通常会采用页面置换算法(如最近最少使用算法或先进先出算法)来选择要替换的页面。
-
更新页表: 加载页面到物理内存后,操作系统更新页表,将虚拟地址与物理地址的映射关系写入页表中。这样,下次程序访问同样的虚拟地址时,就可以直接在物理内存中找到对应的数据或指令,而无需再次触发缺页中断。
-
恢复程序执行: 缺页中断处理程序完成页面加载和页表更新后,将控制权返回给程序,并重新执行导致缺页中断的指令。程序可以继续正常执行,访问所需的数据或指令。
总的来说,缺页中断是操作系统中的重要机制,它允许程序使用虚拟内存,并通过按需加载页面的方式有效利用有限的物理内存资源。缺页中断的处理过程涉及到页面加载、页表更新和页面置换等操作,以实现虚拟内存的管理和访问。
简述mmap的原理和使用场景
mmap的原理是内存映射技术,它可以将文件或其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一映射关系。当应用程序需要访问文件时,它会将文件映射到内存,并将文件的内容复制到内存中,这样应用程序就可以直接访问文件的内容,而不必再调用read或write等系统调用函数。同样,当应用程序需要更新文件时,它会将更新后的内容复制到文件中,从而实现对文件的更新。
mmap具有访问速度快、节约内存空间、可以支持多种类型文件等优点。因此,它在许多场景中都有广泛的应用。
- 文件映射:这是mmap最直接的应用。通过将文件映射到内存,应用程序可以像操作内存一样直接操作文件,极大地提高了文件访问的效率。
- 共享内存:mmap也可以用于实现进程间的共享内存。通过映射同一块物理内存到不同的进程地址空间,可以实现进程间的数据共享和通信,这种方式比传统的消息传递或共享文件等方式更高效。
- 设备驱动:在设备驱动中,mmap也常用于将设备的物理地址映射到用户空间的虚拟地址,使得用户空间的应用程序可以直接访问设备内存。
请注意,虽然mmap提供了高效的内存映射机制,但在使用时也需要注意一些问题,如内存泄漏、地址越界等。因此,在使用mmap时,应确保正确管理映射的内存区域,避免潜在的错误和安全问题。
Linux内核的主要组成部分是什么
中断的响应执行流程?听说过上半部和下半部吗?
中断的响应执行流程通常包括两个阶段:上半部和下半部。
-
上半部(Top Half): 中断首先会引发 CPU 从正常执行流程转移到中断处理程序(也称为中断服务程序或中断处理例程)。在上半部,中断处理程序会执行一些必要的、紧急的、与中断直接相关的操作,如保存寄存器状态、处理中断事件、更新相关数据结构等。上半部通常要求尽快执行完成,以便尽快恢复正常的执行流程。
-
下半部(Bottom Half): 上半部的执行可能会被打断或延迟其他高优先级的中断,因此为了避免阻塞系统的其他部分,有些任务会延迟到下半部执行。下半部通常在中断处理程序执行完毕后、中断服务程序退出后才执行,它可以是中断处理程序的延续,也可以是由中断处理程序触发的异步任务。**下半部的执行可以包括一些非紧急的、耗时较长的操作,如延迟的数据处理、资源释放、更新状态等。**下半部的执行通常不会直接参与中断的硬件响应,而是在中断处理程序完成后异步执行。
整个中断的响应执行流程可以概括为:
-
硬件中断触发: 外设产生中断信号,引发 CPU 转移到中断处理程序。
-
上半部执行: CPU 执行中断处理程序的上半部,处理与中断直接相关的紧急任务。
-
中断服务程序退出: 中断处理程序执行完成,CPU 从中断服务程序退出,恢复到原来的执行流程。
-
下半部执行: 中断处理程序触发下半部任务执行,处理一些延迟的、非紧急的操作,以释放系统资源或更新状态。
上半部和下半部的区分主要是为了提高系统的响应速度和性能,通过将紧急任务放在上半部执行,而将非紧急任务放在下半部执行,可以有效地分离不同优先级的任务,提高系统的并发性和效率。
软中断和硬中断的作用与区别
答案:
硬中断主要用于实时响应硬件设备的请求,而软中断则用于实现操作系统的功能和服务,并处理程序运行过程中的异常情况。
区别:
1.软中断是执行中断指令产生的,而硬中断是由外设引发的。
⒉硬中断的中断号是由中断控制器提供的,软中断的中断号由指令直接指出,无需使用中断控制器。
3.硬中断是可屏蔽的,软中断不可屏蔽。
4.硬中断处理程序要确保它能快速地完成任务,这样程序执行时才不会等待较长时间,称为上半部。
5.软中断处理硬中断未完成的工作,是一种推后执行的机制,属于下半部。
并发编程的三个概念
原子性问题,可见性问题,有序性问题
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性:是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:即程序执行的顺序按照代码的先后顺序执行。
原子性:要么执行且不被打断、要么不执行
可见性:一个线程修改,别的线程要能看到
有序性:按照顺序执行
说明什么是上下文切换?
你可以有很多角度,有进程上下文,有中断上下文。
进程上下文:一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容,当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
中断上下文:由于触发信号,导致CPU中断当前进程,转而去执行另外的程序。那么当前进程的所有资源要保存,比如堆栈和指针。保存过后转而去执行中断处理程序,快读执行完毕返回,返回后恢复上一个进程的资源,继续执行。这就是中断的上下文。
一个进程可以创建多少线程,和什么有关?
理论上,一个进程可用虚拟空间是2G,默认情况下,线程的栈的大小是1MB,所以理论上最多只能创建2048个线程。如果要创建多于2048的话,必须修改编译器的设置。
因此,一个进程可以创建的线程数由可用虚拟空间和线程的栈的大小共同决定,只要虚拟空间足够,那么新线程的建立就会成功。如果需要创建超过2K以上的线程,减小你线程栈的大小就可以实现了,虽然在一般情况下,你不需要那么多的线程。过多的线程将会导致大量的时间浪费在线程切换上,给程序运行效率带来负面影响。
外中断和异常有什么区别?
外中断是指由CPU执行指令以外的事件引起,如I/O完成中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。
异常是由CPU执行指令的内部事件引起,如非法操作码、地址越界、算术溢出等。
中断:cpu外的事件引起(io中断)、异步
异常:内部事件引起、同步
进程调度算法你了解多少?
-
先来先服务 first-come first-serverd(FCFS)
非抢占式的调度算法,按照请求的顺序进行调度。
有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。 -
短作业优先 shortest job first(SJF)
非抢占式的调度算法,按估计运行时间最短的顺序进行调度。
长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。 -
最短剩余时间优先 shortest remaining time next(SRTN)
最短作业优先的抢占式版本,按剩余运行时间的顺序进行调度。 当一个新的作业到达时,其整个运行时间与当前进程的剩余时间作比较。
如果新的进程需要的时间更少,则挂起当前进程,运行新的进程。否则新的进程等待。 -
时间片轮转
将所有就绪进程按FCFS的原则排成一个队列,每次调度时,把CPU时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把CPU时间分配给队首的进程。
时间片轮转算法的效率和时间片的大小有很大关系:
因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。而如果时间片过长,那么实时性就不能得到保证。
三级调度
操作系统中的三级调度通常是指:
-
长期调度(Long-term Scheduling): 也称为作业调度,负责从作业队列中选择作业(Job)或进程(Process)调入内存,以便进入就绪队列。长期调度的目标是控制系统的负载,保持系统中有限的资源(如内存)被合理地利用,避免过多的进程同时在内存中竞争资源。
-
中期调度(Medium-term Scheduling): 也称为挂起调度或深度调度,负责将部分处于执行状态的进程调出内存,放入挂起队列中,从而腾出内存资源给其他进程使用。中期调度的目标是控制系统中的内存使用,防止内存耗尽导致系统无响应或死锁。
-
短期调度(Short-term Scheduling): 也称为进程调度或CPU调度,负责从就绪队列中选择一个进程调度给 CPU 执行。短期调度的目标是优化 CPU 的利用率,提高系统的响应速度和吞吐量,通过动态地选择下一个要执行的进程来实现多任务的并发执行。
这三种调度方式在操作系统中起着不同的作用,长期调度负责控制整体的系统负载,中期调度负责控制内存的使用,而短期调度负责调度 CPU 执行任务。它们共同协作,保证了系统资源的有效管理和任务的合理调度,从而实现了高效的系统运行和多任务处理能力。
【操作系统】三级调度介绍(区别作业调度和进程调度)_中级调度-CSDN博客
内存交换和覆盖有什么区别?
交换技术主要是在不同进程(或作业)之间进行,而覆盖则用于同一程序或进程中。
交换:不同进程
覆盖:同一进程
动态分区分配算法有哪几种?可以分别说说吗?
一个程序从开始运行到结束的完整过程,你能说出来多少?
四个过程:
(1)预编译
主要处理源代码文件中的以“#”开头的预编译指令。处理规则见下
1、删除所有的#define,展开所有的宏定义。
2、处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。
3、处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,文件中包含其他文件。
4、删除所有的注释,“//”和“/**/”。
5、保留所有的#pragma编译器指令,编译器需要用到他们,如:#pragma once是为了防止有文件被重 复引用。
6、添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告是能够显示行号。
预编译:替换宏定义、处理预编译指令、替换头文件、删除注释
(2)编译
把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。
1、词法分析:利用类似于“有限状态机”的算法,将源代码程序输入到扫描机中,将其中的字符序列分割成一系列的记号。
2、语法分析:语法分析器对由扫描器产生的记号,进行语法分析,产生语法树。由语法分析器输出的语法树是一种以表达式为节点的树。
3、语义分析:语法分析器只是完成了对表达式语法层面的分析,语义分析器则对表达式是否有意义进行判断,其分析的语义是静态语义——在编译期能分期的语义,相对应的动态语义是在运行期才能确定的语义。
4、优化:源代码级别的一个优化过程。
5、目标代码生成:由代码生成器将中间代码转换成目标机器代码,生成一系列的代码序列——汇编语言表示。
6、目标代码优化:目标代码优化器对上述的目标机器代码进行优化:寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等。
词法分析、语法分析、语义分析、优化、中间代码生成、目标代码优化
(3)汇编
将汇编代码转变成机器可以执行的指令(机器码文件)。 汇编器的汇编过程相对于编译器来说更简单,没有复杂的语法,也没有语义,更不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译过来,汇编过程有汇编器as完成。经汇编之后,产生目标文件(与可执行文件格式几乎一样)xxx.o(Linux下)、xxx.obj(Windows下)。
汇编语言转机器语言
(4)链接
将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。链接分为静态链接和动态链接。
- 静态链接(Static Linking):
- 在编译时将所有的目标文件和库文件链接到一个单独的可执行文件中。
- 链接后生成的可执行文件包含了所有程序所需的代码和库函数,形成一个独立的可执行文件。
- 静态链接的优点是可移植性强、执行速度快,但缺点是占用的磁盘空间较大,并且不便于更新和维护。
- 静态链接的文件扩展名通常是
.exe
(Windows)、.out
(Linux)等。- 动态链接(Dynamic Linking):
- 在运行时将程序中所需的共享库(动态链接库)链接到可执行文件中。
- 可执行文件只包含了程序的逻辑代码,而共享库被加载到内存中,程序在运行时调用共享库中的函数。
- 动态链接的优点是节省了磁盘空间,多个程序可以共享同一个共享库,并且便于更新和维护;缺点是启动速度稍慢,并且需要确保系统中有相应的共享库文件。
- 动态链接的文件扩展名通常是
.dll
(Windows)、.so
(Linux)等。
操作系统在对内存进行管理的时候需要做些什么?
- 操作系统负责内存空间的分配与回收。
- 操作系统需要提供某种技术从逻辑上对内存空间进行扩充。
- 操作系统需要提供地址转换功能,负责程序的逻辑地址与物理地址的转换。
- 操作系统需要提供内存保护功能。保证各进程在各自存储空间内运行,互不干扰。
分配、扩充、转换、内存保护
说一下你理解中的内存?他有什么作用呢?
内存是计算机系统中的关键组成部分,用于存储和访问数据和指令。它是临时存储器,用于暂时保存正在执行的程序和数据,以供处理器快速读取和写入。
内存的作用主要有以下几个方面:
- 存储程序和数据:内存用于存储操作系统、应用程序和用户数据。当程序被加载到内存时,处理器可以直接从内存中读取指令并执行,而不需要频繁地从硬盘或其他存储设备读取数据。
- 提供快速访问:与硬盘等外部存储设备相比,内存的读取和写入速度非常快。这使得处理器可以更快地获取所需的数据,提高计算机的响应速度和效率。
- 支持多任务处理:内存使得同时运行多个程序成为可能。每个程序都可以在内存中占据一定的空间,处理器可以在它们之间切换,实现快速的上下文切换,使得多个任务可以并发执行。
- 缓存数据:内存中的缓存用于存储最常用的数据和指令,以提高处理器的访问速度。缓存能够根据处理器的需求快速地提供数据,减少了对主存的访问次数。
指令和数据,快速读取和写入
内存交换是什么?有什么特点?
内存交换(Swap)是操作系统中一种将内存中暂时不活跃的数据或进程移动到硬盘上的机制。当系统内存不足时,操作系统可以将部分内存中的数据或进程交换到硬盘的交换空间(Swap Space)中,以释放内存空间供其他进程使用。这样可以提高系统的性能和响应速度,避免内存耗尽导致系统死机或崩溃。
内存交换的特点包括:
-
临时性: 内存交换是一种临时性的操作,用于处理系统内存不足的情况。当系统内存充足时,交换的数据或进程可以被重新加载到内存中,以恢复其执行状态。
-
性能开销: 内存交换涉及到将数据从内存复制到硬盘上的交换空间,以及在需要时再次从硬盘上加载数据到内存中。这些操作会引入一定的性能开销,包括磁盘 I/O 操作和数据传输延迟。
-
提高系统容量: 内存交换可以提高系统的内存容量,使得系统可以运行更多的进程或应用程序,而不会因为内存不足而导致系统性能下降或崩溃。
-
影响系统性能: 当系统频繁进行内存交换时,会增加系统的负载和响应时间,降低系统的性能。因此,内存交换应该在必要时才进行,并尽量减少频繁的交换操作。
总的来说,内存交换是操作系统中一种重要的内存管理机制,用于处理内存不足的情况,并提高系统的容量和稳定性。然而,过度的内存交换可能会影响系统的性能,因此需要根据实际情况进行合理的调整和优化。
不活跃的换出,提高性能
从堆和栈上建立对象哪个快?(考察堆和栈的分配效率比较)
- 分配和释放,堆在分配和释放时都要调用函数(malloc,free),比如分配时会到堆空间去寻找足够大小的空间(因为多次分配释放后会造成内存碎片),这些都会花费一定的时间,具体可以看看malloc和free的源代码,函数做了很多额外的工作,而栈却不需要这些。
- 访问时间,访问堆的一个具体单元,需要两次访问内存,第一次得取得指针,第二次才是真正的数据,而栈只需访问一次。另外,堆的内容被操作系统交换到外存的概率比栈大,栈一般是不会被交换 出去的。
栈快,移动栈指针即可,堆还要动态分配
内存管理有哪几种方式?
内存管理方式为连续分配管理方式和非连续分配管理方式这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,例如块式管理 。非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,例如页式管理、段式管理。
连续和离散
块式管理
把主存分为一大块一大块的,当所需的程序片断不在主存时就分配一块主存空间,把程序片断载入主 存,就算所需的程序片段只有几个字节,也只能把这一块分配给它。这样会造成很大的浪费,平均浪费 了50%的内存空间,但是易于管理。
页式管理
用户程序的地址空间被划分成若干个固定大小的区域,这个区域被称为页”,相应地,内存空间也被划分 为 若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,从而实现了离散分配。 这种方式的优点是页的大小是固定的,因此便于管理;缺点是页长与程序的逻辑大小没有任何关 系。这就导致在某个时刻一个程序可能只有一部分在主存中,而另一部分则在辅存中。这不利于编程时 的独立性,并给换入换出处理、存储保护和存储共享等操作造成麻烦。
段式管理
段是按照程序的自然分界划分的并且长度可以动态改变的区域。使用这种方式,程序员可以把子程序、 操作数和不同类型的数据和函数划分到不同的段中。这种方式将用户程序地址空间分成若干个大小不等 的段,每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻 接,也实现了离散分配。
分页对程序员而言是不可见的,而分段通常对程序员而言是可见的,因而分段为组织程序和数据提 供了方便,但是对程序员的要求也比较高。 分段存储主要有如下优点: 1. 段的逻辑独立性不仅使其易于编译、管理、修改和保护,也便于多道程序共享。 2. 段长可以根据需要动态改变,允许自由调度,以便有效利用主存空间。 3. 方便分段共享、分段保护、动态链接、动态增长。
分段存储的缺点如下: 1. 由于段的大小不固定,因此存储管理比较麻烦。 2. 会生成段内碎片,这会造成存储空间利用率降低。而且段式存储管理比页式存储管理方式需 要更多的硬件支持。 正是由于页式管理和段式管理都有各种各样的缺点,因此,为了把这两种存储方式的优点结合起 来,新引入了段页式管理。
段页式管理
段页式存储组织是分段式和分页式结合的存储组织方法,这样可充分利用分段管理和分页管理的优点。 1. 用分段方法来分配和管理虚拟存储器。程序的地址空间按逻辑单位分成基本独立的段,而每一段有 自己的段名,再把每段分成固定大小的若干页 2. 用分页方法来分配和管理内存,即把整个主存分成与上述页大小相等的存储块,可装入作业的任何 一页。程序对内存的调入或调出是按页进行的,但它又可按段实现共享和保护
为什么使用虚拟内存
虚拟内存是一种内存管理技术,之所以有这个虚拟内存,主要是为了扩张内存,因为我们的这个代码数据什么都是存在硬盘中的,而CPU是没办法直接去拿硬盘的数据,必须借助内存,但是这个内存是有限的,所以在内存不够的时候就会把一部分这个硬盘作为虚拟内存,这样cpu就可以读取虚拟内存的数据。
它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),允许程序员编写并 运行比实际系统拥有的内存大得多的程序
(1)扩大地址空间。每个进程独占一个4G空间,虽然真实物理内存没那么多。
(2)内存保护:防止不同进程对物理内存的争夺和践踏,可以对特定内存地址提供写保护,防止恶意篡改。
(3)可以实现内存共享,方便进程通信。
(4)可以避免内存碎片,虽然物理内存可能不连续,但映射到虚拟内存上可以连续。
解释下内存碎片,内碎片,外碎片
内存碎片是由于多次进行内存分配造成的,当进行内存分配时,内存格式一般为:(用户使用段)(空白段)(用户使用段),当空白段很小的时候,可能不能提供给用户足够多的空间,如夹在中间的空白段的大小为5,而用户需要的内存大小为6,这样会产生很多的间隙,造成使用效率下降,这些很小的空隙叫碎片。
内碎片:分配给程序的存储空间没有用完,有一部分是程序不使用,但其他程序也没法用的空间。内碎 片是处于区域内部或页面内部的存储块,占有这些区域或页面的进程并不使用这个存储块,而在进程占 有这块存储块时,系统无法利用它,直到进程释放它,或进程结束时,系统才有可能利用这个存储块。
外碎片:空间太小,小到无法给任何程序分配(不属于任何进程)的存储空间。外部碎片是出于任何已 分配区域或页面外部的空闲存储块,这些存储块的总和可以满足当前申请的长度要求,但是它们的地址 不连续或其他原因,使得系统无法满足当前申请。
内碎片和外碎片是一对矛盾体,一种特定的内存分配算法,很难同时解决好内碎片和外碎片的问题,只 能根据应用特点进行取舍
内碎片:分配的没用完,别的也不能用
外碎片,空间太小无法分配,地址不连续
解释下虚拟地址、逻辑地址、线性地址、物理地址
- 虚拟地址(Virtual Address):
- 虚拟地址是由程序生成的地址,用于访问虚拟内存空间中的数据。每个进程都有自己的虚拟地址空间,从0开始递增,通常是连续的地址空间。
- 虚拟地址是在程序执行时由CPU生成的,可以通过地址转换机制映射到物理地址空间中的实际存储位置。
- 逻辑地址(Logical Address):
- 逻辑地址是程序中使用的地址,通常是相对于程序的起始地址的偏移量。逻辑地址是程序员编写程序时使用的地址,不直接对应到实际的内存位置。
- 逻辑地址需要经过地址转换机制,转换为虚拟地址或线性地址,最终映射到物理地址上的存储位置。
- 线性地址(Linear Address):
- 线性地址是虚拟地址空间中的地址,经过分段机制或分页机制转换后得到的地址。线性地址是虚拟地址空间的一部分,但不一定是连续的。
- 线性地址是操作系统管理的地址空间,通过地址映射机制将线性地址映射到物理地址空间中的实际存储位置。
- 物理地址(Physical Address):
- 物理地址是内存中实际存储单元的地址,对应到计算机的物理内存中的存储位置。物理地址是硬件直接访问的地址。
- 通过地址转换机制(如页表、段表等),虚拟地址或线性地址最终映射到物理地址,实现程序对内存的访问。
虚拟内存置换方式是怎么样的
四种页面置换算法: 最佳(OPT,Optional) 最近最少使用(LRU,Least Recently Used) 先进先出(FIFO,First In First Out) 时钟(Clock)
最佳置换算法
OPT 策略选择置换下次访问距当前时间最长的那些页,可以看出该算法能导致最少的缺页中断,但是由 于它要求操作系统必须知道将来的事件,显然这是不可能实现的。但它仍然能作为一种标准来衡量其他 算法的性能。
最近最少使用算法
LRU 策略置换内存中上次使用距当前最远的页。根据局部性原理,这也是最不可能访问的页。实际上, LRU 策略的性能接近于 OPT 策略。该方法的问题在于比较难以实现。一种实现方法是给每一页添加一个 最后访问的时间戳,并且必须每次访问内存时,都更新这个时间戳。即使有这种方案的硬件,开销仍然 是非常大的。另外一种可选的方法是维护一个关于访问页的栈,但开销同样很大。
先进先出算法
FIFO 策略把分配给进程的页框视为一个循环缓冲区,按循环方式移动页。它所需的只是一个指针,这个 指针在该进程的页框中循环。因此这是一种最简单的页面置换策略。除了它的简单性,这种选择方法所 隐含的逻辑是置换驻留在内存中最长时间的页:一个很久以前取入内存的页,到现在可能已经不会再用 了。这个推断是错误的,因为经常出现一部分程序或数据在整个程序的生命周期中使用频率很高的情 况,如果使用 FIFO 算法,则这些页会被反复的换入换出,增加了系统开销。
常见内存分配方式有哪些?
(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
用户空间与内核通信方式有哪些?
(1)系统调用。用户空间进程通过系统调用进入内核空间,访问指定的内核空间数据;
(2)驱动程序。用户空间进程可以使用封装后的系统调用接口访问驱动设备节点,以和运行在内核空间的 驱动程序通信;
(3)共享内存mmap。在代码中调用接口,实现内核空间与用户空间的地址映射,在实时性要求很高的项目中为首选,省去拷贝数据的时间等资源,但缺点是不好控制;
(4)copy_to_user()、copy_from_user(),是在驱动程序中调用接口,实现用户空间与内核空间的数据拷贝操作,应用于实时性要求不高的项目中。
系统调用与普通函数调用的区别
- 执行环境不同。系统调用运行在系统态,而普通函数调用运行在用户态,系统态是操作系统的核心部分,拥有访问硬件和系统资源的权限,用户态则主要是应用程序的执行环境,限制在用户程序内部。
- 调用过程不同。系统调用涉及从用户态切换到内核态的过程,需要中断处理,这导致了一定的开销,而普通函数调用仅涉及用户态下的代码执行,不需要进行上下文切换。
- 运行时间不同。系统调用的运行时间属于系统时间,与操作系统的内核相关,而普通函数调用的运行时间属于用户时间,仅涉及用户程序内部的代码执行。
- 权限和功能不同。系统调用提供对操作系统功能和资源的访问权限,可以执行特权操作如文件系统操作、进程管理、网络通信等,普通函数调用则没有直接访问这些底层功能的权限。
- 调用方式不同。系统调用通常通过软中断或陷阱指令触发,而普通函数调用则是通过程序内部的跳转指令触发。
环境、权限、调用过程、调用方式
内核态,用户态的区别
内核态,操作系统在内核态运行——运行操作系统程序
用户态,应用程序只能在用户态运行——运行用户程序
当一个进程在执行用户自己的代码时处于用户运行态(用户态),此时特权级最低,为3级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。Ring3状态不能访问Ring0的地址空间,包括代码和数据;当一个进程因为系统调用陷入内核代码中执行时处于内核运行态(内核态),此时特权级最高,为0级。执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈。
bootloader、内核 、根文件的关系
启动顺序:bootloader->linux kernel->rootfile->app
Bootloader全名为启动引导程序,是第一段代码,它主要用来初始化处理器及外设,然后调用Linux内核。Linux内核在完成系统的初始化之后需要挂载某个文件系统作为根文件系统(RootFilesystem),然后加载必要的内核模块,启动应用程序。(一个嵌入式Linux系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux内核,文件系统,应用程序。)
Bootloader启动的两个阶段
Stage1:汇编语言
(1)基本的硬件初始化(关闭看门狗和中断,MMU(带操作系统),CACHE。配置系统工作时钟)
(2)为加载stage2准备RAM空间
(3)拷贝内核映像和文件系统映像到RAM中
(4)设置堆栈指针sp
(5)跳到stage2的入口点
Stage2:c语言
(1)初始化本阶段要使用到的硬件设备(led uart等)
(2)检测系统的内存映射
(3)加载内核映像和文件系统映像
(4)设置内核的启动参数
嵌入式系统中广泛采用的非易失性存储器通常是Flash,而Bootloader就位于该存储器的最前端,所以系统上电或复位后执行的第一段程序便是Bootloader。
- 基本的硬件初始化,为加载stage2准备RAM空间,拷贝内核和文件系统映像,设置栈指针,跳转到stage2
- 初始化硬件设备,检测内存映射,加载内核和文件系统映射,设置内核启动参数
中断怎么发生,中断处理流程
请求中断→响应中断→关闭中断→保留断点→中断源识别→保护现场→中断服务子程序→恢复现场→中断返回。
常见的几种磁盘调度算法
读写一个磁盘块的时间的影响因素有:
- 旋转时间(主轴转动盘面,使得磁头移动到适当的扇区上)
- 寻道时间(制动手臂移动,使得磁头移动到适当的磁道上)
- 实际的数据传输时间
其中,寻道时间最长,因此磁盘调度的主要目标是使磁盘的平均寻道时间最短。
算法主要有:
1. 先来先服务
按照磁盘请求的顺序进行调度。
2. 最短寻道时间优先
优先调度与当前磁头所在磁道距离最近的磁道。
3. 电梯扫描算法
电梯总是保持一个方向运行,直到该方向没有请求为止,然后改变运行方向。
系统调用是什么,你用过哪些系统调用,和库函数有什么区别?
系统调用:是通向操作系统本身的接口,是面向底层硬件的。通过系统调用,可以使得用户态运行的进程 与硬件设备(如CPU、磁盘、打印机等)进行交互,是操作系统留给应用程序的一个接口。下面为适用于访 问设备驱动程序的系统调用:
open: 打开文件或设备
read: 从打开的文件或设备中读取数据
write: 向打开的文件或设备中写入数据
close: 关闭文件或设备
ioctl: 把控制信息传递给设备驱动文件
库函数(Library function):是把函数放到库里,供别人使用的一种方式。.方法是把一些常用到的函数编 完放到一个文件里,供不同的人进行调用。一般放在.lib文件中。库函数调用则是面向应用开发的,库 函 数可分为两类,一类是C语言标准规定的库函数,一类是编译器特定的库函数。
系统调用是为了方便使用操作系统的接口,而库函数则是为了人们编程的方便。 库函数调用与系统无关,不同的系统,调用库函数,库函数会调用不同的底层函数实现,因此可移植性 好。 由于库函数是基于c库的,因此不能用于内核对于底层驱动设备的操作。
区别
- 库函数是语言或应用程序的一部分,而系统调用是内核提供给应用程序的接口,属于系统的一部分
- 库函数在用户地址空间执行,**系统调用是在内核地址空间执行,**库函数运行时间属于用户时间,系 统调用属于系统时间,库函数开销较小,系统调用开销较大
- 库函数是有缓冲的,系统调用是无缓冲的
- 系统调用依赖于平台,库函数并不依赖
上下文有哪些?怎么理解?
上下文简单说来就是一个环境。 用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的一些寄存器值、变量等。所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等
相对于进程而言,就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进 程打开的文件、内存信息等
一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。 1. 用户级上下文: 正文、数据、用户堆栈以及共享存储区; 2. 寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP); 3. 系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、 pte)、内核栈。
当发生进程调度时,进行进程切换就是上下文切换(context switch).操作系统必须对上面提到的全部信 息进行切换,新调度的进程才能运行。而系统调用进行的模式切换(mode switch)。模式切换与进程切 换比较起来,容易很多,而且节省时间,因为模式切换最主要的任务只是切换进程寄存器上下文的切 换。
硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的 一些变量和参数 也要传递给内核,内核通过这些参数进行中断处理。所谓的“ 中断上下文”,其实也可以看作就是硬件传 递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。中断时,内 核不代表任何进程运行,它一般只访问系统空间,而不会访问进程空间,内核在中断上下文中执行时一 般不会阻塞。
为什么会有上下文这种概念?
上下文(Context)是指在计算机系统中,用于描述当前程序或进程的运行环境和状态的信息集合。上下文概念的存在是因为计算机系统是一个多任务、多进程的环境,不同的程序或进程需要在 CPU 上轮流执行,而在切换执行的过程中需要保存和恢复各个程序或进程的状态,以确保程序的执行能够顺利进行。
以下是几个导致上下文概念出现的主要原因:
-
多任务调度: 操作系统需要在不同的程序或进程之间进行切换调度,以实现多任务并发执行。在切换执行的过程中,需要保存当前程序或进程的状态,以便稍后恢复执行。
-
中断处理: 当发生硬件中断或异常时,操作系统需要暂停当前程序或进程的执行,转而执行中断处理程序。在处理完中断后,需要恢复被中断的程序或进程的执行状态。
-
进程间通信: 不同的程序或进程可能需要进行通信和共享数据。在进行进程间切换时,需要保存发送进程的状态,以便接收进程在恢复执行时能够正确接收和处理数据。
-
线程切换: 在多线程编程中,线程是程序的基本执行单元。线程之间的切换也需要保存和恢复线程的执行状态,以确保程序的正确执行。
总的来说,上下文的概念在计算机系统中是必不可少的,它提供了一种机制来保存和恢复程序或进程的执行状态,以确保程序的连续执行和正确性。操作系统通过管理上下文,实现了程序的并发执行和资源的有效利用,从而提高了系统的性能和可靠性。
什么情况下进行用户态到内核态的切换?
用户态到内核态的切换(也称为系统调用)通常发生在以下几种情况下:
-
系统调用(Syscall): 用户程序通过系统调用请求操作系统提供特权功能或资源,例如文件操作、网络通信、进程管理等。当用户程序执行系统调用时,CPU会从用户态切换到内核态,操作系统会在内核态执行相应的系统调用服务例程,并返回结果给用户程序。
-
异常处理(Exception Handling): 当用户程序执行期间发生异常(如除零、内存访问异常等)或非法操作(如访问未授权的内存、执行特权指令等)时,CPU会触发异常,并将控制权转移到操作系统内核。操作系统会在内核态处理异常,并根据异常的类型采取相应的措施,如终止程序、发送信号、进行内存保护等。
-
外部中断(External Interrupt): 当外部设备(如键盘、鼠标、网络接口等)产生中断请求时,CPU会暂停当前程序的执行,并将控制权转移到操作系统内核。操作系统会在内核态处理中断请求,并根据中断的类型进行相应的处理,如接收输入、发送数据、处理网络通信等。
总的来说,用户态到内核态的切换通常发生在用户程序请求操作系统提供服务、发生异常或外部中断时。在这些情况下,操作系统需要执行特权操作或处理硬件事件,因此需要从用户态切换到内核态执行相应的操作。
中断上下文代码中有哪些注意事项?
在处理中断上下文时,需要注意一些关键事项,以确保中断处理程序的正确性和可靠性。以下是一些注意事项:
-
最小化中断服务程序的执行时间: 中断服务程序应该尽量简洁和高效,以最小化中断响应时间。过长的中断服务程序可能会影响系统的响应性能,甚至导致其他重要任务的延迟。
-
避免阻塞和死锁: 中断服务程序应该避免使用会阻塞或导致死锁的操作,如使用同步机制、申请锁、调用系统调用等。在中断上下文中,不应该做任何可能导致等待的操作。
-
注意共享资源的同步: 如果中断服务程序和其他任务共享资源(如共享内存、设备寄存器等),需要注意在中断服务程序中正确同步对这些资源的访问,以避免竞争条件和数据一致性问题。
-
禁止或延迟抢占: 为了确保中断服务程序的原子性和可预测性,通常会禁止或延迟抢占中断服务程序。这意味着在中断服务程序执行期间,不允许其他中断或任务中断当前中断服务程序的执行。
-
避免递归和动态内存分配: 中断服务程序通常应该避免使用递归调用和动态内存分配操作,因为这些操作可能会增加中断服务程序的执行时间和复杂性,从而影响系统的可靠性和可预测性。
-
注意中断优先级和嵌套中断: 如果系统支持多级中断优先级或嵌套中断,需要注意中断的优先级关系和中断嵌套的情况,以确保中断服务程序的正确执行顺序和优先级。
总的来说,处理中断上下文时需要谨慎设计和编写中断服务程序,遵循一些基本原则和注意事项,以确保系统的稳定性、可靠性和可维护性。
请问线程需要保存哪些上下文,SP、PC、EAX这些寄存器是干嘛用的?
线程在切换的过程中需要保存当前线程id、线程状态、堆栈、寄存器状态等信息。其中寄存器主要包括 SP、PC、EAX等寄存器,其主要功能如下:
SP:堆栈指针,指向当前栈的栈顶地址
PC:程序计数器,存储下一条将要执行的指令
EAX:累加寄存器,用于加法乘法的缺省寄存器
用户态和内核态是什么?
用户态和内核态是操作系统中的两种运行模式。用户态是指程序在执行时只能访问受限资源,不能直接访问操作系统的资源;而内核态是指操作系统拥有对所有资源的完全控制权。
为什么要有用户态和内核态,如何转换?
操作系统需要有用户态和内核态的划分是为了保护操作系统的核心功能不被恶意程序破坏,同时提高系统的安全性和稳定性。用户态和内核态之间的转换是通过系统调用或者中断来实现的。
用户态跳中断到内核态,和用户态跳中断到用户态的区别
- 权限级别:
- 用户态跳转到内核态时,程序将进入操作系统内核的权限级别,即特权级别。在内核态中,程序可以执行特权指令、访问受限资源和执行操作系统提供的特权服务。
- 用户态跳转到用户态时,程序仍然保持在用户态的权限级别,即普通用户级别。在用户态中,程序只能执行受限制的指令集,并且无法直接访问操作系统的核心资源。
- 触发原因:
- 用户态跳转到内核态通常是由系统调用、异常或外部中断等事件触发的。这些事件需要操作系统的特权级别来处理,因此需要从用户态跳转到内核态。
- 用户态跳转到用户态通常是由函数调用或分支指令(如跳转指令)触发的。这些指令可以在用户态下直接执行,不需要进入内核态。
- 开销和复杂性:
- 用户态跳转到内核态通常涉及到上下文切换、状态保存和恢复等复杂操作,因此具有较高的开销和复杂性。在跳转到内核态时,需要保存当前用户态的上下文信息,并将控制权转移到内核态执行相应的操作;而在返回用户态时,需要恢复用户态的上下文信息,并继续执行用户态程序。
- 用户态跳转到用户态通常不涉及到上下文切换和状态保存,因此具有较低的开销和复杂性。在跳转到用户态时,只需简单地更新程序计数器和栈指针等寄存器的值,然后继续执行用户态程序。
内核
内核一般会提供 4 个基本能力:
- 管理进程、线程,决定哪个进程、线程使用 CPU,也就是进程调度的能力;
- 管理内存,决定内存的分配和回收,也就是内存管理的能力;
- 管理硬件设备,为进程与硬件设备之间提供通信能力,也就是硬件通信能力;
- 提供系统调用,如果应用程序要运行更高权限运行的服务,那么就需要有系统调用,它是用户程序与操作系统之间的接口。
内核是怎么工作的?
内核具有很高的权限,可以控制 cpu、内存、硬盘等硬件,而应用程序具有的权限很小,因此大多数操作系统,把内存分成了两个区域:
- 内核空间,这个内存空间只有内核程序可以访问;
- 用户空间,这个内存空间专门给应用程序使用;
用户空间的代码只能访问一个局部的内存空间,而内核空间的代码可以访问所有内存空间。因此,当程序使用用户空间时,我们常说该程序在用户态执行,而当程序使内核空间时,程序则在内核态执行。
应用程序如果需要进入内核空间,就需要通过系统调用,下面来看看系统调用的过程:
内核程序执行在内核态,用户程序执行在用户态。当应用程序使用系统调用时,会产生一个中断。发生中断后, CPU 会中断当前在执行的用户程序,转而跳转到中断处理程序,也就是开始执行内核程序。内核处理完后,主动触发中断,把 CPU 执行权限交回给用户程序,回到用户态继续工作。
然后中断有什么需要设置的?
在处理中断时,需要设置中断向量表、中断服务程序和中断处理程序等,以确保系统能够正确响应和处理各种中断事件。中断向量表用于存储中断向量和对应的中断服务程序的入口地址,中断服务程序用于处理中断事件,而中断处理程序则是在中断服务程序执行完毕后恢复现场并返回到原来的程序执行。
中断服务程序和中断处理程序不一样!
进程调度和通信方式,linux系统进程调度采用的算法,挑一个通信方式细说操作系统实现,
进程调度的方式
- 先来先服务调度(First Come First Served, FCFS):
- 按照进程到达的顺序进行调度,先到达的进程先执行,直到执行完毕或阻塞。
- FCFS调度算法简单直观,但可能导致长作业等待时间过长的问题。
- 最短作业优先调度(Shortest Job First, SJF):
- 选择执行时间最短的进程优先执行,以减少平均等待时间。
- SJF调度算法能够最大程度地减少平均等待时间,但可能导致长作业长时间等待。
- 优先级调度(Priority Scheduling):
- 每个进程都有一个优先级,优先级高的进程先执行。可以是静态优先级或动态优先级。
- 优先级调度算法可以根据进程的重要性和紧急程度进行调度,但可能导致低优先级进程长时间等待。
- 时间片轮转调度(Round Robin, RR):
- 将CPU时间分成若干个时间片,每个进程在一个时间片内执行,时间片结束后切换到下一个进程。
- RR调度算法能够公平地分配CPU时间,但可能导致上下文切换开销增加。
- 多级反馈队列调度(Multilevel Feedback Queue):
- 将进程按照优先级划分成多个队列,每个队列有不同的时间片大小,优先级高的队列时间片短。
- 进程根据优先级和时间片大小在不同队列中调度,可以灵活适应不同类型的进程。
函数调用具体过程
- 函数定义: 在程序中定义函数,包括函数名称、参数列表、返回类型和函数体。
- 函数调用: 当程序执行到调用函数的语句时,会跳转到函数定义的位置执行相应的代码。
- 保存上下文: 在调用函数之前,需要保存当前函数的执行状态,包括函数返回地址、参数值、局部变量等。
- 传递参数: 将调用函数时传递的参数传递给被调用函数,可以通过寄存器、栈或其他方式传递参数。
- 跳转执行: 执行被调用函数的代码,包括参数处理、局部变量声明、函数体执行等。
- 返回结果: 当函数执行完成时,将返回值传递给调用函数,通常通过寄存器、栈或其他方式返回结果。
- 恢复上下文: 恢复调用函数之前保存的执行状态,包括返回地址、参数值、局部变量等。
- 继续执行: 在调用函数之后,程序会继续执行调用函数之后的代码。
对操作系统的理解,应该要拥有哪些功能
操作系统通常应包括下列五大功能模块:
(1)处理机管理。处理机管理(也称处理器管理)主要包括进程控制、进程同步、进程通信和进程调度。
(2)存储器管理。存储器的管理主要任务是为多道程序的运行提供良好的环境,方便用户使用存储器,提高存储器的利用率以及能从逻辑上扩充内存。
(3)设备管理。设备管理是指操作系统对各类外围设备的管理,包括分配、启动和故障处理等。
(4)文件管理。计算机中的数据和信息是以文件的形式存储在外存储器上供用户使用。文件管理是指操作系统对信息资源的管理。文件管理支持文件的存储、检索和修改等操作以及文件的保护功能。
(5)作业管理。每个用户请求计算机系统完成的一个独立的操作称为作业。作业管理是用户与操作系统间的接口,主要包括作业的输入和输出、作业的调度与控制。
三级调度
(1)操作系统中有三级调度:高级调度(作业调度)、中级调度(交换调度)和低级调度(进程调度)。它们构成系统内的多级调度。不同类型的操作系统不一定完全都实现上述三种调度。 (2)处理机三级调度分别在如下情况下发生: 1)高级调度。高级调度是根据系统内所有资源的使用情况,一旦可能便从后备作业中选择一道作业进入系统,并创建相应的进程,分配必要的系统资源,然后将进程“就绪”。 2)低级调度。低级调度即为CPU调度,它是根据CPU资源的使用情况及时分配CPU,即从“就绪”的进程中选择一个进程在CPU上“运行”。这种调度不仅要求调度算法本身的时间复杂度小,而且要求策略精良,因为低级调度直接影响着系统的整体效率。在多道程序系统中必须提供低级调度。 3)中级调度。在内存中常常有许多进程处于某种等待状态,这些进程在“等待”期间无谓地占用着内存资源,如将它们暂时换至外存,则所节省出来的内存空间可用以接纳新的进程。一旦被换至外存的进程具备运行条件,再将其重新换入内存。为此,在逻辑上将主存延伸,用一部分外存空间(称为交换区)替代主存,并且实施交换调度(中级调度)。在各种类型的操作系统中可以根据内存的配置和系统能承受的最大负载,有选择地进行中级调度,或者不实施中级调度。 (3)高级调度完成作业调度,使“后备”状态的作业变为“执行”状态:中级调度完成内存和外存信息的交换调度;低级调度完成进程调度,使“就绪”的进程在CPU上“运行”。
总线
总线是用于 CPU 和内存以及其他设备之间的通信,总线可分为 3 种:
- 地址总线,用于指定 CPU 将要操作的内存地址;
- 数据总线,用于读写内存的数据;
- 控制总线,用于发送和接收信号,比如中断、设备复位等信号,CPU 收到信号后自然进行响应,这时也需要控制总线;
当 CPU 要读写内存数据的时候,一般需要通过下面这三个总线:
- 首先要通过「地址总线」来指定内存的地址;
- 然后通过「控制总线」控制是读或写命令;
- 最后通过「数据总线」来传输数据;
线程同步的方式有哪些?互斥量和信号量有什么区别?什么是临界区?如果一个进程想读取另外一个进程的锁具体怎么办?还有锁相关的内容:为什么要用锁为什么出现竞态条件?