VFS(Virtual File System):
操作系统内核中的一层抽象,统一管理不同类型的文件系统(如ext4、NFS、tmpfs等),为用户和应用提供一致的文件访问接口。
# 查看所有挂载的文件系统及其类型
mount | column -t
# 查看某路径的文件系统类型
df -T /etc/passwd
读取/proc文件(procfs是伪文件系统)
cat /proc/cpuinfo
效果:就像普通文件一样读取,实际上内容由内核动态生成。
文件描述符(File Descriptor, fd):
内核为进程分配的一个非负整数,用于唯一标识已打开的文件或其他IO资源(socket、pipe等)。
# 查看当前shell进程的打开文件描述符
ls -l /proc/$$/fd
# 追加内容到文件(fd 1是标准输出)
echo "hello world" > fd_test.txt
用C打开文件,写入内容
#include
#include
int main() {
int fd = open("fd_demo.txt", O_WRONLY|O_CREAT, 0644);
write(fd, "fd demo\n", 8);
close(fd);
return 0;
}
效果:生成fd_demo.txt文件,内容为“fd demo”。
IO重定向(Redirection):
将进程的标准输入/输出/错误重定向到文件、设备或其他进程。
# 标准输出重定向
echo "test output" > output.txt
# 标准错误重定向
ls not_exist 2> error.txt
# 同时重定向标准输出和错误
ls foo 1> all.txt 2>&1
C语言实现stdout重定向
#include
#include
int main() {
int fd = open("redirect.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644);
dup2(fd, 1); // 1为stdout
printf("redirected output\n");
close(fd);
return 0;
}
效果:终端无输出,内容写入redirect.txt。
PageCache:
操作系统内核中用于缓存磁盘文件数据的内存区域,加速文件读写,减少磁盘IO。
# 查看内存中的PageCache占用
free -h
# 强制将PageCache中的数据写入磁盘
sync
# 释放PageCache(需要root权限)
echo 3 > /proc/sys/vm/drop_caches
对比首次和再次读取大文件的速度
time cat bigfile > /dev/null # 第一次,慢
time cat bigfile > /dev/null # 第二次,快
效果:第二次明显更快(走PageCache)。
mmap(Memory Map):
将文件或设备的内容映射到进程的虚拟内存地址空间,实现高效数据访问和共享。
# 查看进程mmap映射情况
cat /proc/$(pgrep your_program)/maps
C语言mmap写文件
#include
#include
#include
#include
int main() {
int fd = open("mmap_exp.txt", O_RDWR|O_CREAT, 0644);
ftruncate(fd, 4096);
char* addr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
strcpy(addr, "Hello mmap!");
munmap(addr, 4096);
close(fd);
return 0;
}
效果:文件内容直接被修改。
BIO读取文件
try (FileInputStream fis = new FileInputStream("test.txt")) {
int b;
while ((b = fis.read()) != -1) {
System.out.print((char) b);
}
}
NIO内存映射文件写入
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MmapDemo {
public static void main(String[] args) throws Exception {
RandomAccessFile raf = new RandomAccessFile("mmap_java.txt", "rw");
FileChannel channel = raf.getChannel();
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
buffer.put("Hello Java mmap!".getBytes());
channel.close();
raf.close();
}
}
效果:文件内容被直接修改。
BIO(Blocking IO):
每个客户端连接由独立线程处理,read/write操作会阻塞线程。
Java BIO服务端
import java.net.*;
import java.io.*;
public class BioServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(8080);
while (true) {
Socket client = ss.accept();
new Thread(() -> {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
String line = in.readLine();
System.out.println("收到: " + line);
client.close();
} catch (IOException e) { e.printStackTrace(); }
}).start();
}
}
}
体验:多开客户端,top/htop观察线程数暴增。
# 查看TCP相关内核参数
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_keepalive_time
# 临时调整参数
sudo sysctl -w net.core.somaxconn=2048
Java设置socket参数
ServerSocket ss = new ServerSocket();
ss.setReuseAddress(true);
ss.setReceiveBufferSize(65536);
C10K问题:
服务器如何高效处理1万(甚至更多)并发连接,是高性能IO的经典挑战。
# 向服务器发起1万并发连接压力测试
wrk -t4 -c10000 -d10s http://127.0.0.1:8080/
多路复用器:
一种机制,可以让单线程同时监听多个IO事件(socket、文件等),只在有事件发生时才处理。
man epoll
查看进程epoll使用情况
lsof -p <pid> | grep epoll
C语言epoll服务端核心片段
#include
int epfd = epoll_create(1024);
struct epoll_event ev, events[10];
ev.events = EPOLLIN;
ev.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
int nfds = epoll_wait(epfd, events, 10, -1);
// 处理events数组
Selector:
Java NIO的多路复用器,单线程可管理成千上万个Channel(连接)。
Java NIO Echo服务端
import java.io.IOException;
import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import java.util.*;
public class NioEchoServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8080));
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
if (key.isAcceptable()) {
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = sc.read(buf);
if (len == -1) {
sc.close();
} else {
buf.flip();
sc.write(buf); // Echo
}
}
}
}
}
}
telnet 127.0.0.1 8080
# 输入内容,回显
如需对某一部分更细致的原理、代码或实验,请随时继续提问!