从零写 OS 内核-第二十四篇:Display Server 与 Client —— 构建共享内存图形架构
"Framebuffer 让你能画像素,但多个应用如何共享屏幕?
今天,我们实现 Display Server + Client 架构,通过共享内存实现高效图形合成!"
在上一篇中,我们实现了 Framebuffer 驱动,用户程序可直接操作像素。
但这带来严重问题:
- 多个程序同时写屏幕 → 画面撕裂、内容混杂
- 无窗口概念 → 无法实现 GUI
- 无权限控制 → 任意程序可覆盖整个屏幕
真正的图形系统需要 Display Server(显示服务):
- 唯一拥有 Framebuffer 写权限
- 接收 Client(客户端)的绘图请求
- 合成多个窗口并上屏
而 Client 通过 共享内存 高效提交绘图内容。
今天,我们就来:
✅ 设计 Display Server 架构
✅ 实现共享内存机制(shm)
✅ 构建 Client 绘图 API
✅ 演示多窗口合成上屏
让你的 OS 拥有现代图形系统雏形!
🖥️ 一、Display Server 架构设计
核心组件:
| 组件 | 职责 | |——|——| | Display Server | 管理屏幕、合成窗口、处理输入 | | Client | 应用程序(如 Terminal、Painter)| | Shared Memory | Client 绘制内容 → Server 读取合成 | | IPC 通道 | Client 发送命令(创建窗口、提交帧)|
数据流:
+------------+ +----------------+ +--------------+
| Client 1 | | Client 2 | | ... |
| (Terminal) | | (Painter) | | |
+-----+------+ +-------+--------+ +--------------+
| |
| 1. 绘制到 shm buffer |
| 2. 发送 "commit" IPC |
v v
+-----+----------------------+--------+
| Display Server |
| - 管理窗口列表 |
| - 读取 shm buffer |
| - 合成到 Framebuffer |
+------------------------------------+
|
v
+---------------+
| Framebuffer |
| (物理屏幕) |
+---------------+
💡 关键:Client 无法直接访问 Framebuffer,只能通过 Server 间接显示。
🧱 二、共享内存(Shared Memory)机制
1. 系统调用:shmget / shmat
// 创建共享内存段
int sys_shmget(key_t key, size_t size, int shmflg) {
// 1. 分配物理页
void *pages = buddy_alloc_pages(ROUND_UP(size, PAGE_SIZE) / PAGE_SIZE);
// 2. 创建共享内存对象
struct shm_object *shm = kmem_cache_alloc(shm_cache);
shm->key = key;
shm->size = size;
shm->paddr = (uint32_t)pages;
shm->ref_count = 1;
// 3. 加入全局哈希表
hash_add(shm_table, key, shm);
return key;
}
// 附加到进程地址空间
void *sys_shmat(int shmid, const void *shmaddr, int shmflg) {
struct shm_object *shm = hash_find(shm_table, shmid);
if (!shm) return MAP_FAILED;
// 1. 找到未使用的虚拟地址
uint32_t vaddr = find_free_vma(shm->size);
// 2. 映射物理页到用户空间
for (size_t i = 0; i < shm->size; i += PAGE_SIZE) {
map_page(current_task->cr3, vaddr + i,
shm->paddr + i,
PAGE_PRESENT | PAGE_RW | PAGE_USER);
}
// 3. 增加引用计数
shm->ref_count++;
return (void*)vaddr;
}
2. 共享内存对象管理
struct shm_object {
key_t key;
size_t size;
uint32_t paddr; // 物理地址
atomic_t ref_count; // 引用计数
spinlock_t lock; // 同步锁
};
🔑 共享内存的物理页在多个进程间共享,但虚拟地址可不同。
🖼️ 三、Display Server 实现
1. Server 初始化
// display_server.c
void display_server_init() {
// 1. 创建命令通道(管道)
display_ipc_fd = pipe_create();
// 2. 创建共享内存池
shm_pool = shmget(DISPLAY_SHM_KEY, 4 * 1024 * 1024, 0666); // 4MB
// 3. 启动 Server 主循环(作为内核线程)
kernel_thread_start(display_server_main, NULL);
}
2. 窗口管理
struct window {
int id;
int pid; // 所属 Client PID
int x, y, width, height;
int shm_offset; // 在共享内存中的偏移
bool visible;
struct window *next;
};
static struct window *window_list = NULL;
3. Server 主循环
void display_server_main(void *arg) {
while (1) {
// 1. 读取 IPC 命令
struct display_cmd cmd;
if (read(display_ipc_fd, &cmd, sizeof(cmd)) <= 0) continue;
// 2. 处理命令
switch (cmd.type) {
case CMD_CREATE_WINDOW:
handle_create_window(&cmd);
break;
case CMD_COMMIT:
handle_commit(&cmd);
break;
case CMD_CLOSE_WINDOW:
handle_close_window(&cmd);
break;
}
// 3. 合成并上屏(每 16ms 一帧)
if (need_repaint) {
compose_and_blit();
need_repaint = false;
}
}
}
4. 合成与上屏(Blit)
void compose_and_blit() {
// 1. 清空 Framebuffer
fb_clear();
// 2. 遍历窗口,从共享内存复制内容
for (struct window *win = window_list; win; win = win->next) {
if (!win->visible) continue;
// 获取共享内存虚拟地址
void *shm_vaddr = get_shm_vaddr(shm_pool) + win->shm_offset;
// 3. 复制到 Framebuffer(带窗口偏移)
for (int y = 0; y < win->height; y++) {
for (int x = 0; x < win->width; x++) {
uint32_t pixel = ((uint32_t*)shm_vaddr)[y * win->width + x];
if (pixel & 0xFF000000) { // 有 alpha
fb_putpixel(win->x + x, win->y + y, pixel);
}
}
}
}
}
✅ Server 是唯一写 Framebuffer 的实体!
🖌️ 四、Client 绘图 API
1. Client 初始化
// user/display_client.c
display_t* display_connect() {
display_t *disp = malloc(sizeof(display_t));
// 1. 连接到 Server IPC 通道
disp->ipc_fd = open("/dev/display", O_RDWR);
// 2. 附加共享内存
disp->shm = shmat(DISPLAY_SHM_KEY, NULL, 0);
disp->shm_size = 4 * 1024 * 1024;
return disp;
}
2. 创建窗口
int display_create_window(display_t *disp, int width, int height) {
struct display_cmd cmd = {
.type = CMD_CREATE_WINDOW,
.width = width,
.height = height
};
write(disp->ipc_fd, &cmd, sizeof(cmd));
// Server 返回窗口 ID
int win_id;
read(disp->ipc_fd, &win_id, sizeof(win_id));
return win_id;
}
3. 绘图与提交
void* display_get_buffer(display_t *disp, int win_id) {
// 从共享内存池分配区域(简化:固定偏移)
return (char*)disp->shm + win_id * MAX_WINDOW_SIZE;
}
void display_commit(display_t *disp, int win_id) {
struct display_cmd cmd = {
.type = CMD_COMMIT,
.win_id = win_id
};
write(disp->ipc_fd, &cmd, sizeof(cmd));
}
4. Client 绘图示例
// user/painter.c
void _start() {
display_t *disp = display_connect();
int win = display_create_window(disp, 320, 240);
uint32_t *buffer = display_get_buffer(disp, win);
// 绘制红色矩形
for (int y = 0; y < 100; y++) {
for (int x = 0; x < 100; x++) {
buffer[y * 320 + x] = 0xFFFF0000; // ARGB
}
}
// 提交帧
display_commit(disp, win);
sleep(10); // 保持 10 秒
exit(0);
}
📁 五、设备文件与 IPC 通道
1. 注册 Display 设备
// drivers/display.c
static struct file_operations display_fops = {
.read = display_read,
.write = display_write,
.poll = display_poll, // 支持 select
};
void display_init() {
struct device *dev = kmalloc(sizeof(struct device));
strcpy(dev->name, "display");
dev->devt = mkdev(MAJOR_DISPLAY, 0);
device_register(dev);
register_chrdev(MAJOR_DISPLAY, "display", &display_fops);
}
2. IPC 通道实现
- 使用 匿名管道(pipe) 或 Unix 域套接字(简化版)
- Server 创建管道,Client 通过
/dev/display访问
🧪 六、测试:多 Client 合成显示
启动两个 Client:
- Terminal Client:创建 640x480 窗口,显示文本
- Painter Client:创建 320x240 窗口,绘制图形
运行效果:
- Screen 显示两个窗口:
- 左上:Terminal(黑底白字)
- 右下:Painter(红色矩形)
- 无画面撕裂,窗口独立更新
✅ Display Server 成功合成多 Client 内容!
⚠️ 七、优化方向
- 双缓冲 Client
- Client 有 front/back buffer,提交时交换
- 避免 Server 读取到绘制一半的画面
- 脏矩形更新
- Client 只提交变化区域
- 减少内存拷贝量
- 硬件加速 Blit
- 利用 GPU 的 BitBLT 指令
- 需要更复杂的驱动
- 输入事件路由
- Server 接收键盘/鼠标事件
- 根据窗口位置路由到 Client
💡 现代 Wayland/X11 正是基于此架构演进而来!
💬 写在最后
Display Server 是图形系统的中枢神经。
它隔离了应用与硬件,
通过共享内存实现高效通信,
为现代 GUI 奠定基础。
今天你合成的第一个窗口,
正是 Wayland、X11、Windows Desktop 的雏形。
🌟 图形系统的优雅,在于让复杂对应用透明。
📬 动手挑战:
实现 display_close_window,并测试动态关闭窗口。
欢迎在评论区分享你的多窗口截图!
👇 下一篇你想看:输入事件处理与窗口焦点,还是 字体渲染与文本显示?
#操作系统 #内核开发 #DisplayServer #共享内存 #图形系统 #GUI #从零开始
📢 彩蛋:关注后回复关键词 "display",获取:
- 完整 Display Server/Client 代码
- 共享内存(shm)系统调用实现
- 多窗口合成测试程序模板