cocolord / InterviewFAQ-Linux

总结操作系统及Linux的面试常见问题

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

InterviewFAQ-Linux

Linux

常用命令

  • 编辑相关
    • awk
      • NF:字段总数
      • NR:第几行数据
      • FS:分隔字符
    • sed
      • -n
      • -i 直接修改
      • 4a:在第四行后添加
      • 4i:在第四行前插入
      • 1,5c sting:用sting替换1到5行的内容
      • s/要被替换的字符串/新的字符串/g
    • sort
      • -t
      • -nr sort |uniq -c |sort -nr
    • tr
      • -d:删除
      • [a-z] [A Z]:替换
  • 查看负载相关
    • top
      • load average cpu 里面的几个数字代表什么意思,怎么衡量,为什么
        • 系统在1,5,15分钟的平均工作负载,是进程队列中的平均进程数量。
        • 一般不能大于系统逻辑CPU的个数
        • /proc/loadavg
      • 关键参数
        • Task:僵尸进程的数量(一个子进程exit之后变成僵尸进程,直到被父进程回收资源)
        • CPU:%wa IOwait
      us, user : time running un-niced user processes sy, system : time running kernel processes ni, nice : time running niced user processes id, idle : time spent in the kernel idle handler wa, IO-wait : time waiting for I/O completion hi : time spent servicing hardware interrupts si : time spent servicing software interrupts st : time stolen from this vm by the hypervisor ``` - Mem: - Swap:要尽可能的少用
    • uptime
    • free:读取自文件:/proc/meminfo
      • buffer存放要写回到磁盘的数据
      • cache存放从磁盘上读出的数据
      • -buffers/cache,表示一个应用程序认为系统被用掉多少内存;被程序实实在在占用的内存
      • +buffers/cache,表示一个应用程序认为系统还有多少内存;可用的内存数。
    • vmstat:动态的了解系统资源运行
      • -d:磁盘
      • r:等待运行的进程数,r<5表示状态好
      • b:处于非中断睡眠状态的进程数,b≈0表示状态好
      • id:CPU闲置时间
      • 如果r经常大于3或4,且id经常小于50,表示CPU负荷很重
    • ps
      • aux
      • -l
    • lsof:列出被进程所打开的文件名
    • pwd
      • 首先获取当前目录的i节点编号,但是并不能知道当前目录的名称,我们切换到其的父目录,在里面寻找当前i节点编号对应的文件名即可。终止条件是"."和".."指向同一个i节点,我们可以以此判断是否发到达了根目录
    • pgrep
  • 查找
    • grep
      • -n
      • -v
      • -A
      • -B
    • find
      • 时间:
        • 4:4天前的那一天
        • +4:大于等于5天之前
        • -4:小于等于4天之内
      • -exec 命令 { } ;
  • 磁盘
    • du
      • -sh /
      • du -cks * | sort -rn | head -n 10
      • 评估目录所占容量,通过将指定文件系统中所有的目录、符号链接和文件使用的块数累加得到该文件系统使用的总块数
      • du命令是用户级的程序,它不考虑Meta Data,而df命令则查看文件系统的磁盘分配图并考虑Meta Data。
      • du以文件名、目录名为依据计算空间使用的,而df是以硬盘块使用情况来计算空间使用的。
      • -sm 以M为单位列出文件容量
    • df
      • 列出所有文件系统的整体磁盘使用量,通过读取块位图获取
      • dumpe2fs
  • 网络配置命令
    • netstat
      • -tlnp
    • ss
    • ping
    • traceroute
    • tcpdump
      • tcpdump ip host
      • tcpdump tcp port 25 and host 210.27.48.1
    • nslookup
    • dig
    • nmap
      • tcp端口扫描:-sT、-sP
      • TCP SYN端口扫描:-sS
        • nmap -sS 192.168.137.10 -255 -p 20,21,53-110,30000 --v
      • UDP端口扫描:-sU
      • TCP ACK扫描:-sA

Raid阵列

  • RAID 的各个级别及区别
    • RAID0:数据切片,分盘存储,性能最佳,风险最高
    • RAID1:镜像模式,完整备份,写性能差,利用率低
    • RAID10/RAID01
    • RAID5:循环写入,同位检查码,性能与备份的均衡考虑
    • RAID6:牺牲两块磁盘的容量做同位检查码

系统调优参数

  • /etc/sysctl.conf 这个文件有没有改过?列举一些常见的kernel参数和作用。
    • time_wait相关
      • net.ipv4.tcp_tw_reuse = 1:是否允许新的TCP连接重新应用处于time_wait状态的socket
      • net.ipv4.tcp_tw_recycle = 1:加速time_wait socket回收
      • net.ipv4.tcp_max_tw_buckets:time_wait套接字的最大数量,把time_wait所占用内存控制在一定范围
    • syn攻击相关
      • net.inet.tcp.syncookies = 1:开启syncookies功能,防止dos攻击,syn攻击
      • net.ipv4.tcp_synack_retries = 2:内核放弃连接之前发送SYN+ACK包的数量
      • net.ipv4.tcp_syn_retries = 2:新连接,内核放弃连接之前发送SYN包的数量
      • net.ipv4.tcp_max_syn_backlog = 65536:表示SYN队列的长度
    • 缓冲区
      • net.core.rmem_default:接收套接字缓冲区大小缺省值
      • net.core.wmem_default:发送套接字缓冲区大小缺省值
      • net.core.rmem_max:最大TCP接收缓冲区大小
      • net.core.wmem_max:最大TCP发送缓冲区大小
    • kern.ipc.somaxconn :并发连接数
    • net.core.netdev_max_backlog = 32768:进入包的最大设备队列

常见服务占用端口

  • 80(http) 8080 443
  • 20 21(ftp) 22 23 25 53
  • 135(RPC)137(NetBIOS/UDP) 138(UDP) 139 (samba)
  • 161 SNMP
  • 1080 Socket代理
  • 3306 11211 8080 jboss tomcat 50170

文件系统

  • (ext4)性能 安全性

  • 启动扇区 块组 超级块 inode表格 block 块对照表(Bitmap) inode对照表

    • 超级块
      • 记录整个文件系统的整体信息,包括inode(记录文件的权限与属性)与block(记录数据)总量、使用量、剩余量
    • inode表格 = inode + 存储block号码的block (ls -l命令)
    • inode本身不记录文件名,文件名的记录在目录的block中
    • 创建新的目录时,新目录的链接数是2(产生了/.),上层目录的链接数会增加1(产生了/..)
  • 读写文件会遇到的问题

    • 文件数据离散:文件很大、经常变动、无法写在连续的块中、机械臂移动大、
      • 复制出来、格式化、复制回去
    • 创建文件流程
      • 查询目录权限
      • 在日志记录块中记录准备写入的信息
      • 查询inode bitmap,向inode中写入权限和属性
      • 查询block bitmap,向block写入数据
      • 更新inode指向block
      • 更新inode bitmap和block bitmap 的状态,更新superblock内容
      • 在日志记录块中完成文件记录
    • 读文件失败
      • 块数据损坏
      • inode损坏:记录数据块号码的块损坏
    • 写文件失败
      • 文件描述符不够
      • 存储空间不够了(块不够、inode不够)
  • hdfs的一个block多大,为什么128M?

    • 不能远小于128M:减少硬盘寻道时间、减少Namenode内存消耗
    • 不能远大于128M:
      • Map崩溃问题 (数据块大,重新加载时间长)
      • 预设时间间隔问题(从数据块的角度大概估算,数据块越大,时间越长)
      • 问题分解问题:数据量大小和问题解决的复杂度成线性关系
      • 约束map输出:map之后的数据需要排序后再执行reduce,大文件不利于归并排序的**
  • ext4文件系统的block多大?

    • 4k
    • HDFS的块比磁盘块大,其目的是为了最小化寻址开销
  • 索引式文件系统:ext

  • 非索引式文件系统:FAT 碎片整理

  • cp/mv/rm的区别(实现)

    • cp
      • -a(pdr:连同文件属性一起、链接文件属性、递归)
      • -u(新才复制)
      • -l -s (复制为链接)
      • -d 复制链接文件时,默认复制的是源文件,除非加-d参数,才会复制链接文件
    • 当目标文件存在时,cp 命令并不是先删除已经存在的目标文件,而是将原目标文件内容清空后再写入。
    • mv 的主要功能就是检查初始文件和目标文件是否存在及是否有访问权限,之后执行 rename 系统调用,因而,当目标文件存在时,mv 的行为由 rename() 系统调用决定,即类似于删除文件后再重建一个同名文件。
    • 删除文件名是指在原目录下不再含有此文件名,并不一定删除磁盘上文件的内容。只有在文件的链接数为1,并且没有进程打开此文件的时候,unlink() 才会真正删除文件内容。
  • 软硬连接(inode这块,ln / ln -s)

    • 硬链接:一个inode节点对用不同的文件名,
      • 不创建新的inode,每增加一个硬链接,inode节点链接数加一
      • rm 硬链接:删除的只是文件名,对应的数据块只有在inode节点链接数减少为0的时候才会被系统回收。
      • 不能对目录创建硬链接,因为文件系统不能存在链接环,否则会导致文件便利操作的混乱(du,pwd等命令的运作原理就是基于文件硬链接)
      • 不能跨文件系统
      • 不能对不存在的文件创建硬链接
    • 软链接:如果目标路径名较短则直接保存在inode中,如果较长则分配一个block存储
      • 创建新的inode,指向的数据块存放着源文件的路径
      • 删除源文件,软链接失效
      • 可以对目录 创建软连接,遍历操作会忽略目录的软链接
      • 可以跨文件系统
      • 可以对不存在的文件创建软链接

开机启动过程

  • Mbr 与gpt的区别
  • BIOS、CMOS、MBR、Boot Loader、Grub2、Kernel、/sbin/init、/etc/init/*.conf、/ect/inittab、/etc/rc.d/rc.sysinit、/etc/rc.d/rc.$runleave
  • 双系统
    • 多重引导:MBR、各分区的启动扇区boot sector

###Shell常用脚本

  • 从日志文件里面筛选出符合要求的ip或者其他信息
    • cat logname | sort | uniq -c | sort -nr | head -n 10
  • 正则表达式匹配IP地址
    • [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1-3}\.{1,3}
    • ^((25[0-5]|2[0-4]\d|[1]{1}\d{1}\d{1}|[1-9]{1}\d{1}|\d{1})($|(?!\.$)\.)){4}$
    • ((25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))).){3}(25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))
  • 20G大小的文件,内容都是IP,有重复的,如何找出这里面的top N ?
    • 分表、哈希
  • 统计nginx日志出现次数最多的ip
    • awk '{print $1}' urllogfile | sort | uniq -c | sort -nr -k1 | head -n 10
  • 查看Web服务器(Nginx Apache)的并发请求数及其TCP连接状态
    • netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

Swap

  • swap分区是怎么设置的?
    • 使用物理分区构建swap:fdisk分区(改分区类型ID)、mkswap格式化、swapon启动、free查看、
    • 使用文件构建swap:dd、mkswap、swapon、free
  • 为什么要有swap分区,工作原理是什么?为什么云服务器上的swap没有开启?
    • 内存不足时,将内存中暂时不使用的程序与数据放置到swap中
    • 服务器休眠时,运行中的程序状态会被记录到swap
    • 某些程序运行时会利用swap的特性

Iptables

  • filter
    • INPUT
    • OUTPUT
    • FORWARD
  • nat
    • PREROUTING
    • OUTPUT
    • POSTROUTING
  • mangle
    • PREROUTING
    • INPUT
    • FORWERD
    • POSTROUTING
    • OUTPUT
  • 语法
    • iptables [-t 表名] <-A|I|D|R>链名 -i|o网卡名称 -p 协议类型 -s源IP --sport 源端口号 -d 目标IP地址 --dport 目标端口号 <-j 动作>
    • iptables -P INPUT DROP
    • iptables -A INPUT -m state --state NEW -j DROP
    • iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
    • iptables -A INPUT -p tcp -dport 445 -j ACCEPT
    • iptables -A INPUT -p tcp -m multiport --dports 22,80 -j ACCEPT
    • 只允许某个IP上网

Shell操作

  • find /data -type f -name "*.txt" | xargs sed -i 's/oldgirl/oldboy/g'
  • mkdir -p /data/oldboy && echo
  • 算术运算
    • $[]
    • $(())
    • $(expr a + b) 或者 expr a \* b :注意运算符两边要空格,且乘法符号要转义
  • 查看http的并发请求数与其TCP连接状态
    • netstat -tan | awk '/^tcp>/{split($5,ip,":");count[ip[1]]++}END{for(i in count) print i,count[i]}'
  • awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr -k1 | head -n 10
  • cat /dev/urandom | head -1 | md5sum | haed -c 5
  • watch -n 1 "/sbin/ifconfig eth0 | grep bytes"
  • find /opt -size +15k -exec mv {} /tmp/ ;
  • sed和awk
    • 如果文件是格式化的,即由分隔符分为多个域的,优先使用awk
    • awk适合按列(域)操作,sed适合按行操作
    • awk适合对文件的抽取整理,sed适合对文件的编辑。
  • 写一个脚本,该脚本能对标准的apache日志进行分析并统计出总的访问次数和每个访问ip的访问次数,按访问次数列出前5名?
  • 显示/test下所有目录
    • ls -d */
    • find . -type d -maxdepth 1
    • ls -F | grep '/$'
    • ls -l | grep '^d' | awk '{print $9}'
  • 将文件/etc/a 下中除了 b文件外的所有文件压缩打包放到/home/a下,名字为a.tar.gz
    • tar -exclude /etc/a/b -Pcvfz /home/a/a.tar.gz /etc/a
  • 如何查看某进程打开的所有文件
    • lsof -p `ps -ef | grep crond | awk '{print $2}'`
  • 获取网卡eth0的80端口的数据包信息,找出访问最高的
    • tcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F "." '{print $1"."$2"."$3"."$4"."}'|sort|uniq -c|sort -nr|head -5
  • 查看/var/log目录下的文件数
    • ls /var/log -1R | grep "-" | wc -l
  • 查看Linux系统每个IP的连接数
    • netstat -n | awk '/^tcp/{print $5}' | awk -F":" ’{print $1}' | sort | uniq -c | sort -rn
  • 用iptables控制来自192.168.1.2主机的80端口请求
    • iptables -A INPUT -p tcp -s 192.168.1.2 -dport 80 -j ACCEPT
  • Linux如何挂载Windows下的共享目录
    • mount .cifs //IP地址/server /mnt/server -o user=username,password=123
  • 生成32位随机密码
    • cat /dev/urandom | head -1 | md5sum | head -c 32
  • 密码加密
    • echo abc | openssl md5
    • echo abc | openssl base64
    • echo abc | openssl sha
  • ps aux 中的VSZ代表什么意思,RSS代表什么意思?
    • VSZ:虚拟内存集,进程占用的虚拟内存空间
    • RSS:物理内存集,进程占用的实际物理内存空间
  • 修改内核参数
    • vi /etc/sysctl.conf
    • sysctl -p
  • 取0-39随机数
    • expr $[RANDOM%39] + 1 # 注意操作符两边的空格
  • 限制apache每秒新建连接数为1,峰值为3
    • iptables -A INPUT -d 172.16.100.1 -p tcp -dport 80 -m limit -limit 1/second -j ACCEPT
  • 怎么把脚本添加到系统服务里,即用service来调用?
    • 脚本里添加
      • #!/bin/bsh
      • # chkconfig: 345 85 15
      • # description: httpd
    • chkconfig httpd -add
    • service start httpd
  • 按修改时间排序显示目录中的文件
    • ls -lrt /etc
  • 打印文件的权限值
    • stat -c %a /etc/inittab
  • 查看 ARP 缓存记录的命令是?
    • “arp –a”
  • 软件工具的原则
    • 一次做好一件事
    • 处理文本行,不要处理二进制数据
    • 使用正则表达式
    • 默认使用标准输入、输出
    • 避免喋喋不休
    • 输出格式必须与可接受的输出格式一致
    • 让工具去做困难的部分
    • 构建特定工具前,先想想
  • 获取密码
    • printf "Enter new password:"
    • stty -echo
    • read pass < /dev/tty
    • printf "Enter again:"
    • read pass2 < /dev/tty
    • stty echo
  • 在程序中执行跟踪:
    • set -x:打开跟踪功能
    • set +x:关闭跟踪功能
  • 为/home/qiuye目录结构建立一份副本在/home/qy下
    • find /home/qiuye -type -d -print | sed 's;/home/qiuye/;/home/qy/;' | sed 's/^/mkdir /' | sh -x
  • sed 's/Tony/Camus/2':只替换第二次匹配到的
  • 单词频率过滤器
    • tr -cs A-Za-z' '\n' | tr A-Z a-z | sort | uniq -c | sort -k1,1nr -k2 | head 25
  • tcpdump tcp port 80 -s 0 -w net_stat.pcap

操作系统

IO模型

  • IO多路复用:I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

    • select:
      • 调用者将需要见监控的IO句柄放入一个数组,将这个数组传递给select调用,并设定监控何种事件(一般是可读或可写);
      • 这时select会阻塞调用进程;
      • 当有IO事件发生时,select就在数组中给发生事件的那些IO句柄做一个标记后返回;
      • 之后,调用者便轮询这个数组,发现被打入标记的便进行相应处理,并去掉这个标记以备下次使用。
      • 这样,对于服务器来说,一个进程或线程就可以处理很多客户端的读写请求了。
      • poll解决了传递给select的IO句柄最多不能超过1024个
    • epoll
      • 优点
        • 理论上没有IO句柄数量上的限制
        • IO效率与IO句柄数量没有多大关系,因为每次返回的,只是一个具体事件的列表。
        • epoll使用mmap来加速内核与用户空间的消息传递。
      • 工作方式
        • 事件触发(ET)
          • 事件发生后,只会产生一次通知。
          • 缓冲区读取数据丢失问题:解决方法是反复读取缓冲区,直到返回错误,
          • 减少了每次需要返回的IO句柄数量,在并发量极多的时候能够加快epoll_wait的处理。
        • 条件触发(LT)
          • 只要其监控的IO句柄具备调用者所要捕获的条件,一般是可读或者可写,就会通知给调用者。如果调用者不理会这个通知,他将一直通知下去,知道这个状态发生变化。
          • 当采用多进程模式编写服务器软件时,根据系统任务调度特性,采用LT模式可以使得所有连接均匀的分布于每个用于处理网络请求的进程。
  • Select、poll 和epoll的区别

    • 基本上select有3个缺点:

      1. 连接数受限
      2. 查找配对速度慢
      3. 数据由内核拷贝到用户态

      poll改善了第一个缺点,epoll改了三个缺点。

    • select,poll实现需要自己不断轮询所有文件描述符集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。

    • epoll需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它在设备就绪时,调用回调函数,把就绪文件描述符放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。

    • 虽然都要睡眠和交替,但是select和poll在"醒着"的时候要遍历整个fd集合,而epoll在"醒着"的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

    • select,poll每次调用都要把文件描述符集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,且只需要把current往等待队列上挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

  • 同步、异步、阻塞、非阻塞的区别

    • 同步IO:select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。
    • 异步IO:无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
      • Nginx通过异步IO来解决主线程阻塞的问题
      • 异步IO在处理大量网络请求时,相较于IO多路复用需要更多的系统资源,因此更适用于量少但对性能要求较高的文件处理和网络传输。
    • 非阻塞式IO:需要请求者主动轮询不断发IO请求直到返回正确。IO多路复用同非阻塞式IO本质是一样的,不过利用新的select、poll、epoll等系统调用,由操作系统来负责轮询操作。
  • 两种方式可以提高服务器的响应速度和并发:

    • 一种是异步IO或epool的方式
    • 另一种是多线程或多进程方式
      • 利用线程池和增加线程数量能很大程度地提高系统地响应速度
      • 但连接数很多的时候,资源消耗也会成为瓶颈

进程,线程,协程

  • 进程

    • 进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
    • 一个进程接到来自客户端新的请求时,可以通过fork()复制出一个子进程让其来处理,父进程只需负责监控请求的到来,这样就能做到并发处理。根据写时拷贝(copy on write)的机制,分为两个进程继续运行后面的代码。fork分别在父进程和子进程中返回,在子进程返回的值永远是0,在父进程返回的是子进程的pid。
  • 线程

    • 线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
  • 线程和进程各自有什么区别和优劣呢?

    • 进程是资源分配的最小单位,线程是程序执行的最小单位。
    • 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
    • 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
    • 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
    • 进程有严格的父进程和子进程的概念,而且它们之间有很多的联系,父进程可以很容易地了解到子进程出现问题退出了,子进程退出的行为很多时候可以不用交给程序来处理,操作系统就可以做的很好,充分利用这种机制可以获得很好的系统可靠性。
    • Linux系统提供了丰富的进程间通信机制。在Linux下进程的执行效率与线程的执行效率基本相当。
    • 在完全不需要数据同步的基于UDP协议的大数据量读取应用(流式视频播放器)下,线程更为简单、方便且高效。
  • 协程

    • 协程是什么?

      • 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。
      • 所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。
      • 子程序调用总是一个入口,一次返回,调用顺序是明确的。
      • 而协程的调用和子程序不同。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
      • 在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断
    • 协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

      • 最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
      • 第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
    • 因为协程是一个线程执行,那怎么利用多核CPU呢?

      • 最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
      • Python对协程的支持还非常有限,用在generator中的yield可以一定程度上实现协程。虽然支持不完全,但已经可以发挥相当大的威力了。
    • 来看例子:

      • 传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。

      • 如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

      • import time
        
        def consumer():
            r = ''
            while True:
                n = yield r
                if not n:
                    return
                print('[CONSUMER] Consuming %s...' % n)
                time.sleep(1)
                r = '200 OK'
        
        def produce(c):
            c.next()
            n = 0
            while n < 5:
                n = n + 1
                print('[PRODUCER] Producing %s...' % n)
                r = c.send(n)
                print('[PRODUCER] Consumer return: %s' % r)
            c.close()
        
        if __name__=='__main__':
            c = consumer()
            produce(c)
        
      • 执行结果:

        [PRODUCER] Producing 1...
        [CONSUMER] Consuming 1...
        [PRODUCER] Consumer return: 200 OK
        [PRODUCER] Producing 2...
        [CONSUMER] Consuming 2...
        [PRODUCER] Consumer return: 200 OK
        [PRODUCER] Producing 3...
        [CONSUMER] Consuming 3...
        [PRODUCER] Consumer return: 200 OK
        [PRODUCER] Producing 4...
        [CONSUMER] Consuming 4...
        [PRODUCER] Consumer return: 200 OK
        [PRODUCER] Producing 5...
        [CONSUMER] Consuming 5...
        [PRODUCER] Consumer return: 200 OK
        
        • 注意到consumer函数是一个generator(生成器),把一个consumer传入produce后:
        1. 首先调用c.next()启动生成器;
        2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
        3. consumer通过yield拿到消息,处理,又通过yield把结果传回;
        4. produce拿到consumer处理的结果,继续生产下一条消息;
        5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。
        • 整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为"协程",而非线程的抢占式多任务。

内存管理

  • mmap:把文件当作内存看待,让用户将某个文件映射到自己程序地址空间的某个部分,使用简单的内存访问指令就能对这个文件进行读写。
  • 虚拟内存技术:让多任务操作系统能够将多个进程的地址空间保护起来,让它们相互隔离。
    • MMU:内存管理单元
  • 分页分段算法
    • 分页:
      • 用户程序的地址空间被划分成若干固定大小的区域,称为"页",相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。
      • 将整个内存划分成许多大小相等的页面,每个进程的地址空间可以由多个页面构成。
    • 分段:
      • 将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。
      • 将整个内存划分为大小不同的段,每个进程的地址空间处于不同的独立段中。
    • 段页式:
      • 用分段方法来分配和管理虚拟存储器。程序的地址空间按逻辑单位分成基本独立的段,而每一段有自己的段名,再把每段分成固定大小的若干页。
      • 用分页方法来分配和管理实存。即把整个主存分成与上述页大小相等的存储块,可装入作业的任何一页。程序对内存的调入或调出是按页进行的。但它又可按段实现共享和保护。
    • x86芯片考虑到兼容性,使得页式内存管理构筑在段式内存管理之上。所以先把内存划分成一个段,在进行页式映射。
    • 应用场景
      • 进程与进程之间可以让虚拟地址相同,但是物理地址不同而达到空间上的真正分离。
      • 进程自己并不能看到自己的真实物理地址,而且即便物理地址不存在,也可以通过页面交换技术让它存在,那么操作系统就可以欺骗进程拥有很多的内存可用。
      • 利用页面交换技术,可以将一个文件映射到内存中,使得mmap这样的系统调用可以实现。
      • 将虚拟地址转换成相同的物理地址,就可以做到数据的共享,线程就是这么干的。
      • 将硬件设备的控制存储区域反映到虚拟内存上,就可以实现通过内存访问就达到控制硬件的目的。
    • 分页与分段的主要区别
      • 页是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,或者说分页是由于系统管理的需要.段是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地实现共享,满足用户的需要.
      • 页的大小固定,由系统确定,将逻辑地址划分为页号和页内地址是由机器硬件实现的.而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划分.
      • 分页的作业地址空间是一维的.分段的地址空间是二维的。
  • 页面置换算法
    • 最佳置换算法OPT:不可能实现
    • 先进先出FIFO
    • 最近最久未使用算法LRU:最近一段时间里最久没有使用过的页面予以置换.
    • clock算法

Linux与windows

  • Linux:
    • 以进程为主,强调任务的独立性
    • 线程方面的处理:NPTL原生POSIX线程库
      • 一个线程与一个内核的调度实体一一对应
      • 新的线程同步机制:futex(快速用户空间互斥体)
    • Linux处理进程和线程的机制就是是否开启COW
      • 子进程先跟父进程共享内存,采用COW及术后,子进程还需要拷贝父进程的页面表。
  • Windows
    • 以线程为主,强调任务的协同性
  • windows的调度实体就是线程,进程只是一堆数据结构。而Linux不是。Linux将进程和线程做了同等对待,进程和线程在内核一级没有差别,只是通过特殊的内存映射方法使得它们从用户的角度上看来有了进程和线程的差别。
  • Windows至今也没有真正的多进程概念,创建进程的开销远大于创建线程的开销。Linux则不然。Linux在内核一级并不区分进程和线程,这使得创建进程的开销和创建线程的开销差不多。
  • Windows和Linux的任务调度策略也不尽相同。Windows会随着线程越来越多而变得越来越慢,这也是为什么Windows服务器在运行一段时间后必须重启的原因。Linux可以持续运行很长时间,系统的效率也不会有什么变化。

内核态和用户态

  • 内核态和用户态的区别
    • 当进程执行系统调用而陷入内核代码中执行时,我们就称进程处于内核状态。此时处理器处于特权级最高的(0级)内核代码。当进程处于内核态时,执行的内核代码会使用当前的内核栈。每个进程都有自己的内核栈。
    • 当进程在执行用户自己的代码时,则称其处于用户态。即此时处理器在特权级最低的用户代码中运行。
    • 当正在执行用户程序而突然中断时,此时用户程序也可以象征性地处于进程的内核态。因为中断处理程序将使用当前进程的内核态。
    • 内核态与用户态是操作系统的两种运行级别,跟intel cpu没有必然联系,intel cpu提供Ring0-Ring3三种级别运行模式,Ring0级别最高,Ring3级别最低。Linux使用了Ring3级别运行用户态。Ring0作为内核态,没有使用Ring1和Ring2。Ring3不能访问Ring0的地址空间,包括代码和数量。Linux进程的4GB空间,3G-4G部分大家是共享的,是内核态的地址空间,这里存放在整个内核代码和所有的内核模块,以及内核所维护的数据。用户运行一程序,该程序所创建的进程开始是运行在用户态的,如果要执行文件操作,网络数据发送等操作,必须通过write,send等系统调用,这些系统会调用内核中的代码来完成操作,这时必须切换到Ring0,然后进入3GB-4GB中的内核地址空间去执行这些代码完成操作,完成后,切换Ring3,回到用户态。这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。
  • 用户态和内核态的转换
    • 用户态切换到内核态的3种方式
      • 系统调用
        • 这是用户进程主动要求切换到内核态的一种方式,用户进程通过系统调用申请操作系统提供的服务程序完成工作。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的ine 80h中断。
      • 异常
        • 当CPU在执行运行在用户态的程序时,发现了某些事件不可知的异常,这是会触发由当前运行进程切换到处理此异常的内核相关程序中,也就到了内核态,比如缺页异常。
      • 外围设备的中断
        • 当外围设备完成用户请求的操作之后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条将要执行的指令转而去执行中断信号的处理程序,如果先执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了有用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
    • 具体的切换操作
      • 从出发方式看,可以在认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一样的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断处理机制基本上是一样的,用户态切换到内核态的步骤主要包括:
      • (1)从当前进程的描述符中提取其内核栈的ss0及esp0信息。
      • (2)使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个过程也完成了由用户栈找到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。
      • (3)将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。

进程间通信

  • 管道:用于具有亲缘关系进程间的通信
    • 由pipe函数创建,调用pipe函数时在内核中开辟一块缓冲区用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端。
    • 所以管道在用户程序看来就像一个打开的文件,通过read(filedes[0])或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。
    • 管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖先那里继承管道文件描述符。
  • FIFO和Unix Domain Socket
    • 利用文件系统中的特殊文件来标识内核提供的通道
    • FIFO和Unix Domain Socket文件在磁盘上没有数据块,仅用来标识内核中的一条通道,各进程可以打开这个文件进行read和write,实际上实在读写内核通道,这样就实现了进程间通信。
    • FIFO又名有名管道,每个FIFO有一个路径名与之关联,从而允许无亲缘关系的进程访问同一个FIFO。半双工。
  • fork和wait
    • 父进程通过fork可以将打开的文件描述符传递给子进程
    • 子进程结束时,父进程调用wait可以得到子进程的终止信息
  • 信号
    • 信号又称软中断,通知程序发生异步事件,程序执行中随时被各种信号中断,进程可以忽略该信号,也可以中断当前程序转而去处理信号,
  • 信号量
    • 分为命名和匿名信号量。命名信号量通常用于不共享内存的进程之间(内核实现);匿名信号量可以用于线程通信(存放于线程共享的内存,如全局变量),或者用于进程间通信(存放于进程共享的内存,如System V/ Posix 共享内存)。
    • 信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。 信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。
  • 消息队列
    • Linux 中的消息可以被描述成在内核地址空间的一个内部链表,每一个消息队列由一个IPC 的标识号唯一地标识。
  • 共享文件
    • 几个进程可以在文件系统中读写某个共享文件,也可以通过给文件加锁来实现进程间同步
  • 共享内存:通过mmap函数实现,几个进程可以映射同一内存区
  • 现在两个进程间通信,数据量很大,要求延时很小,你会使用哪种形式?为什么?共享内存使用的时候需要注意什么

死锁

  • 概念:指多个进程因竞争共享资源而造成的一种僵局,若无外力作用,这些进程都将永远不能再向前推进。

  • 原因:

    • 竞争资源\
    • 程序推进顺序不当
  • 必要条件:

    • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
    • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
    • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
    • 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。
  • 处理死锁基本方法:

    1. 预防死锁(摒弃除1以外的条件)

      1. 资源一次性分配:(破坏请求和保持条件)
      2. 可剥夺资源:即当某进程新的资源未满足时,释放已占有的资源(破坏不可剥夺条件)
      3. 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
    2. 避免死锁(银行家算法)

      1. 预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
    3. 检测死锁(资源分配图)

      1. 首先为每个进程和每个资源指定一个唯一的号码;

        然后建立资源分配表和进程等待表

    4. 解除死锁

      1. 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
      2. 撤销进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。
  • 如何制造死锁

变量存储区域

  • 栈:
    • 由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。
    • 地址是不固定的。
    • 存储的变量通常是局部变量、函数参数等。
  • 堆:
    • 由new分配的内存块,它们的释放编译器不去管,而是由应用程序去控制,一般一个new就要对应一个delete。
    • 如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
  • 自由存储区:
    • 由malloc等分配的内存块,和堆是十分类似,不过它是用free来结束自己的生命的。
  • 全局存储区(静态存储区):
    • 全局变量和静态变量的存储是放在一块的。
    • 初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
    • 程序结束后由系统释放。
  • 常量存储区:
    • 这是一块比较特殊的存储区,位置是固定的。
    • 这里面存放的是常量,不允许修改。

About

总结操作系统及Linux的面试常见问题