操作系统
约 1810 字大约 6 分钟
2025-01-18
1.BIO、NIO、AIO
在操作系统层面上,IO类型大体上可分为 BIO 、NIO 、AIO
1.1 BIO
BIO:Blocking IO,阻塞IO,在进行 IO(读取或写入)操作时,当前线程会被阻塞,无法执行其他操作
- 对于单线程的网络服务,会产生线程卡死的问题,因为当等待时,整个线程会被挂起,无法执行,也无法做其他的工作
- 多线程的网络服务,会产生资源浪费的问题,主要有两个场景会产生上述问题:一是当线程越多,同时线程上下文就越多,浪费CPU资源;二是每个线程都有独立的内存空间:栈,比如有1000个线程同时运行,每个线程占用1MB内存,总共就会占用1G的内存
根本问题:当调用 IO 接口的方法(read、write、connect)接受网络请求时,有数据到了线程就工作,没有数据时,线程处于阻塞空闲时间
新思路
要是在调用 IO 接口之前,操作系统直接告知线程有没有数据,而不是阻塞去等就好了,这和下面的 NIO 设计相关
1.2 NIO
NIO:Non-Blocking IO ,非阻塞IO,在进行 IO(读取或写入)操作时,无论数据是否准备好,都会立即返回。如果没有数据可用,操作会返回-1,并将文件的文件描述符 errno 设置为 EAGAIN ,而不是阻塞等待
举个例子来说说 BIO 和 NIO 的区别?
- 在BIO模式下,调用read,如果发现没数据已经到达,就会Block住。
- 在NIO模式下,调用read,如果发现没数据已经到达,就会立刻返回-1, 并且errno被设为
EAGAIN
。
NIO 的C++伪代码如下:
struct timespec sleep_interval{.tv_sec = 0, .tv_nsec = 1000};
ssize_t nbytes;
while (1) {
/* 尝试读取 */
if ((nbytes = read(fd, buf, sizeof(buf))) < 0) {
// 没有数据
if (errno == EAGAIN) {
perror("nothing can be read");
} else {
perror("fatal error");
// 将文件描述符 errno 设置为 EAGAIN
exit(EXIT_FAILURE);
}
} else {
// 有数据,处理数据
process_data(buf, nbytes);
}
// 睡眠
nanosleep(sleep_interval, NULL);
}
以上代码解读:轮询读取数据,不断的尝试有没有数据到达,有了就处理,没有(得到EWOULDBLOCK
或者EAGAIN
)就等一小会再试。这比之前BIO好多了,起码程序不会被卡死了。
但 NIO 操作会产生两个新问题:
- 如果有大量文件描述符都要等,那么就得一个一个的 read。这会带来大量的上下文切换,资源浪费的问题
- 休息一会的时间不好把握。这里是要猜多久之后数据才能到。等待时间设的太长,程序响应延迟就过大;设的太短,就会造成过于频繁的重试,干耗CPU而已。
新思路
NIO的根本问题在于,我们需要自己去不断循环去查是否有文件数据到达,没有的话还需要设置文件描述符,那如果操作系统能一口气告诉程序,哪些数据到了就好了,于是就有了IO多路复用了
1.3 IO多路复用
IO多路复用:程序会注册一组socket文件描述符给操作系统,表示:我要监视这些fd是否有IO事件发生,有了就告诉程序处理
- NIO和IO多路复用的区别:
NIO仅仅是指 IO API总是能立刻返回,不会被Blocking;而IO多路复用仅仅是操作系统提供的一种便利的通知机制。
操作系统实现IO多路复用的方式:
- select和poll
- epoll
1.3.1 select和poll
待写
1.3.2 epoll
待写
3.软连接和硬连接的区别
软连接:实际上是一个指向目标文件的路径的符号链接,类似于Windows系统中的快捷方式,创建软连接不会占用目标文件的inode节点,只是简单地指向目标文件的路径。删除原始文件后,软连接仍然存在,但指向的目标文件失效,称为"悬空链接"。软链接可以跨文件系统创建软连接。
硬连接:是指多个文件实际上指向同一个inode节点,即多个文件共享同一块数据块。创建硬连接会增加目标文件的链接计数,删除任何一个硬连接并不会影响其他硬连接指向的文件数据。只能在同一文件系统内创建硬连接。
4.内核态和用户态
内核态和用户态是操作系统中的两种运行模式。它们的主要区别在于权限和可执行的操作:
- 内核态(Kernel Mode):在内核态下,CPU可以执行所有的指令和访问所有的硬件资源。这种模式下的操作具有更高的权限,主要用于操作系统内核的运行。
- 用户态(User Mode):在用户态下,CPU只能执行部分指令集,无法直接访问硬件资源。这种模式下的操作权限较低,主要用于运行用户程序。
内核态的底层操作主要包括:内存管理、进程管理、设备驱动程序控制、系统调用等。这些操作涉及到操作系统的核心功能,需要较高的权限来执行。
分为内核态和用户态的原因主要有以下几点:
- 安全性:通过对权限的划分,用户程序无法直接访问硬件资源,从而避免了恶意程序对系统资源的破坏。
- 稳定性:用户态程序出现问题时,不会影响到整个系统,避免了程序故障导致系统崩溃的风险。
- 隔离性:内核态和用户态的划分使得操作系统内核与用户程序之间有了明确的边界,有利于系统的模块化和维护。
内核态和用户态的划分有助于保证操作系统的安全性、稳定性和易维护性。
5.有哪些进程调度算法
- 先来先服务:先来后到,每次从就绪队列选择最先进入队列的进程,然后一直运行,直到进程退出或被阻塞,才会继续从队列中选择第一个进程接着运行
- 最短作业优先:会优先选择运行时间最短的进程来运行,这有助于提高系统的吞吐量。
- 高响应比优先:每次进行进程调度时,先计算「响应比优先级」,然后把「响应比优先级」最高的进程投入运行
- 时间片轮转调度算法
每个进程被分配一个时间段,称为时间片(*Quantum*),即允许该进程在该时间段中运行。
- 如果时间片用完,进程还在运行,那么将会把此进程从 CPU 释放出来,并把 CPU 分配给另外一个进程;
- 如果该进程在时间片结束前阻塞或结束,则 CPU 立即进行切换;