物是人非事事休,欲语泪先流|

游客0721

园龄:2年粉丝:4关注:2

操作系统重点整理(新)

0.TCP/IP 网络模型有哪几层?

1.Linux中查看进程运行状态的指令、查看内存使用情况的指令、tar解压文件的参数

查看进程运行状态的指令:ps命令。“ps -aux | grep PID”,用来查看某PID进程状态

查看内存使用情况的指令:free命令。“free -m”,命令查看内存使用情况。

tar解压文件的参数:

  • -x:解压
  • -z:有gzip属性的
  • -v:显示所有过程

2.文件权限怎么修改

Linux文件的基本权限就有九个,分别是owner/group/others三种身份各有自己的read/write/execute权限

修改权限指令:chmod,文件的权限字符为 -rwxrwxrwx 时,这九个权限是三个三个一组。其中,我们可以使用数字来代表各个权限
image

每种身份(owner/group/others)各自的三个权限(r/w/x)分数是需要累加的,

例如当权限为: [-rwxrwx---] ,则分数是

owner = rwx = 4+2+1 = 7
group = rwx = 4+2+1 = 7
others= --- = 0+0+0 = 0

chmod [-R] xyz 文件或目录

选项与参数

  • xyz : 就是刚刚提到的数字类型的权限属性,为 rwx 属性数值的相加。
  • -R : 进行递归(recursive)的持续变更,亦即连同次目录下的所有文件都会变更
  • chmod 770 test.c //即修改test.c文件的权限为770

3.说说常用的Linux命令

cd命令:用于切换当前目录

ls命令:查看当前文件与目录

grep命令:该命令常用于分析一行的信息,若当中有我们所需要的信息,就将该行显示出来,该命令通常与管道命令一起使用,用于对一些命令的输出进行筛选加工。

cp命令:复制命令

mv命令:移动文件或文件夹命令

rm命令:删除文件或文件夹命令

ps命令:查看进程情况

kill命令:向进程发送终止信号

tar命令:对文件进行打包,调用gzip或bzip对文件进行压缩或解压

cat命令:查看文件内容,与less、more功能相似

top命令:可以查看操作系统的信息,如进程、CPU占用率、内存信息等

pwd命令:命令用于显示工作目录。

4.说说如何以root权限运行某个程序

sudo chown root app(文件名)

5.说说软链接和硬链接的区别

定义不同

软链接又叫符号链接,这个文件包含了另一个文件的路径名。可以是任意文件或目录,可以链接不同文件系统的文件。

硬链接就是一个文件的一个或多个文件名。把文件名和计算机文件系统使用的节点号链接起来。因此我们可以用多个文件名与同一个文件进行链接,这些文件名可以在同一目录或不同目录。

限制不同

硬链接只能对已存在的文件进行创建,不能交叉文件系统进行硬链接的创建;

软链接可对不存在的文件或目录创建软链接;可交叉文件系统;

创建方式不同

硬链接不能对目录进行创建,只可对文件创建;

软链接可对文件或目录创建;

影响不同

删除一个硬链接文件并不影响其他有相同 inode 号的文件。

删除软链接并不影响被指向的文件,但若被指向的原文件被删除,则相关软连接被称为死链接(即 dangling link,若被指向路径文件被重新创建,死链接可恢复为正常的软链接)。

6.说说静态库和动态库怎么制作及如何使用,区别是什么

静态库的制作:
gcc hello.c -c //这样就生成hello.o目标文件 ar rcs libhello.a hello.o//生成libhello.a静态库

静态库的使用:
gcc main.c -lhello -o staticLibrary //main.c和hello静态库链接,生成staticLibrary执行文件

/* main.c:是指main主函数 -lhello:是我们生成的.a 文件砍头去尾(lib不要 .a也不要)前面加-l -L:是指告诉gcc编译器先从-L指定的路径去找静态库,默认是从/usr/lib/ 或者 /usr/local/lib/ 去找。./:是指当前路径的意思 staticLibrary:是最后想生成的文件名(这里可随意起名字) */

动态库的制作
gcc -shared -fpic hello.c -o libhello.so -shared 指定生成动态库 -fpic :fPIC选项作用于编译阶段,在生成目标文件时就得使用该选项,以生成位置无关的代码。

动态库的使用
gcc main.c -lhello -L ./ -o dynamicDepot

/* main.c:是指main主函数 -lhello:是我们生成的.so 文件砍头去尾(lib不要 .so也不要)前面加-l -L:是指告诉gcc编译器先从-L指定的路径去找静态库,默认是从/usr/lib/ 或者 /usr/local/lib/ 去找。 ./:是指当前路径的意思 dynamicDepot:是最后想生成的文件名(这里可随意起名字) */

区别

  • 静态库代码装载的速度快,执行速度略比动态库快。

  • 动态库更加节省内存,可执行文件体积比静态库小很多。

  • 静态库是在编译时加载,动态库是在运行时加载。

  • 生成的静态链接库,Windows下以.lib为后缀,Linux下以.a为后缀。生成的动态链接库,Windows下以.dll为后缀,Linux下以.so为后缀。

7.简述GDB常见的调试命令,什么是条件断点,多进程下如何调试

GDB调试:gdb调试的是可执行文件,在gcc编译时加入 -g ,告诉gcc在编译时加入调试信息,这样gdb才能调试这个被编译的文件 gcc -g tesst.c -o test

GDB命令格式:

quit:退出gdb,结束调试

list:查看程序源代码

list 5,10:显示5到10行的代码

list test.c:5, 10: 显示源文件5到10行的代码,在调试多个文件时使用

list get_sum: 显示get_sum函数周围的代码

list test,c get_sum: 显示源文件get_sum函数周围的代码,在调试多个文件时使用

reverse-search:字符串用来从当前行向前查找第一个匹配的字符串

run:程序开始执行

help list/all:查看帮助信息

break:设置断点

break 7:在第七行设置断点

break get_sum:以函数名设置断点

break 行号或者函数名 if 条件:以条件表达式设置断点

watch 条件表达式:条件表达式发生改变时程序就会停下来

next:继续执行下一条语句 ,会把函数当作一条语句执行

step:继续执行下一条语句,会跟踪进入函数,一次一条的执行函数内的代码

条件断点:break if 条件 以条件表达式设置断点

多进程下如何调试:用set follow-fork-mode child 调试子进程
或者set follow-fork-mode parent 调试父进程

8.说说进程调度算法有哪些

进程调度基础

调度程序:操作系统选择一个进程切换到运行状态占用 CPU运行

进程调度时机
当进程从一个运行状态到另外一状态变化的时候,其实会触发一次调度

硬件时钟提供某个频率的周期性中断的方式,把调度算法分为两类

  • 非抢占式调度算法挑选一个进程,然后让该进程运行直到被阻塞,或者直到该进程退出,才会调用另外一个进程,也就是说不会理时钟中断这个事情。
  • 抢占式调度算法挑选一个进程,然后让该进程只运行某段时间,如果在该时段结束时,该进程仍然在运行时,则会把它挂起,接着调度程序从就绪队列挑选另外一个进程。这种抢占式调度处理,需要在时间间隔的末端发生时钟中断,以便把 CPU 控制返回给调度程序进行调度,也就是常说的时间片机制

调度原则image
目的就是要使得进程要「快」

先来先服务调度算法
image
先来后到,每次从就绪队列选择最先进入队列的进程,然后一直运行,直到进程退出或被阻塞,才会继续从队列中选择第一个进程接着运行。

FCFS 对长作业有利,适用于 CPU 繁忙型作业的系统,而不适用于 I/O 繁忙型作业的系统。
短作业(进程)优先调度算法

最短作业优先调度算法
image
优先选择运行时间最短的进程来运行,这有助于提高系统的吞吐量。

使得长作业不断的往后推,周转时间变长,致使长作业长期不会被运行。

高响应比优先调度算法image
每次进行进程调度时,先计算「响应比优先级」,然后把「响应比优先级」最高的进程投入运行
一个进程要求服务的时间不可预知,高响应比优先调度算法是「理想型」的调度算法,现实中是实现不了的。

时间片轮转法image
每个进程被分配一个时间段,称为时间片(Quantum),即允许该进程在该时间段中运行。

如果时间片用完,进程还在运行,那么将会把此进程从 CPU 释放出来,并把 CPU 分配给另外一个进程;
如果该进程在时间片结束前阻塞或结束,则 CPU 立即进行切换
缺点:如果时间片设得太短会导致过多的进程上下文切换,降低了 CPU 效率;如果设得太长又可能引起对短作业进程的响应时间变长。

多级反馈队列调度算法image
「多级」表示有多个队列,每个队列优先级从高到低,同时优先级越高时间片越短。
「反馈」表示如果有新的进程加入优先级高的队列时,立刻停止当前正在运行的进程,转而去运行优先级高的队列;

  • 设置了多个队列,赋予每个队列不同的优先级,每个队列优先级从高到低,同时优先级越高时间片越短;

  • 新的进程会被放入到第一级队列的末尾,按先来先服务的原则排队等待被调度,如果在第一级队列规定的时间片没运行完成,则将其转入到第二级队列的末尾,以此类推,直至完成;

  • 当较高优先级的队列为空,才调度较低优先级的队列中的进程运行。如果进程运行时,有新进程进入较高优先级的队列,则停止当前运行的进程并将其移入到原队列末尾,接着让较高优先级的进程运行;

  • 可以发现,对于短作业可能可以在第一级队列很快被处理完。对于长作业,如果在第一级队列处理不完,可以移入下次队列等待被执行,虽然等待的时间变长了,但是运行时间也会更长了,所以该算法很好的兼顾了长短作业,同时有较好的响应时间。

9.简述操作系统如何申请以及管理内存的(虚拟内存、物理内存)

物理内存:物理内存有四个层次,分别是寄存器、高速缓存、主存、磁盘。

寄存器:速度最快、量少、价格贵。

高速缓存:次之。

主存:再次之。

磁盘:速度最慢、量多、价格便宜。image
操作系统内核会对物理内存进行管理,有一个部分称为内存管理器(memory manager),它的主要工作是有效的管理内存,记录哪些内存是正在使用的,在进程需要时分配内存以及在进程完成时回收内存。

为什么使用虚拟内存
内存中同时运行两个程序,将会出现内存覆盖,擦掉第二个程序存放在相同位置上的所有内容。两个程序都引用了绝对物理地址image

虚拟内存解决方案
image

进程所使用的地址「隔离」,操作系统为每个进程分配独立的一套「虚拟地址」,每个进程都不能访问物理地址,由操作系统负责将不同进程的虚拟地址和不同内存的物理地址映射起来

操作系统引入了虚拟内存,进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存,

使用虚拟内存的好处

扩大地址空间。每个进程独占一个4G空间,虽然真实物理内存没那么多。

内存保护:防止不同进程对物理内存的争夺和践踏,可以对特定内存地址提供写保护,防止恶意篡改。

可以实现内存共享,方便进程通信。

使用虚拟内存的缺点

虚拟内存需要额外构建数据结构,占用空间。

虚拟地址到物理地址的转换,增加了执行时间。

页面换入换出耗时

一页如果只有一部分数据,浪费内存。

10.操作系统是如何管理虚拟地址与物理地址之间的关系

内存分段和内存分页image

虚拟地址分成段选择因子和段内偏移量,段选择因子段号对应段表项,段表项的段基地址 + 段内偏移量就是实际物理地址

缺点: 内存碎片(外部内存碎片,分配完之后产生的碎片分散,总和足够但无法占用
解决: 内存交换(效率低),产生了外部内存碎片,内存交换需要把一大段连续的内存数据写到硬盘上,硬盘的访问速度要比内存慢太多了

内存分页 虚拟和物理内存空间切成一段段固定尺寸的大小的页,Linux 下,每一页的大小为 4KB,虚拟地址与物理地址之间通过页表来映射
页表是存储在内存里的,内存管理单元 (MMU)就做将虚拟内存地址转换成物理地址的工作 image
虚拟地址分为页号和页内偏移页号,作为页表的索引,页表由虚拟页号和物理页号,页表包含物理页每页所在物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存地址

  • 把虚拟内存地址,切分成页号和偏移量;

  • 根据页号,从页表里面,查询对应的物理页号;

  • 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。

缺点
内分碎片
内存分页机制分配内存的最小单位是一页,页与页之间是紧密排列的,不会有外部碎片,即使程序不足一页大小,我们最少只能分配一个页,所以会有内部内存碎片的现象

内存交换效率
内存空间不够,「最近没被使用」的内存页面给释放掉,换出在硬盘上,需要再加载进内存,一次性写入磁盘的也只有少数的一个页或者几个页内存交换的效率就相对比较高。只有在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去。

多级页表、TLB、段页式内存管理(略)

  • 页表占用的内存空间更少了,提高空间,导致 CPU 在寻址的过程中,需要有很多层表参与,加大了时间上的开销

  • 负责缓存最近常被访问的页表项,大大提高了地址的转换速度

  • 但提高了内存的利用率

11.简述Linux系统态与用户态,什么时候会进入系统态 以及虚拟内存空间与内存分布image

内核作用image
计算机是由各种外部硬件设备组成的,应用都要和这些硬件设备对接通信协议,让内核作为应用连接硬件设备的桥梁,应用程序只需关心与内核交互,不用关心硬件的细节。

内核的四个能力

  • 进程与硬件设备之间提供通信能力

  • 决定内存的分配和回收,也就是内存管理

  • 决定哪个进程、线程使用 CPU调度

  • 系统调用,它是用户程序与操作系统之间的接口

内核态与用户态:内核态(系统态)与用户态是操作系统的两种运行级别。内核态拥有最高权限,可以访问所有系统指令;用户态则只能访问一部分指令。

内核程序执行在内核态,用户程序执行在用户态。当应用程序使用系统调用时,会产生一个中断。发生中断后, CPU 会中断当前在执行的用户程序,转而跳转到中断处理程序,也就是开始执行内核程序。内核处理完后,主动触发中断,把 CPU 执行权限交回给用户程序,回到用户态继续工作。

为什么区分内核态与用户态:在CPU的所有指令中,有一些指令是非常危险的,如果错用,将导致整个系统崩溃。比如:清内存、设置时钟等。所以区分内核态与用户态主要是出于安全的考虑。

虚拟地址空间分布
虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同位数的系统,地址空间的范围也不同

32 位系统的内核空间占用 1G,位于最高处,剩下的 3G 是用户空间;
image

  • 代码段,包括二进制可执行代码;

  • 数据段,包括已初始化的静态常量和全局变量

  • BSS 段,包括未初始化的静态变量和全局变量;

  • 堆段,包括动态分配的内存,从低地址开始向上增长;

  • 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关 (opens new window));

  • 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小;

  • 代码段下面还有一块区域是「保留区」,比较小数值的地址不是一个合法地址,我们通常在 C 的代码里会将无效的指针赋值为 NULL,这里会出现一段不可访问的内存保留区,防止程序因为出现 bug,导致读或写了一些小内存地址的数据

12.简述操作系统中的缺页中断

缺页异常:当 CPU 访问的页面不在物理内存时,便会产生一个缺页中断,请求操作系统将所缺页调入到物理内存。那它与一般中断的主要区别在于:

  • 缺页中断在指令执行「期间」产生和处理中断信号,而一般中断在一条指令执行「完成」后检查和处理中断信号。

  • 缺页中断返回到该指令的开始重新执行「该指令」,而一般中断返回回到该指令的「下一个指令」执行
    image

  • 在 CPU 里访问一条 Load M 指令,然后 CPU 会去找 M 所对应的页表项。

  • 如果该页表项的状态位是「有效的」,那 CPU 就可以直接去访问物理内存了,如果状态位是「无效的」,则 CPU 则会发送缺页中断请求。

  • 操作系统收到了缺页中断,则会执行缺页中断处理函数,先会查找该页面在磁盘中的页面的位置。

  • 找到磁盘中对应的页面后,需要把该页面换入到物理内存中,但是在换入前,需要在物理内存中找空闲页,如果找到空闲页,就把页面换入到物理内存中。

  • 页面从磁盘换入到物理内存完成后,则把页表项中的状态位修改为「有效的」。

  • 最后,CPU 重新执行导致缺页异常的指令。

  • 找不到空闲页的话,就说明此时内存已满了,这时候,就需要「页面置换算法」选择一个物理页,如果该物理页有被修改过(脏页),则把它换出到磁盘,然后把该被置换出去的页表项的状态改成「无效的」,最后把正在访问的页面装入到这个物理页中。

页面置换算法:当出现缺页异常,需调入新页面而内存已满时,选择被置换的物理页面,也就是说选择一个物理页面换出到磁盘,然后把需要访问的页面换入到物理页。算法目标则是,尽可能减少页面的换入换出的次数

13.最近最久未使用LRU(内存页面置换算法)

发生缺页时,选择最长时间没有被访问的页面进行置换,通过「历史」的使用情况来推测要淘汰的页面

哈希表实现:当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。

在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。

缺点: 每次访问内存时都必须要更新「整个链表」,找到一个页面,删除它,然后把它移动到表头是一个非常费时的操作。

14.说说堆栈溢出是什么,会怎么样?

  • 堆栈溢出就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,导致数据越界。常指调用堆栈溢出,本质上一种数据结构的满溢情况。堆栈溢出可以理解为两个方面:堆溢出和栈溢出。

  • 堆溢出:比如不断的new 一个对象,一直创建新的对象,而不进行释放,最终导致内存不足。将会报错:OutOfMemory Error。

  • 栈溢出:一次函数调用中,栈中将被依次压入:参数,返回地址等,而方法如果递归比较深或进去死循环,就会导致栈溢出。将会报错:StackOverflow Error。

15.malloc 是如何分配内存的

brk() 系统调用从堆分配内存
mmap() 系统调用在文件映射区域分配内存

如果用户分配的内存小于 128 KB,则通过 brk() 申请内存;
如果用户分配的内存大于 128 KB,则通过 mmap() 申请内存;

只用mmap, mmap 分配的内存每次释放的时候,都会归还给操作系统每次都会发生运行态的切换,还会发生缺页中断(在第一次访问虚拟地址后),这样会导致 CPU 消耗较大,用完会归还操作系统

只用brk小块内存,堆内将产生越来越多不可用的碎片,导致“内存泄露”malloc 通过 brk() 方式申请的内存,free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用;当进程再次申请 1 字节的内存时就可以直接复用,这样速度快了很多。

16.32位系统能访问4GB以上的内存吗?

正常情况下是不可以的。原因是计算机使用二进制,每位数只有0或1两个状态,32位正好是2的32次方,正好是4G,所以大于4G就没办法表示了,而在32位的系统中,因其它原因还需要占用一部分空间,所以内存只能识别3G多。要使用4G以上就只能换64位的操作系统了。

但是使用PAE技术就可以实现 32位系统能访问4GB以上的内存。

17.请你说说并发和并行

并发:对于多个CPU,多个进程同时运行

并发:1 秒钟期间,多个程序、交替执行,产生并行的错觉image
一个读取硬盘数据的程序,当进程要从硬盘读取数据时,CPU 不需要阻塞等待数据的返回,而是去执行另外的进程。当硬盘数据返回时,CPU 会收到个中断,于是 CPU 再继续运行这个进程。

18.说说进程有多少种状态image

进程有五种状态:创建、就绪、执行、阻塞、终止。一个进程创建后,被放入队列处于就绪状态,等待操作系统调度执行,执行过程中可能切换到阻塞状态(并发),任务完成后,进程销毁终止。

创建状态 一个应用程序从系统上启动,首先就是进入创建状态,需要获取系统资源创建进程管理块(PCB:Process Control Block)完成资源分配。

进程的控制结构PCB:来描述进程的数据结构,PCB 是进程存在的唯一标识,这意味着一个进程的存在,必然会有一个 PCB,如果进程消失了,那么 PCB 也会随之消失。

  • 进程描述信息:进程和用户描述符

  • 进程控制信息:进程当前状态

  • 资源分配清单: 有关虚拟地址空间和io文件信息

  • CPU:保存寄存器信息以便中断返回

  • PCB组成:链表把具有相同状态的进程链在一起,组成各种队列
    因为可能面临进程创建,销毁等调度导致进程状态发生变化,所以链表能够更加灵活的插入和删除。

就绪状态 在创建状态完成之后,进程已经准备好,处于就绪状态,但是还未获得处理器资源,无法运行。

运行状态 获取处理器资源,被系统调度,当具有时间片开始进入运行状态。如果进程的时间片用完了就进入就绪状态。

阻塞状态 在运行状态期间,如果进行了阻塞的操作,如耗时的I/O操作,此时进程暂时无法操作就进入到了阻塞状态,在这些操作完成后就进入就绪状态。等待再次获取处理器资源,被系统调度,当具有时间片就进入运行状态。

终止状态 进程结束或者被系统终止,进入终止状态

挂起状态:描述进程没有占用实际的物理内存空间的情况

  • 阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现

  • 就绪挂起状态:进程在外存(硬盘),但只要进入内存,即刻立刻运行

  • 物理内存空间是有限的,被阻塞状态的进程占用着物理内存就一种浪费物理内存的行为,通常会把阻塞状态的进程的物理内存空间换出到硬盘,等需要再次运行的时候,再从硬盘换入到物理内存。

19.进程的控制(太复杂略)

进程的状态变迁和进程的数据结构 PCB了解之后,可以了解具体过程
进程的上下文切换

20.进程、线程、协程是什么,区别是什么

线程:线程是进程当中的一条执行流程。同一个进程内多个线程之间可以共享代码段、数据段、打开的文件等资源,但每个线程各自都有一套独立的寄存器和栈,这样可以确保线程的控制流是相对独立的。image
线程的上下文切换:当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据;

协程::轻量级线程

  • 协程调用跟切换比线程效率高:协程执行效率极高。协程不需要多线程的锁机制,可以不加锁的访问全局变量,所以上下文的切换非常快。

  • 协程占用内存少:执行协程只需要极少的栈内存(大概是4~5KB),而默认情况下,线程栈的大小为1MB。

  • 切换开销更少:协程直接操作栈基本没有内核切换的开销,所以切换开销比线程少。

进程、线程、协程是什么,区别是什么

  • 进程是资源(包括内存、打开的文件等)分配的单位,线程是 CPU 调度的单位;

  • 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈;

  • 线程同样具有就绪、阻塞、执行三种基本状态,同样具有状态之间的转换关系;

  • 线程能减少并发执行的时间和空间开销;

  • 线程的创建时间比进程快,因为进程在创建的过程中,还需要资源管理信息,比如内存管理信息、文件管理信息,而线程在创建的过程中,不会涉及这些资源管理信息,而是共享它们;

  • 线程的终止时间比进程快,因为线程释放的资源相比进程少很多;

  • 同一个进程内的线程切换比进程切换快,因为线程具有相同的地址空间(虚拟内存共享),这意味着同一个进程的线程都具有同一个页表,那么在切换的时候不需要切换页表。而对于进程之间的切换,切换的时候要把页表给切换掉,而页表的切换过程开销是比较大的;

  • 由于同一进程的各线程间共享内存和文件资源,那么在线程之间数据传递的时候,就不需要经过内核了,这就使得线程之间的数据交互效率更高了;

  • 所以,不管是时间效率,还是空间效率线程比进程都要高。

21.有了进程,为什么还要有线程

原因

进程在早期的多任务操作系统中是基本的执行单元。每次进程切换,都要先保存进程资源然后再恢复,这称为上下文切换。但是进程频繁切换将引起额外开销,从而严重影响系统的性能。为了减少进程切换的开销,人们把两个任务放到一个进程中,每个任务用一个更小粒度的执行单元来实现并发执行,这就是线程

线程与进程对比

  • 进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。但多个线程共享进程的内存,如代码段、数据段、扩展段,线程间进行信息交换十分方便。

  • 调用 fork() 来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着 fork() 调用在时间上的开销依然不菲。

但创建线程比创建进程通常要快 10 倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。

22.说说进程通信的方式有哪些

进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存)、套接字socket。

管道:包括无名管道和命名管道,无名管道半双工(复制文件描述符),只能用于具有亲缘关系的进程直接的通信(父子进程或者兄弟进程),可以看作一种特殊的文件;命名管道可以允许无亲缘关系进程间的通信(专门的设备文件)

缺点:

  • 就是内核里面的一串缓存,写缓存在内核中的,从内核中读取这段数据
  • 管道传输的数据是无格式的流且大小受限
  • 通信方式效率低,不适合进程间频繁地交换数据,
  • 简单,得知管道里的数据已经被另一个进程读取了

系统IPC
消息队列:消息的链接表,放在内核中。消息队列独立于发送与接收进程,进程终止时,消息队列及其内容并不会被删除;消息队列可以实现消息的随机查询,可以按照消息的类型读取。
消息队列是保存在内核中的消息链表,用户自定义的一个个固定大小数据块,消息队列生命周期随内核

  • 不适合比较大数据的传输,内核中消息体和消息体队列长度都有上限
  • 消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销
  • 写入数据到内核中的消息队列——从用户态拷贝数据到内核态
  • 读取数据内核态拷贝数据到用户态
  • 通信不及时
  • 大小限制

信号量semaphore:是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步。
可以实现同步信号量和互斥信号量

信号:用于通知接收进程某个事件的发生。
说说常见信号有哪些,表示什么含义

  • 2 SIGINT 程序中止信号,用于中止前台进程。相当于输出 Ctrl+C 快捷键
  • 9 SIGKILL 用来立即结束程序的运行。本信号不能被阻塞、处理和忽略。般用于强制中止进程
  • 15 SIGTERM 正常结束进程的信号,kill 命令的默认信号。如果进程已经发生了问题,那么这 个信号是无法正常中止进程的,这时我们才会尝试 SIGKILL 信号,也就是信号 17 SIGCHLD 子进程结束时, 父进程会收到这个信号。

异步通信机制 因为可以在任何时候发送信号给某一进程
信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令)

  • 1.执行默认操作。Linux 对每种信号都规定了默认操作,例如,上面列表中的 SIGTERM 信号,就是终止进程的意思。
  • 2.捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。
  • 3.忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL(Cltr+C) 和 SEGSTOP(Cltr+Z),它们用于在任何时候中断或结束某一进程

内存共享:使多个进程访问同一块内存空间。
共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。

套接字socket:用于不同主机直接的通信。

23.说说进程同步的方式

信号量semaphore:是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步。P操作(递减操作)可以用于阻塞一个进程,V操作(增加操作)可以用于解除阻塞一个进程。

管道:一个进程通过调用管程的一个过程进入管程。在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。

消息队列:消息的链接表,放在内核中。消息队列独立于发送与接收进程,进程终止时,消息队列及其内容并不会被删除;消息队列可以实现消息的随机查询,可以按照消息的类型读取。

24.说说线程间通信的方式有哪些(线程安全)

互斥: 多线程相互竞争操作共享变量,在执行过程中发生了上下文切换,输出的结果存在不确定性

同步: 并发进程/线程在一些关键点上可能需要互相等待与互通消息

同步与互斥区别:

  • 同步就好比:「操作 A 应在操作 B 之前执行」,「操作 C 必须在操作 A 和操作 B 都完成之后才能执行」等;

  • 互斥就好比:「操作 A 和操作 B 不能在同一时刻执行」;

临界区:每个线程中访问临界资源的那段代码称为临界区(Critical Section)(临界资源是一次仅允许一个线程使用的共享资源)。每次只准许一个线程进入临界区,进入后不允许其他线程进入。不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它进行访问。

互斥锁:采用互斥对象机制,只有拥有互斥对象的线程才可以访问。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。

信号量
概念:信号量本质上是一个计数器,用于多进程对共享数据对象的读取,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。

原理:由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),具体的行为如下:

  • P(sv)操作:如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行(信号量的值为正,进程获得该资源的使用权,进程将信号量减1,表示它使用了一个资源单位)。

  • V(sv)操作:如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1(若此时信号量的值为0,则进程进入挂起状态,直到信号量的值大于0,若进程被唤醒则返回至第一步)。
    作用:用于多进程对共享数据对象的读取,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。

互斥量能不能在进程中使用
能。
不同的进程之间,存在资源竞争或并发使用的问题,所以需要互斥量。
进程中也需要互斥量,因为一个进程中可以包含多个线程,线程与线程之间需要通过互斥的手段进行同步,避免导致共享数据修改引起冲突。可以使用互斥锁,属于互斥量的一种。

条件变量:通过条件变量通知操作的方式来保持多线程同步。

读写锁:读写锁与互斥量类似。但互斥量要么是锁住状态,要么就是不加锁状态。读写锁一次只允许一个线程写,但允许一次多个线程读,这样效率就比互斥锁要高。
读写锁区分读者和写者,而互斥锁不区分,互斥锁同一时间只允许一个线程访问该对象,无论读写;读写锁同一时间内只允许一个写者,但是允许多个读者同时读对象。

自旋锁:一直自旋,利用 CPU 周期,直到锁可用。在单处理器上,需要抢占式的调度器(即不断通过时钟中断一个线程,运行其他线程)。否则,自旋锁在单 CPU 上无法使用,因为一个自旋的线程永远不会放弃 CPU。

25.说说什么是死锁,产生的条件,如何解决

两个线程为了保护两个不同的共享资源而使用了两个互斥锁,可能会造成两个线程都在等待对方释放锁,发生了死锁

必须满足以下四个条件:
互斥条件: 如果线程 A 已经持有的资源,不能再同时被线程 B 持有,如果线程 B 请求获取线程 A 已经占用的资源,那线程 B 只能等待,直到线程 A 释放了资源。
持有并等待条件: 持有并等待条件是指,当线程 A 已经持有了资源 1,又想申请资源 2,而资源 2 已经被线程 C 持有了,所以线程 A 就会处于等待状态,但是线程 A 在等待资源 2 的同时并不会释放自己已经持有的资源
不可剥夺条件:不可剥夺条件是指,当线程已经持有了资源 ,在自己使用完之前不能被其他线程获取,线程 B 如果也想使用此资源,则只能在线程 A 使用完并释放后才能获取。
环路等待条件,线程 A 已经持有资源 2,而想请求资源 1, 线程 B 已经获取了资源 1,而想请求资源 2,这就形成资源请求等待的环形图。

解决:

  • 资源一次性分配,从而解决请求保持的问题

  • 可剥夺资源:当进程新的资源未得到满足时,释放已有的资源;

  • 资源有序分配:资源按序号递增,进程请求按递增请求,释放则相反

26.请你说说Linux的fork的作用

  • fork函数用来创建一个子进程。对于父进程,fork()函数返回新创建的子进程的PID。
  • 对于子进程,fork()函数调用成功会返回0。
  • 如果创建出错,fork()函数返回-1。

fork()函数,其原型如下:
#include <unistd.h> pid_t fork(void);

fork()函数创建一个新进程后,会为这个新进程分配进程空间,将父进程的进程空间中的内容复制到子进程的进程空间中,包括父进程的数据段和堆栈段,并且和父进程共享代码段。这时候,子进程和父进程一模一样,都接受系统的调度。因为两个进程都停留在fork()函数中,最后fork()函数会返回两次,一次在父进程中返回,一次在子进程中返回,两次返回的值不一样,如上面的三种情况。

27.请你说说什么是孤儿进程,什么是僵尸进程,如何解决僵尸进程

孤儿进程:是指一个父进程退出后,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并且由init进程对它们完整状态收集工作。

僵尸进程:是指一个进程使用fork函数创建子进程,如果子进程退出,而父进程并没有调用wait()或者waitpid()系统调用取得子进程的终止状态,那么子进程的进程描述符仍然保存在系统中,占用系统资源,这种进程称为僵尸进程。

如何解决僵尸进程
一般,为了防止产生僵尸进程,在fork子进程之后我们都要及时使用wait系统调用;同时,当子进程退出的时候,内核都会给父进程一个SIGCHLD信号,所以我们可以建立一个捕获SIGCHLD信号的信号处理函数,在函数体中调用wait(或waitpid),就可以清理退出的子进程以达到防止僵尸进程的目的

使用kill命令: 打开终端并输入下面命令:
ps aux | grep Z 会列出进程表中所有僵尸进程的详细内容。
然后输入命令: kill -s SIGCHLD pid(父进程pid)

28.请你说说什么是守护进程,如何实现?

守护进程:守护进程是运行在后台的一种生存期长的特殊进程。它独立于控制终端,处理一些系统级别任务

如何实现

  • 创建子进程,终止父进程。方法是调用fork() 产生一个子进程,然后使父进程退出。

  • 调用setsid() 创建一个新会话。

  • 将当前目录更改为根目录。使用fork() 创建的子进程也继承了父进程的当前工作目录。

  • 重设文件权限掩码。文件权限掩码是指屏蔽掉文件权限中的对应位。

  • 关闭不再需要的文件描述符。子进程从父进程继承打开的文件描述符。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#define MAXFILE 65535
int main(){
//第一步:创建进程
int pid = fork();
if (pid > 0)
exit(0);//结束父进程
else if (pid < 0){
printf("fork error!\n");
exit(1);//fork失败,退出
}
//第二步:子进程成为新的会话组长和进程组长,并与控制终端分离
setsid();
//第三步:改变工作目录到
chdir("/");
//第四步:重设文件创建掩模
umask(0);
//第五步:关闭打开的文件描述符
for (int i=0; i<MAXFILE; ++i)
close(i);
sleep(2);
}
return 0;
}

29.单核机器上写多线程程序,是否要考虑加锁,为什么

在单核机器上写多线程程序,仍然需要线程锁。

原因:因为线程锁通常用来实现线程的同步和通信。在单核机器上的多线程程序,仍然存在线程同步的问题。因为在抢占式操作系统中,通常为每个线程分配一个时间片,当某个线程时间片耗尽时,操作系统会将其挂起,然后运行另一个线程。如果这两个线程共享某些数据,不使用线程锁的前提下,可能会导致共享数据修改引起冲突。

30.进程、线程的中断切换的过程是怎样的?

上下文切换指的是内核(操作系统的核心)在CPU上对进程或者线程进行切换。
进程上下文切换

  • 保护被中断进程的处理器现场信息

  • 修改被中断进程的进程控制块有关信息,如进程状态等

  • 把被中断进程的进程控制块加入有关队列

  • 选择下一个占有处理器运行的进程

  • 根据被选中进程设置操作系统用到的地址转换和存储保护信息

  • 切换页目录以使用新的地址空间
  • 切换内核栈和硬件上下文(包括分配的内存,数据段,堆栈段等)
  • 根据被选中进程恢复处理器现场

线程上下文切换

  • 保护被中断线程的处理器现场信息

  • 修改被中断线程的线程控制块有关信息,如线程状态等

  • 把被中断线程的线程控制块加入有关队列

  • 选择下一个占有处理器运行的线程

  • 根据被选中线程设置操作系统用到的存储保护信息

  • 切换内核栈和硬件上下文(切换堆栈,以及各寄存器)

  • 根据被选中线程恢复处理器现场

31.说说多线程和多进程的不同

  • 多线程从属于一个进程,单线程也从属于一个进程;一个线程挂掉都会导致从属的进程挂掉。

  • 一个进程里有多个线程,可以并发执行多个任务;一个进程里只有一个线程,就只能执行一个任务。

  • 多线程并发执行多任务,需要切换内核栈与硬件上下文,有切换的开销;单线程不需要切换,没有切换的开销。

  • 多线程并发执行多任务,需要考虑同步的问题;单线程不需要考虑同步的问题。

  • 多线程编程需要考虑同步的问题。线程间的同步方式包括互斥锁、信号量、条件变量、读写锁。

  • 多线程加锁,主要需要注意死锁的问题。破坏死锁的必要条件从而避免死锁。

32.说说sleep和wait的区别

sleep是一个延时函数,让进程或线程进入休眠。休眠完毕后继续运行。
wait是父进程回收子进程PCB(Process Control Block)资源的一个系统调用。

wait是父进程回收子进程PCB资源的一个系统调用。进程一旦调用了wait函数,就立即阻塞自己本身,然后由wait函数自动分析当前进程的某个子进程是否已经退出,当找到一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞,直到有一个出现为止。

子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一起返回。如果不需要结束状态值,则参数status可以设成 NULL。

33.说说线程池的设计思路,线程池中线程的数量由什么确定

设计思路

  • 设置一个生产者消费者队列,作为临界资源。
  • 初始化n个线程,并让其运行起来,加锁去队列里取任务运行
  • 当任务队列为空时,所有线程阻塞
  • 当生产者队列来了一个任务后,先对队列加锁,把任务挂到队列上,然后使用条件变量去通知阻塞中的一个线程来处理。

线程池中线程数量
如果是CPU密集型应用,则线程池大小设置为:CPU数目+1
如果是IO密集型应用,则线程池大小设置为:2CPU数目+1 最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1) CPU数目

为什么要创建线程池:

创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。同时线程池也是为了提升系统效率。

线程池的核心线程与普通线程

任务队列可以存放100个任务,此时为空,线程池里有10个核心线程,若突然来了10个任务,那么刚好10个核心线程直接处理;若又来了90个任务,此时核心线程来不及处理,那么有80个任务先入队列,再创建核心线程处理任务;若又来了120个任务,此时任务队列已满,不得已,就得创建20个普通线程来处理多余的任务。 以上是线程池的工作流程。

34.简述Linux零拷贝的原理?

  • 早期 I/O 操作,内存与磁盘的数据传输的工作都是由 CPU 完成的,而此时 CPU 不能执行其他任务,会特别浪费 CPU 资源。

  • 于是,为了解决这一问题,DMA 技术就出现了,每个 I/O 设备都有自己的 DMA 控制器,通过这个 DMA 控制器,CPU 只需要告诉 DMA 控制器,我们要传输什么数据,从哪里来,到哪里去,就可以放心离开了。后续的实际数据传输工作,都会由 DMA 控制器来完成,CPU 不需要参与数据传输的工作。

  • 传统 IO 的工作方式,从硬盘读取数据,然后再通过网卡向外发送,我们需要进行 4 上下文切换,和 4 次数据拷贝,其中 2 次数据拷贝发生在内存里的缓冲区和对应的硬件设备之间,这个是由 DMA 完成,另外 2 次则发生在内核态和用户态之间,这个数据搬移工作是由 CPU 完成的。

  • 为了提高文件传输的性能,于是就出现了零拷贝技术,它通过一次系统调用(sendfile 方法)合并了磁盘读取与网络发送两个操作,降低了上下文切换次数。另外,拷贝数据都是发生在内核中的,天然就降低了数据拷贝的次数。

  • Kafka 和 Nginx 都有实现零拷贝技术,这将大大提高文件传输的性能。

  • 零拷贝技术是基于 PageCache 的,PageCache 会缓存最近访问的数据,提升了访问缓存数据的性能,同时,为了解决机械硬盘寻址慢的问题,它还协助 I/O 调度算法实现了 IO 合并与预读,这也是顺序读比随机读性能好的原因。这些优势,进一步提升了零拷贝的性能。

  • 另外,当传输大文件时,不能使用零拷贝,因为可能由于 PageCache 被大文件占据,而导致「热点」小文件无法利用到 PageCache,并且大文件的缓存命中率不高,这时就需要使用「异步 IO + 直接 IO 」的方式。

35.说说多路IO复用技术有哪些,区别是什么?

36.简述socket中select,epoll的使用场景和区别,epoll水平触发与边缘触发的区别

37.说说Reactor、Proactor模式

38.简述同步与异步的区别,阻塞与非阻塞的区别

39.BIO、NIO有什么区别?

40.请介绍一下5种IO模型

41.请说一下socket网络编程中客户端和服务端用到哪些函数?

42.哲学家 读写者 常见同步问题

本文作者:Gal0721

本文链接:https://www.cnblogs.com/Gal0721/p/17746447.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   游客0721  阅读(25)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 優しい光 水谷広実
  2. 2 ひだまりの中で SONO MAKERS,鈴丸
  3. 3 白い吐息 MANYO
  4. 4 夏の子守歌 折戸伸治
ひだまりの中で - SONO MAKERS,鈴丸
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.