Linux 操作系统相关概念
2025-01-22 08:19:30    2.9k 字   
This post is also available in English and alternative languages.

Linux/Unix操作系统相关概念。


1. 什么是内核

操作系统通常包含两种不同含义:

  1. 指完整的软件包,这包括用来管理计算机资源的核心层软件,以及附带的所有标准软件工具,诸如命令行解释器、图形用户界面、文件操作工具和文本编辑器等。

  2. 在更狭义的范围内,是指管理和分配计算机资源(即CPU、RAM和设备)的核心层软件。

而术语内核通常是第二种含义;
虽然在没有内核的情况下,计算机也能运行程序,但有了内核会极大简化其他程序的编写和使用,令开发者"功力"大进、游刃有余,这要归功于内核为管理计算机的有限资源所提供的软件层。


2. 内核的职责

职责、功能释义
进程调度Linux属于抢占式多任务操作系统,多个进程可同时驻留于内存,且每个进程都能获得CPU的使用权;
内核的进程调度负责控制哪些进程获得CPU的使用及使用时长。
内存管理物理内存(RAM)属于有限资源,内核必须以公平高效地方式在进程间共享这一资源,Linux采用了虚拟内存管理机制。
进程与进程之间、进程与内核之间彼此隔离,因此一个进程无法读取或修改内核或其他进程的内存内容;
只需将进程的一部分保持在内存中,降低了每个进程对内存的需求量,让RAM中同时加载更多的进程;
文件系统内核在磁盘之上提供有文件系统,允许对文件执行创建、获取、更新以及删除等操作。
创建、终止进程内核可将新程序载入内存,为其提供运行所需的资源;
一旦进程执行完毕,内核还要确保释放其占用资源,以供后续程序重新使用。
设备访问计算机外接设备(鼠标、键盘、磁盘等)可实现计算机与外部世界的通信,这一通信机制包括输入、输出或是两者兼而有之。
内核既为程序访问设备提供了简化版的标准接口,同时还要仲裁多个进程对每一个设备的访问。
联网内核以用户进程的名义收发网络消息(数据包),该任务包括将网络数据包路由至目标系统。
系统调用系统调用是受控的内核入口,借助于这一机制,进程可以请求内核以自己的名义去执行某些动作。
等…

3. 内核态、用户态

现代处理器架构一般允许CPU至少在两种不同状态下运行,即: userMode(用户态)kernelMode(内核态) ;与之对应,可将虚拟内存区域划分(标记)为 userSpace(用户空间)kernelSpace(内核空间)

userMode(用户态) 下运行时,CPU只能访问被标记为 userSpace(用户空间) 的内存,试图访问属于 kernelSpace(内核空间) 的内存会引发硬件异常。

当运行于 kernelMode(内核态) 时,CPU既能访问 userSpace(用户空间) 内存,也能访问 kernelSpace(内核空间) 内存。

用户态和内核态

需要注意的是,仅当处理器在 kernelMode(内核态) 运行时,才能执行涉及底层硬件的操作,比如:执行宕机(halt)指令去关闭系统、访问内存管理硬件、以及设备/O操作的初始化等。

Linux/Unix实现者们利用这一设计,将操作系统置于内核空间。这确保用户进程既不能访问内核指令和数据结构,也无法执行不利于系统运行的操作


4. 系统调用

用户程序能够完成的工作相当有限,需要使用操作系统提供的服务才能编写功能丰富的用户程序。系统调用作为操作系统提供的接口,它与底层的硬件关系十分紧密,因为硬件的种类繁杂,所以不同架构要使用不同的指令。

systemCall(系统调用) 是受控的内核入口,借助于这一机制,用户进程可以请求内核以自己的名义去执行某些动作;内核提供有一系列API形式的服务供程序访问,包括创建新进程、执行I/O、为进程间通信创建管道等。

最值得注意的是:使用系统调用时需要将CPU从 userMode(用户态) 切换到 kernelMode(内核态) ,以便CPU能访问受到保护的内核内存,因此系统调用与用户空间的函数调用相比,哪怕是最简单的系统调用函数都会产生显著的开销

其原因是为了执行系统调用,系统需要临时性地切换到核心态,并且内核还需验证系统调用的参数、用户内存和内核内存之间也有数据需要传递。

关于系统调用的消耗、成本,推荐阅读此篇:《为什么系统调用会消耗较多资源》。


5. 一切皆文件

Linux/Unix中一切皆文件的思想可谓众所周知,不仅是普通文件,还包括目录、字符设备、块设备、套接字等,都能以文件的方式被对待(都可以用fopen()fclose()fwrite()fread()等函数进行处理)。

这种设计方式屏蔽硬件的区别,所有设备都抽象成文件,提供统一的接口给用户。

文件类型:

文件类型描述
普通文件类似mp4、pdf、html这样应用层面上的文件类型都属于普通文件;
Linux用户可以根据访问权限对普通文件进行查看、更改和删除
目录文件类似/usr/、/home/目录文件包含了各自目录下的文件名和指向这些文件的指针,只要有访问权限,就可以随意访问这些目录下的文件。
字符设备文件隐藏在/dev目录下,在进行设备读取和外设交互时会被使用到串行端口设备,例如键盘、鼠标等。
块设备文件存储数据以供系统存取的接口设备,简单而言就是硬盘;
套接字启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信。用于进程间的网络通信,也可以用于本机之间的非网络通信
FIFO管道文件如:ls -l /etc/,管道文件主要用于进程间通讯,FIFO解决多个程序同时存取一个文件所造成的错误。
硬链接本文忽略
符号链接(软链接)本文忽略

6. 文件描述符

上面简单介绍了 userSpace(用户空间)kernelSpace(内核空间) ,这对于理解 fileDescriptor(文件描述符) 有很大的帮助。

Linux中把一切都看做是文件,当用户进程打开现有文件或创建新文件时,内核向用户进程返回一个 fileDescriptor(文件描述符)
fileDescriptor(文件描述符) 在形式上是一个非负整数,而实际上它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。

因此 fileDescriptor(文件描述符) 是内核为了高效管理已被打开的文件所创建的索引,所有执行I/O操作的系统调用都会通过 fileDescriptor(文件描述符)

在程序设计中,一些涉及底层的程序编写往往会围绕着 fileDescriptor(文件描述符) 展开。


fileDescriptor(文件描述符) 具体可以指向什么?
Linux/Unix中一切皆文件,因此只要是文件(上面<一切皆文件>小节中列举的),都可以被 fileDescriptor(文件描述符) 所指向。


fileDescriptor(文件描述符) 相关的有3个表,分别是:file descriptorfile tableinode table

FileDescriptor
  • file descriptor 表

    file descriptor表由用户进程所有,每个进程都有一个这样的表,这里记录了进程打开的文件所代表的 fileDescriptor(文件描述符) ,这些 fileDescriptor(文件描述符) 的值映射到file table中的条目(entry)。

  • global file table 表

    file table是全局唯一的表,由内核维护,这个表记录了所有进程打开的文件的状态(是否可读、可写等状态),同时它也映射到inode table中的entry。

  • inode table 表

    inode table同样是全局唯一的,它指向了真正的文件地址(磁盘中的位置),每个entry全局唯一。


6.1. Java中的fileDescriptor

Java封装了FileDescriptor类来表示 fileDescriptor(文件描述符) ,FileInputStrem和FileOutputStream中都会持有这个类。

1
2
3
4
5
// java.io.FileDescriptor
public final class FileDescriptor {
private int fd;
//.......
}

fileDescriptor(文件描述符) 在输入输出流的构造器中创建,JVM会发起system call open()初始化资源并返回 fileDescriptor(文件描述符)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// java.io.FileInputStream
public class FileInputStream extends InputStream {
/* File Descriptor - handle to the open file */
private final FileDescriptor fd;

public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
//......
}

下面这段示例代码可以查看标准输入/输出/错误流中的 fileDescriptor(文件描述符)

1
2
3
4
5
6
7
8
9
public class FDTester {
public static void main(String[] args) throws Exception{
BufferedInputStream in = (BufferedInputStream) System.in;
PrintStream out = System.out;
PrintStream err = System.err;
FileInputStream fis = new FileInputStream("/tmp/test.txt");
System.in.read();
}
}
input(图片来源:https://wiyi.org/linux-file-descriptor.html) output(图片来源:https://wiyi.org/linux-file-descriptor.html)

7. Socket

Socket中文翻译为套接字,是计算机网络中进程间进行双向通信的端点的抽象。一个Socket代表了网络通信的一端,是由操作系统提供的进程间通信机制。

在操作系统中,通常会为应用程序提供一组应用程序接口,称为Socket接口(SocketAPI);应用程序可以通过Socket接口,来使用网络Socket,以进行数据的传输。

一个Socket由IP地址和端口组成,即:Socket地址=IP地址:端口号;在同一台计算机上,TCP协议与UDP协议可以同时使用相同的端口(Port),而互不干扰。

要想实现网络通信,至少需要一对Socket,其中一个运行在客户端,称之为ClientSocket;另一个运行在服务器端,称之为ServerSocket。

Socket之间的连接过程可以分为三个步骤:1.服务器监听;2.客户端连接;3.连接确认。


8. Socket缓冲区

每个Socket被创建后,都会在内核中分配两个缓冲区:输入缓冲区和输出缓冲区。

通过Socket发送数据并不会立即向网络中传输数据,而是先将数据写入到输出缓冲区中,再由TCP协议将数据从输出缓冲区发送到目标主机。

通过Socket接收数据也是如此,也是从输入缓冲区中读取数据,而不是直接从网络中读取。


9. Reference