2018年编程问答

操作系统既然有内存分页为什么还要内存分段?

一句话来说就是为了兼容。

早期的计算机,使用的是分段管理,比如16位的8086机器,最大的内存地址到,但是使用段寄存器+偏移量的方式,可以访问到1MB的内存。(把1M内存分为16个段)。例如8086四个段寄存器(代码段寄存器,数据段寄存器,堆栈段寄存器,附加段寄存器)。并且分段还有一个好处是做到数据隔离,保证安全。

后来32的80386采用了分页内存管理,使用虚拟内存地址映射到物理内存,所以让每个进程看上去自己都有4GB的内存空间(虚拟内存地址)可用。每一个页大概有4K,而且真正被使用的时候才会分配真实的内存,否则可能在硬盘上,或者就没有被分配。但后来的80386还保留了分段模式主要是为了兼容。

再后来使用了“段页式管理”,结合了分段和分页两种内存管理思路,用户程序的逻辑地址空间首先被划分成若干的逻辑段,每个段都有自己的段号(常量段,堆栈段),然后再将这些段分页。所以不要把最早的内存分段管理和如今的段页式管理搞混,即使他们都有所谓的“堆栈段”,要分清它们的区别和联系。

总结一下分段内存管理的优势在于内存共享和安全控制,而分页内存管理的优势在于提高内利用率。

为什么链接数据库不用epoll的方式来提升并发量?

简单的来说,数据库的瓶颈不在并发量。我们把多线程改为epll的前提是有这么多的线程可以工作。对于数据库而言,你这些线程只能阻塞的,因为没有那么多的连接池给你。

IO什么时候会发生阻塞?

zusefeizuse

顺便说一句,阻塞非阻塞,主要指等待数据的情况,输出时由于磁盘写入、网络传输等因素,也有可能会发生阻塞,但发生的概率 并不高。而且即便发生了阻塞,等待时间也相对较短,因此不必过于在意。

所有的中断都是轮询么?

实际上看上去所有的中断,在底层都有一个轮询机制来看此中断。但是首先让我们从概念上来区分中断和轮询,中断像是处理高优先级和突发事件,而轮询则更像是自发有规律的处理一件事,或者一堆事。
但是即使到cpu级别的,中断也是用轮询实现的。CPU在执行完当前程序的每一条指令后,都会去确认在执行刚才的指令过程中中断控制器(如:8259A)是否发送中断请求过来,如果有那么CPU就会在相应的时钟脉冲到来时从总线上读取中断请求对应的中断向量。

假如我的系统虽然每次只有几个线程是活跃的,那我的线程池大小开到100和开到10000,性能是一样的么,除了线程本身耗费的内存资源?

如果你的系统只有几个进程是活跃的,那么线程池开100还是10000,是区别不大的。因为只有就绪态的线程才会占调度队列。linux的目前的调度器是CFS,使用红黑树管理就绪队列,所以每当线程试图读写网络,并遇到阻塞时,都会发生O(logm)级别的开销。而且每次收到报文,唤醒阻塞在fd上的上下文时,同样要付出O(logm)级别的开销。因为红黑树的时间复杂度大概是O(logm).

操作系统中的进程模型,一共有几种形态?

最简单的可分为三类:

  1. 运行态(running):占有处理器正在运行
  2. 就绪态(ready):具备运行条件,等待系统分配处理器以便运行
  3. 等待态(blocked):不具备运行条件,正在等待某个事件的完成

复杂一点的分类是五类,引入了新建态和终止态。因为在新建进程和销毁进程的时候,需要创建和清理上下文。
更复杂一点的七态模型,因为进程不仅仅存在于内存,可能因为系统资源的不足而挂起置换到磁盘镜像中去。所以又加入了两种形态:“挂起就绪态”和“挂起等待态”,而变为七种形态。

阻塞IO和非阻塞IO的本质区别到底是什么?

这件事要想搞清楚,仅仅从软件的角度是不够的,还需要从操作系统,硬件分别来做考虑才行。

首先阻塞IO是一个很常规的操作,比如你从硬盘读取一个文件或者从网站获取一段数据再解析,那么你除了等这段数据返回再不能干别的什么。之所以引入非阻塞io,主要是为了网络io,相比于文件io的读写,网络io更像是在通信,就有延迟,不确定性,很可能被无限阻塞,因为你不知道数据什么时候会来。它就适合非阻塞模型。而从通信模型上说,有一个非就绪到就绪的过程,而硬盘可以说始终是就绪的,除非本地文件被锁了,否则是不可能被阻塞住的,或者说文件就没有阻塞不阻塞这么一说。当然这不是说非阻塞io对文件读写没有意义,比如对于SSD而言,是支持并行读写的,这个时候就需要非阻塞IO的支持来提供并发了。比如windows的IOCP,linux上的kqueue

从软件上的复杂性而言,阻塞io将会阻塞你的主控,直到有数据返回。而非阻塞不会。你可以继续获得程序的执行权,不过需要用轮询,或者回调的方式让你知道数据已经准备好了,使用起来更复杂。所以使用非阻塞io最好是在你同时需要发出多个io请求,或者在发出io请求之后还有别的事可以做。如果从执行流来划分程序的话,阻塞io一直在那一个执行流上,而非阻塞IO需要在多个执行流上切换,而切换上下文本身就是开销。

形象一点说,阻塞io像是IO操作和线程本身绑定了,而非阻塞io分离和io和线程,通过io事件,让其可以进行通信。那既然都是事件,自然也就需要一系列的事件处理模型了。比如epoll这种多路复用,就是让你同时管理多个io事件。

在进行io操作的时候,cpu会去一直轮询硬件设备看是不是好了吗?

早期外设到内存的IO操作,都需要cpu介入,不停的轮询状态。控制io操作,这种方式被称为PIO。但是后来硬件外设都引入了DMA,可以说将io操作托管给了DMA,这样只要在开始传输和传输结束的时候让CPU介入就可以了。这样减少了CPU的负担。

什么是ssh登录?

SSH登录,既公钥登录,就是将自己的公钥存储在远程主机上,远程登录的时候,请求远程主机,远程主机会向用户发送一段随机字符串,接着用户会用自己的私钥签名,在发给远程主机,如果远程主机能够验签成功,就证明用户是可信的(私钥可以用来证明身份)。这种登录的好处是不用像密码登录一样每次登录都要输入密码,但是SSH有一个风险就是中间人攻击,既冒充远程主机将你本来要发给真正远程主机的信息截获(你的公钥是公开的)。而且SSH也不像HTTPS一样有公证的证书中心(CA),不过好在你在用ssh远程登录时终端会提示你指纹警告,让你自己去核对一下远程主机的公钥指纹。
你调用ssh-keygen时,id_rsa.pub是你的公钥,id_rsa是私钥。

为什么老一辈的程序员喜欢把堆和栈一起说叫“堆栈”,而不是堆是堆,栈是栈?

因为早期的计算机,比如8086,内存分段,而有一个段就叫堆栈段。

epoll为啥要用非阻塞IO而不是阻塞IO?

epoll告诉你可读,但是没告诉你读多少,除非提前用某种协议协商好,但是可读也不代表就一定能读,所以如果你要是在读的时候和在写的时候,不符合预期的话,就会把整个Epoll给阻塞了。而非阻塞可以在读写不符合预期的时候,先返回然后下次再尝试,这里要特别说明一点Epoll有水平触发(LT)【默认模式】和边缘触发(ET)【高速模式】。对于LT模式使用阻塞模式尚可,但如果是ET模式,则必须使用非阻塞,因为ET的读写事件只会通知你一次,如果你不处理完,下次也不通知你了。

为什么有人说java的Netty,tornado这些是异步库,而这些库都是基于epoll的,但是又有人说,epoll是同步的?

首先软件是分层的,说netty是异步的,是指在框架层,进行io操作的时候,可以不用阻塞在那里,从业务上是异步的。
而对于操作系统级别的io操作,epoll,回调这些都是同步的,因为这个时候数据还没有从内核缓存区拷贝到进程缓存区,只有IOCP才是相对于操作系统级别的真异步。

水平触发与边缘触发的区别?

Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你,如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。
Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你,这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

为什么epoll不支持文件而kqueue支持文件?

select()/poll()/epoll()不能工作在常规的磁盘文件上。这是因为epoll有一个强烈基于准备就绪模型的假设前提。你监视的是准备就绪的套接字,因此套接字上的顺序IO调用不会发生阻塞。但是磁盘文件并不符合这种模型,因为它们总是处于就绪状态。 磁盘I/O只有在数据没有被缓存到内存时会发生阻塞,而不是因为客户端没发送消息。磁盘文件的模型是完成通知模型。在这样的模型里,你只是产生I/O操纵,然后等待完成通知。kqueue支持这种方式,通过设置EVFILT_AIO 过滤器类型来关联到 POSIX AIO功能上,诸如aio_read()。

为什么5种io模型中,只有第五种io算是异步,其余都是同步?

因为只有第五种io模型收到通知的时候,数据已经完全准备好了,其余的模型,都是要自己去把数据从内核缓冲区读到进程缓冲区中,而且这个读取的操作是阻塞的。

一个标准的阻塞调用是什么样的?

1
2
3
4
5
6
7
8
9
10
11
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
char buf[10];
int n = read(STDIN_FILENO, buf, 10);
dosomethinforbuf(buf);
return 0;
}

一个非阻塞调用是什么样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
int main(void)
{
char buf[10];
int fd, n;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
while (1)
{
n = read(fd, buf, 10);
if (n >= 0)
break;
sleep(1);
}
dosomethinforbuf(buf);
close(fd);
return 0;
}

线程是如何被唤醒的,线程池中的线程又是如何被唤醒的?

操作系统层面是无法识别你进程中有多少线程池的,因为在操作系统眼里这些都一样,线程池只是你自己方便维护管理线程的一种手段。线程在读数据时,如果未就绪。系统状态会被标记为阻塞,然后上下文会被挂到目标对象的wait_queue队列里。等目标对象就绪了,会解锁合适个数的wait_queue上的上下文。上下文解锁后,状态就转为就绪,然后被扔到系统调度队列里去。随后它们会被系统分配资源并执行。

什么是内网,什么是公网?

以下都是内网ip,其余都是公网ip。

1
2
3
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0~192.168.255.255

ping使用的是哪个端口?

ICMP不象TCP或UDP有端口,但它确实含有两个域:类型(type)和代码(code)。而且这些域的作用和端口也完全不同.

ping没有指定端口这个选项,它是网络层的,端口是传输层的概念。

多个进程可以占用一个端口么?

一个进程可以同时LISTENING,ESTABLISHED若干个端口。
ESTABLISHED状态,一个端口只能对应一个进程,但是一个端口同时被几个进程LISTENING。就是可以有多个等待。

硬盘的读写是串行还是并行的?

先说机械硬盘,机械硬盘的串口比并口快,因为并口要维护同步,而且频率低。串口的频率很高。目前大部分硬盘是串口。
这里只是说数据传输,实际上机械硬盘是支持并行读写的,比如磁盘阵列。磁盘阵列是由很多价格较便宜的磁盘,组合成一个容量巨大的磁盘组,利用个别磁盘提供数据所产生加成效果提升整个磁盘系统效能。利用这项技术,将数据切割成许多区段,分别存放在各个硬盘上。但是机械硬盘慢的真正原因是磁盘转速有物理极限。
固态硬盘是支持并行读写的,一块SSD有多个NAND颗粒,主控将数据分配到各个颗粒上。大部分SSD也是SATA接口。

chuankoubingkou

网卡的读写是串行还是并行的?

网卡肯定是并行的,因为只要发送和接受就行了,所以网卡的指标是千兆网卡、万兆网卡是表示每秒转发包数处理能力。