Operating System
华科《操作系统实验》
目录
- 实验一
- task_1 编译新内核
- task_2 添加新系统调用
- task_4 显示系统信息
- 实验二
- task_1 在 Linux 下创建两个线程 A 和 B, 循环输出数据或者字符串
- task_2 在 Linux 下创建父子进程,实现 wait 同步函数,理解父子进程同步
- task_3 在 Windows 下,利用线程实现并发画圆画方
- task_4 在 Linux 下利用线程实现“生产者-消费者”同步控制
- task_6 在 Linux 下模拟哲学家就餐,提供死锁和非死锁解法
- 实验三
- task_1 Linux模拟实现 OPT, FIFO, LRU 等淘汰算法
- task_2 Linux 下利用 /proc/pid/pagemap 技术计算某个变量或函数虚拟地址对应的物理地址等信息
- 实验四
- task_1 编写一个 Linux 内核模块,并完成模块的安装/卸载等操作
- task_2 在 Linux 平台编写一个字符设备的驱动程序和测试用的应用程序
- task_3 实现 proc 接口,将应用程序和驱动程序的部分交互信息写入proc文件系统,并在应用程序快结束时读出相关文件信息并显示出来
实验一
task_1 编译新内核
- 从 kernel.org 下载新内核
- 安装乱七八糟的包
- sudo make menuconfig 打开配置内核的图形界面,然后 Save Exit
- 修改 .config:
CONFIG_SYSTEM_TRUSTED_KEYS="",CONFIG_SYSTEM_REVOCATION_KEYS="" - make -j16 编译
- make modules_install 安装 modules
- make install 安装内核
task_2 添加新系统调用
- 旧内核版本:6.14.0.37-generic
- 新内核版本:6.17.12
修改三个文件:
- arch/x86/entry/syscalls/syscall_64.tbl
548 common Max sys_Max
549 common GetPID sys_GetPID
550 common GetCMD sys_GetCMD
- include/linux/syscalls.h
asmlinkage long sys_Max(int a, int b, int c);
asmlinkage long sys_GetPID(void);
asmlinkage long sys_GetCMD(char __user *buf, int len);
#endif
- kernel/sys.c
// 1. Max: 比较三个整数,返回最大值
SYSCALL_DEFINE3(Max, int, a, int, b, int, c)
{
int max = a;
if (b > max) max = b;
if (c > max) max = c;
printk(KERN_INFO "[MyCall] Max(%d, %d, %d) = %d\n", a, b, c, max);
return max;
}
// 2. GetPID: 获得当前进程 ID
SYSCALL_DEFINE0(GetPID)
{
printk(KERN_INFO "[MyCall] GetPID called. PID: %d\n", current->pid);
return current->pid;
}
// 3. GetCMD: 获得当前进程名称
SYSCALL_DEFINE2(GetCMD, char __user *, buf, int, len)
{
// 定义一个内核栈上的临时缓冲区
// TASK_COMM_LEN 通常是 16,定义在 <linux/sched.h> 中
char comm[TASK_COMM_LEN];
int comm_len;
get_task_comm(comm, current);
// 2. 计算长度
comm_len = strlen(comm) + 1;
// 3. 检查用户缓冲区大小
if (len < comm_len)
return -EINVAL;
// 4. 从“内核栈”拷贝到“用户空间”,这是被允许的
if (copy_to_user(buf, comm, comm_len))
return -EFAULT;
return 0;
}
/* --- My Custom System Calls End --- */
task_4 显示系统信息
sys_monitor.sh
#!/bin/bash
# 定义颜色变量,为了让输出好看一点
# 用 ANSI 转义码来实现彩色高亮
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m' # No Color,用完颜色记得清除,不然会乱
echo -e "${BLUE}${BOLD}=== Linux系统监测脚本运行报告 ===${NC}"
echo "----------------------------------------"
# ================= 1. 系统基本信息部分 =================
echo -e "${GREEN}${BOLD}1. 系统基本信息部分${NC}"
# 1. 获取系统名称
# 有的系统没有 lsb_release,所以先读文件试试,不行再用 uname
if [ -f /etc/os-release ]; then
# 读取配置文件里的变量
source /etc/os-release
echo "系统名称: $PRETTY_NAME"
else
echo "系统名称: $(uname -s)"
fi
# 2. 内核版本
echo "内核版本: $(uname -r)"
# 3. 运行时间
# 使用 -p 参数让时间显示更人性化 (pretty format)
echo "运行时间: $(uptime -p)"
# 4. 当前用户
# id -u 可以查到用户的 UID,whoami 查名字
echo "当前用户: $(whoami) (UID: $(id -u))"
# 5. CPU信息
# 直接去 /proc/cpuinfo 里 grep 找型号,uniq 去重只显示一行
# xargs 用来去除多余的空格
CPU_MODEL=$(grep 'model name' /proc/cpuinfo | head -1 | cut -d: -f2 | xargs)
# 统计 processor 出现的次数就知道有几个核了
CPU_CORES=$(grep -c 'processor' /proc/cpuinfo)
echo "CPU信息 : $CPU_MODEL ($CPU_CORES 核心)"
# 6. 内存信息
# 注意:这里加了 LC_ALL=C,强制命令输出英文,
# 否则如果系统是中文环境,grep "Mem" 可能会失败,导致变量为空报错
MEM_TOTAL=$(LC_ALL=C free -h | grep Mem | awk '{print $2}')
MEM_USED=$(LC_ALL=C free -h | grep Mem | awk '{print $3}')
MEM_AVAIL=$(LC_ALL=C free -h | grep Mem | awk '{print $7}')
echo "内存信息: 总共 $MEM_TOTAL, 已用 $MEM_USED, 可用 $MEM_AVAIL"
echo ""
# ================= 2. 系统状态部分 =================
echo -e "${GREEN}${BOLD}2. 系统状态部分${NC}"
# 1. 系统负载
# 直接读取 /proc/loadavg 文件的系统平均负载数据
LOAD_AVG=$(awk '{print $1", "$2", "$3}' /proc/loadavg)
echo "系统负载: $LOAD_AVG (1, 5, 15分钟)"
# 2. 当前时间
echo "当前时间: $(date "+%Y年%m月%d日 %H点%M分%S秒")"
# 3. CPU使用率
# 用 top 抓取一次状态 (-bn1),然后用 sed 提取 id (idle 空闲率)
# 只要算出空闲率,用 100 减去它就是使用率了
CPU_IDLE=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print $1}')
# 判断一下是否获取到了数值,防止计算报错
if [ -n "$CPU_IDLE" ]; then
# 用 bc 或者简单的 shell 算术运算
# 这里为了兼容性,用 awk 来做减法
CPU_USAGE=$(awk "BEGIN {print 100 - $CPU_IDLE}")
echo "CPU使用率: ${CPU_USAGE}%"
else
echo "CPU使用率: 无法获取"
fi
# 4. 内存使用率
# 为了计算百分比,这里用 bytes 为单位取数值 (free 不带 -h)
# 同样加上 LC_ALL=C 防止出错
MEM_TOTAL_NUM=$(LC_ALL=C free | grep Mem | awk '{print $2}')
MEM_USED_NUM=$(LC_ALL=C free | grep Mem | awk '{print $3}')
# 这里的 awk 用来做浮点数除法运算,保留两位小数
# 加上判断防止分母为 0 的情况
if [ -n "$MEM_TOTAL_NUM" ] && [ "$MEM_TOTAL_NUM" -gt 0 ]; then
MEM_PERCENT=$(awk "BEGIN {printf \"%.2f\", $MEM_USED_NUM / $MEM_TOTAL_NUM * 100}")
echo "内存使用率: ${MEM_PERCENT}%"
else
echo "内存使用率: 无法计算"
fi
echo ""
# ================= 3. 进程信息部分 =================
echo -e "${GREEN}${BOLD}3. 进程信息部分${NC}"
# 使用 ps 命令自定义输出格式:PID, 用户, CPU占用, 内存占用, 命令名
# --sort 参数用来排序,-pcpu 表示按 CPU 降序,-pmem 表示按内存降序
echo -e "${BOLD}CPU占用最高的5个进程:${NC}"
# head -n 6 是因为第一行是标题,所以取前6行就是 Top 5
ps -eo pid,user,pcpu,pmem,comm --sort=-pcpu | head -n 6 | awk '{printf "%-8s %-10s %-8s %-8s %s\n", $1, $2, $3"%", $4"%", $5}'
echo ""
echo -e "${BOLD}内存占用最高的5个进程:${NC}"
ps -eo pid,user,pcpu,pmem,comm --sort=-pmem | head -n 6 | awk '{printf "%-8s %-10s %-8s %-8s %s\n", $1, $2, $3"%", $4"%", $5}'
echo "----------------------------------------"
实验一截图
![[Pasted image 20251223153920.png]]
实验二
task_1 在 Linux 下创建两个线程 A 和 B, 循环输出数据或者字符串
task_1.cpp
#include <pthread.h>
#include <iostream>
#include <unistd.h>
// 线程 A 的执行函数
void* ThreadAFunc(void* arg) {
for (int i = 1; i <= 1000; ++i) {
std::cout << "A:" << i << std::endl;
usleep(200000); // 暂停 0.2 秒 (200,000 微秒)
}
return NULL;
}
// 线程 B 的执行函数
void* ThreadBFunc(void* arg) {
for (int i = 1000; i >= 1; --i) {
std::cout << "B:" << i << std::endl;
usleep(200000); // 暂停 0.2 秒
}
return NULL;
}
int main() {
pthread_t threadA, threadB;
// 创建线程 A
if (pthread_create(&threadA, NULL, ThreadAFunc, NULL) != 0) {
std::cerr << "Error creating thread A" << std::endl;
return 1;
}
// 创建线程 B
if (pthread_create(&threadB, NULL, ThreadBFunc, NULL) != 0) {
std::cerr << "Error creating thread B" << std::endl;
return 1;
}
// 等待线程结束
pthread_join(threadA, NULL);
pthread_join(threadB, NULL);
return 0;
}
g++ task_1.cpp -o task_1 -lpthread
- 利用
pthread库同时启动两个线程 A 和 B - 线程 A:从 1 累加到 1000
- 线程 B:从 1000 递减到 1
- 每个循环均使用
usleep(200000)暂停 0.2 秒,使两个线程在控制台交替输出 main函数使用pthread_join等待两个子线程全部执行完毕后才退出
task_1 截图
![[Pasted image 20251223154235.png]]
task_2 在 Linux 下创建父子进程,实现 wait 同步函数,理解父子进程同步
task_2.cpp
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <cstdlib>
int main() {
pid_t pid = fork();
if (pid < 0) {
// Fork 失败
std::cerr << "Fork failed!" << std::endl;
return 1;
} else if (pid == 0) {
// 子进程代码
std::cout << "[Child] I am the child process (PID: " << getpid() << ")." << std::endl;
std::cout << "[Child] Sleeping for 5 seconds..." << std::endl;
sleep(5);
std::cout << "[Child] Woke up. Exiting with status 42." << std::endl;
exit(42); // 子进程返回 42
} else {
// 父进程代码
std::cout << "[Parent] I am the parent process (PID: " << getpid() << ")." << std::endl;
std::cout << "[Parent] Created child process with PID: " << pid << std::endl;
int status;
std::cout << "[Parent] Waiting for child to exit..." << std::endl;
// 等待任意子进程结束
pid_t child_pid = wait(&status);
if (WIFEXITED(status)) {
// 子进程正常退出
int exit_status = WEXITSTATUS(status);
std::cout << "[Parent] Child process " << child_pid << " exited normally." << std::endl;
std::cout << "[Parent] The return status is: " << exit_status << std::endl;
} else {
std::cout << "[Parent] Child process exited abnormally." << std::endl;
}
}
return 0;
}
g++ task_2.cpp -o task_2
- 调用
fork()创建一个与父进程几乎完全一样的子进程。此时程序通过返回值 pid 分流:pid == 0:当前处于子进程环境; pid > 0:当前处于父进程环境(返回值为子进程的 PID) - 子进程行为:打印自身 PID 后进入 5 秒休眠,随后通过
exit(42)退出并返回状态码 42 - 父进程行为:调用
wait(&status)进入阻塞状态,等待子进程结束 - 状态捕获:子进程退出后,父进程被唤醒,利用宏
WIFEXITED确认子进程是正常退出的,并用WEXITSTATUS提取出子进程返回的数值 42
task_2 截图
![[Pasted image 20251223154345.png]]
task_3 在 Windows 下,利用线程实现并发画圆画方
task_3.cpp
#include <iostream>
#include <math.h>
#include <windows.h>
#define PI 3.14159265
// 全局变量,用于窗口句柄
HWND hGraphWindow = NULL;
// 窗口过程函数 (处理窗口消息)
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 这里什么都不做,留给线程去画
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// 专门用于创建和维护窗口的线程
DWORD WINAPI WindowThread(LPVOID lpParam) {
const char CLASS_NAME[] = "Sample Window Class";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 白色背景
RegisterClass(&wc);
hGraphWindow = CreateWindowEx(0, CLASS_NAME, "Concurrent Drawing Lab",
WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100,
800, 600, // 窗口位置和大小
NULL, NULL, GetModuleHandle(NULL), NULL);
if (hGraphWindow == NULL)
return 0;
// 消息循环
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
// 线程 A:画圆
DWORD WINAPI DrawCircle(LPVOID lpParam) {
// 等待窗口创建完毕
while (hGraphWindow == NULL)
Sleep(10);
HDC hdc = GetDC(hGraphWindow);
int centerX = 300;
int centerY = 300;
int radius = 100;
COLORREF color = RGB(255, 0, 0); // 红色
for (int i = 0; i < 720; ++i) {
double angle = (double)i * PI / 360.0;
int x = centerX + (int)(radius * cos(angle));
int y = centerY + (int)(radius * sin(angle));
SetPixel(hdc, x, y, color);
// 画大一点点,防止看不清
SetPixel(hdc, x + 1, y, color);
SetPixel(hdc, x, y + 1, color);
SetPixel(hdc, x + 1, y + 1, color);
Sleep(2); // 稍微快一点
}
ReleaseDC(hGraphWindow, hdc);
return 0;
}
// 线程 B:画正方形
DWORD WINAPI DrawSquare(LPVOID lpParam) {
// 等待窗口创建完毕
while (hGraphWindow == NULL)
Sleep(10);
HDC hdc = GetDC(hGraphWindow);
int centerX = 600;
int centerY = 300;
int sideLength = 200;
int halfSide = sideLength / 2;
COLORREF color = RGB(0, 0, 255); // 蓝色
int steps = 180;
// 定义四个顶点的逻辑
// 上边
for (int i = 0; i < steps; i++) {
int x = (centerX - halfSide) + (int)((double)sideLength * i / steps);
int y = centerY - halfSide;
SetPixel(hdc, x, y, color);
SetPixel(hdc, x + 1, y, color);
SetPixel(hdc, x, y + 1, color);
Sleep(2);
}
// 右边
for (int i = 0; i < steps; i++) {
int x = centerX + halfSide;
int y = (centerY - halfSide) + (int)((double)sideLength * i / steps);
SetPixel(hdc, x, y, color);
SetPixel(hdc, x + 1, y, color);
SetPixel(hdc, x, y + 1, color);
Sleep(2);
}
// 下边
for (int i = 0; i < steps; i++) {
int x = (centerX + halfSide) - (int)((double)sideLength * i / steps);
int y = centerY + halfSide;
SetPixel(hdc, x, y, color);
SetPixel(hdc, x + 1, y, color);
SetPixel(hdc, x, y + 1, color);
Sleep(2);
}
// 左边
for (int i = 0; i < steps; i++) {
int x = centerX - halfSide;
int y = (centerY + halfSide) - (int)((double)sideLength * i / steps);
SetPixel(hdc, x, y, color);
SetPixel(hdc, x + 1, y, color);
SetPixel(hdc, x, y + 1, color);
Sleep(2);
}
ReleaseDC(hGraphWindow, hdc);
return 0;
}
int main() {
// 1. 启动窗口线程
HANDLE hWinThread = CreateThread(NULL, 0, WindowThread, NULL, 0, NULL);
std::cout << "Waiting for window..." << std::endl;
while (hGraphWindow == NULL)
Sleep(100);
std::cout << "Window created! Drawing..." << std::endl;
// 2. 启动画图线程
HANDLE hThread1 = CreateThread(NULL, 0, DrawCircle, NULL, 0, NULL);
HANDLE hThread2 = CreateThread(NULL, 0, DrawSquare, NULL, 0, NULL);
// 等待画图完成
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
std::cout << "Drawing finished. Close the window to exit." << std::endl;
// 等待窗口关闭
WaitForSingleObject(hWinThread, INFINITE);
return 0;
}
g++ .\task_3.cpp -o task_3 -lgdi32
- 通过
CreateThread启动了一个专门的WindowThread线程,用于注册窗口类、创建HWND窗口并运行GetMessage消息循环 - 确认窗口句柄
hGraphWindow有效后,同时启动两个绘图线程:DrawCircle线程:利用三角函数cos和sin计算坐标,在窗口左侧绘制一个红色圆形;DrawSquare线程:通过四个顺序执行的for循环计算线性坐标,在窗口右侧绘制一个蓝色正方形 - 调用
GetDC获取窗口的设备上下文(HDC),并使用SetPixel逐点打点绘图。通过Sleep(2)控制绘制速度,展示两个图形同时生长的动画效果 main函数利用WaitForSingleObject阻塞等待两个绘图线程执行完毕
task_3 截图
![[Pasted image 20251223154415.png]]
task_4 在 Linux 下利用线程实现“生产者-消费者”同步控制
task_4.cpp
#include <pthread.h>
#include <semaphore.h>
#include <iostream>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#define BUFFER_SIZE 10
// 缓冲区
int buffer[BUFFER_SIZE];
int in = 0; // 写入位置
int out = 0; // 读取位置
// 信号量和互斥锁
sem_t empty_slots; // 空槽位数量
sem_t full_slots; // 满槽位数量 (即产品数量)
pthread_mutex_t mutex; // 互斥锁,保护缓冲区
// 生产者线程函数
void* Producer(void* arg) {
int id = *(int*)arg;
int start_val = (id == 1) ? 1000 : 2000;
int end_val = (id == 1) ? 1999 : 2999;
for (int i = start_val; i <= end_val; ++i) {
int data = i;
// 随机睡眠 100ms - 1s
usleep((rand() % 900000) + 100000);
// 等待空槽位
sem_wait(&empty_slots);
// 获取互斥锁
pthread_mutex_lock(&mutex);
// 放入数据
buffer[in] = data;
std::cout << "[Producer " << id << "] Produced: " << data << " at index " << in << std::endl;
in = (in + 1) % BUFFER_SIZE;
// 释放互斥锁
pthread_mutex_unlock(&mutex);
// 增加满槽位信号量
sem_post(&full_slots);
}
return NULL;
}
// 消费者线程函数
void* Consumer(void* arg) {
int id = *(int*)arg;
while (true) {
// 随机睡眠 100ms - 1s
usleep((rand() % 900000) + 100000);
// 等待满槽位 (有产品)
sem_wait(&full_slots);
// 获取互斥锁
pthread_mutex_lock(&mutex);
// 取出数据
int data = buffer[out];
std::cout << " [Consumer " << id << "] Consumed: " << data << " from index " << out << std::endl;
out = (out + 1) % BUFFER_SIZE;
// 释放互斥锁
pthread_mutex_unlock(&mutex);
// 增加空槽位信号量
sem_post(&empty_slots);
}
return NULL;
}
int main() {
// 初始化信号量和互斥锁
sem_init(&empty_slots, 0, BUFFER_SIZE); // 初始空槽位为 10
sem_init(&full_slots, 0, 0); // 初始产品数为 0
pthread_mutex_init(&mutex, NULL);
pthread_t p1, p2;
pthread_t c1, c2, c3;
int id1 = 1, id2 = 2, id3 = 3; // 线程 ID
// 创建消费者 (先创建消费者或者生产者都可以)
pthread_create(&c1, NULL, Consumer, &id1);
pthread_create(&c2, NULL, Consumer, &id2);
pthread_create(&c3, NULL, Consumer, &id3);
// 创建生产者
pthread_create(&p1, NULL, Producer, &id1);
pthread_create(&p2, NULL, Producer, &id2);
// 等待生产者结束 (消费者是死循环,这里主线程只等生产者)
pthread_join(p1, NULL);
pthread_join(p2, NULL);
// 清理资源
sem_destroy(&empty_slots);
sem_destroy(&full_slots);
pthread_mutex_destroy(&mutex);
return 0;
}
g++ task_4.cpp -o task_4 -lpthread
- 利用
sem_t类型的两个信号量empty_slots(空位)和full_slots(产品数)来协调生产与消费的节奏 - 使用
pthread_mutex_t互斥锁保护共享的循环缓冲区buffer以及索引变量in和out。确保同一时刻只有一个线程可以操作缓冲区,避免竞态条件 - 生产者:调用
sem_wait确认有空位后,获取互斥锁并将数据写入buffer[in]。完成写入后通过sem_post增加产品计数,通知消费者可以读取 - 消费者:调用
sem_wait确认有产品后,获取互斥锁从buffer[out]取出数据。处理完毕后通过sem_post增加空位计数,通知生产者缓冲区已有新空间 - 通过
(index + 1) % BUFFER_SIZE的取模运算,实现固定大小数组的循环利用,使in和out指针在缓冲区内首尾相连地循环移动
task_4 截图
![[Pasted image 20251223154452.png]]
task_6 在 Linux 下模拟哲学家就餐,提供死锁和非死锁解法
task_6_gui.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <ncurses.h>
#include <signal.h>
#define PHILOSOPHER_NUM 5
// ============================================================================
// 开关配置:
// 0 = 【死锁演示模式】(拿到左筷子后强制停顿 1s)
// 1 = 【非死锁解法模式】(使用 trylock 策略)
#define ENABLE_NO_DEADLOCK_SOLUTION 1
// ============================================================================
typedef enum {
STATE_THINKING, // 思考 (Blue)
STATE_HUNGRY, // 饿了/等左筷子 (Yellow)
STATE_GOT_LEFT, // 拿到了左筷子/等右筷子 (Magenta)
STATE_EATING, // 吃饭 (Green)
STATE_GIVE_UP // 放弃并放下筷子 (Red blink)
} State;
pthread_mutex_t chopsticks[PHILOSOPHER_NUM];
pthread_mutex_t screen_lock;
State philo_states[PHILOSOPHER_NUM];
void random_sleep(int min_ms, int max_ms) {
int sleep_micros = (rand() % (max_ms - min_ms + 1) + min_ms) * 1000;
usleep(sleep_micros);
}
void draw_interface() {
pthread_mutex_lock(&screen_lock);
mvprintw(1, 2, "=== Philosopher's Dining (Ncurses GUI) ===");
if (ENABLE_NO_DEADLOCK_SOLUTION)
mvprintw(3, 2, "Mode: [NO DEADLOCK] (TryLock)");
else
mvprintw(3, 2, "Mode: [DEADLOCK DEMO] (Force Wait 1s)");
mvprintw(5, 5, "ID | Left Fork | Status | Right Fork");
mvprintw(6, 5, "-----|-----------|-------------------|-----------");
for (int i = 0; i < PHILOSOPHER_NUM; i++) {
int row = 8 + i * 2;
mvprintw(row, 5, "[P%d]", i);
mvprintw(row, 12, "%d", i);
move(row, 24);
switch (philo_states[i]) {
case STATE_THINKING:
attron(COLOR_PAIR(1)); printw(" Thinking... "); attroff(COLOR_PAIR(1)); break;
case STATE_HUNGRY:
attron(COLOR_PAIR(2)); printw(" Waiting Left... "); attroff(COLOR_PAIR(2)); break;
case STATE_GOT_LEFT:
attron(COLOR_PAIR(3)); printw(" HAS LEFT > WAIT "); attroff(COLOR_PAIR(3)); break;
case STATE_EATING:
attron(COLOR_PAIR(4)); printw(" ** EATING ** "); attroff(COLOR_PAIR(4)); break;
case STATE_GIVE_UP:
attron(COLOR_PAIR(5)); printw(" !! GIVE UP !! "); attroff(COLOR_PAIR(5)); break;
}
mvprintw(row, 44, "%d", (i + 1) % PHILOSOPHER_NUM);
}
mvprintw(20, 2, "Press Ctrl+C to exit.");
refresh();
pthread_mutex_unlock(&screen_lock);
}
void update_state(int id, State s) {
philo_states[id] = s;
draw_interface();
}
void* philosopher(void* arg) {
int id = *(int*)arg;
int left = id;
int right = (id + 1) % PHILOSOPHER_NUM;
while (1) {
#if ENABLE_NO_DEADLOCK_SOLUTION
// ================= 【非死锁模式】 =================
update_state(id, STATE_THINKING);
random_sleep(100, 500); // 符合要求:100ms-500ms
update_state(id, STATE_HUNGRY);
// 尝试拿左边
if (pthread_mutex_lock(&chopsticks[left]) == 0) {
update_state(id, STATE_GOT_LEFT);
random_sleep(100, 500); // 模拟拿起的动作
// 尝试拿右边 (TryLock)
if (pthread_mutex_trylock(&chopsticks[right]) == 0) {
update_state(id, STATE_EATING);
random_sleep(100, 500); // 符合要求:吃饭 100ms-500ms
pthread_mutex_unlock(&chopsticks[right]);
// 放下右筷子后,状态暂回左手持有(瞬间)
update_state(id, STATE_GOT_LEFT);
} else {
// 拿不到,放弃
update_state(id, STATE_GIVE_UP);
random_sleep(100, 500); // 展示放弃状态
}
pthread_mutex_unlock(&chopsticks[left]);
// 放弃后稍作等待
random_sleep(100, 500);
}
#else
// ================= 【死锁模式】 =================
update_state(id, STATE_THINKING);
random_sleep(100, 500); // 符合要求:100ms-500ms
update_state(id, STATE_HUNGRY);
// 1. 拿左边
pthread_mutex_lock(&chopsticks[left]);
update_state(id, STATE_GOT_LEFT);
// 【致命停顿】:拿到左筷子后,强制等1秒
sleep(1);
// 2. 拿右边 (死锁发生点)
pthread_mutex_lock(&chopsticks[right]);
update_state(id, STATE_EATING);
random_sleep(100, 500); // 符合要求:吃饭 100ms-500ms
pthread_mutex_unlock(&chopsticks[right]);
pthread_mutex_unlock(&chopsticks[left]);
#endif
}
return NULL;
}
void finish(int sig) {
endwin();
exit(0);
}
int main() {
signal(SIGINT, finish);
initscr();
cbreak();
noecho();
curs_set(0);
start_color();
init_pair(1, COLOR_BLUE, COLOR_BLACK);
init_pair(2, COLOR_YELLOW, COLOR_BLACK);
init_pair(3, COLOR_MAGENTA, COLOR_BLACK);
init_pair(4, COLOR_GREEN, COLOR_BLACK);
init_pair(5, COLOR_RED, COLOR_BLACK);
srand(time(NULL));
pthread_mutex_init(&screen_lock, NULL);
pthread_t philo[PHILOSOPHER_NUM];
int ids[PHILOSOPHER_NUM];
for (int i = 0; i < PHILOSOPHER_NUM; i++) {
pthread_mutex_init(&chopsticks[i], NULL);
}
draw_interface();
for (int i = 0; i < PHILOSOPHER_NUM; i++) {
ids[i] = i;
if (pthread_create(&philo[i], NULL, philosopher, &ids[i]) != 0) {
endwin();
perror("Create thread failed");
return 1;
}
}
for (int i = 0; i < PHILOSOPHER_NUM; i++) {
pthread_join(philo[i], NULL);
}
endwin();
return 0;
}
gcc task_6_gui.c -o task_6_gui -lpthread -lncurses
利用 ncurses 库构建字符图形界面,实时显示每个哲学家的状态(思考、饥饿、持有左筷子、进食、放弃)。通过 screen_lock 确保多线程刷新界面时不会产生乱序
- 死锁
- 哲学家先调用
pthread_mutex_lock锁定左手筷子,随后强制执行sleep(1) - 通过
sleep(1)确保所有哲学家都已成功持有左手筷子,强制死锁(不加这个sleep(1)的话可能得等很久才会出现死锁) - 哲学家再次调用
pthread_mutex_lock锁定右手筷子。由于右侧筷子已被邻座哲学家作为“左手筷子”持有且不释放,导致所有线程永久阻塞
- 非死锁
- 哲学家锁定左手筷子后,改用
pthread_mutex_trylock尝试获取右手筷子 - 如果右手筷子已被占用,
trylock会立即返回失败,哲学家随后会通过pthread_mutex_unlock释放已持有的左手筷子
task_6 截图
死锁:
![[Pasted image 20251223154600.png]]
非死锁:
![[Pasted image 20251223154530.png]]
实验三
task_1 Linux模拟实现 OPT, FIFO, LRU 等淘汰算法
task_1.cpp
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <climits>
#include <iomanip>
#include <queue> // 新增:用于FIFO算法的队列
using namespace std;
class LruAndOpt {
private:
// 设置页面大小为10
const int pageSize = 10;
// 物理页框数量
const int frameCount = 3;
public:
int getPageNumber(int address) {
return address / pageSize;
}
// ================= FIFO 算法 (新增) =================
void Fifo(const vector<int>& A) {
int count = 0; // 缺页计数
int n = A.size();
unordered_map<int, int> pageMap; // 逻辑页 -> 物理页框
queue<int> q; // 记录进入内存的顺序
for (int i = 0; i < n; i++) {
int page = getPageNumber(A[i]);
// 第一次访问特殊处理
if (i == 0) {
count++;
pageMap[page] = 1;
q.push(page);
} else {
// 如果页面不在内存中 (缺页)
if (pageMap.find(page) == pageMap.end()) {
count++;
// 如果还有空闲物理页框
if (pageMap.size() < frameCount) {
int newFrameId = pageMap.size() + 1;
pageMap[page] = newFrameId;
q.push(page);
} else {
// 内存已满,需要置换
// FIFO 核心:淘汰最早进入队列的页面
int eliminate = q.front();
q.pop();
// 获取被淘汰页面的物理页框号,给新页面用
int phy = pageMap[eliminate];
pageMap.erase(eliminate);
// 装入新页
pageMap[page] = phy;
q.push(page);
}
}
// 如果页面已经在内存中 (Hit),FIFO 不做任何操作,也不改变队列顺序
}
}
cout << "访问次数:" << n << ",缺页次数:" << count << ",缺页率:";
cout << fixed << setprecision(2) << ((double)count / n * 100) << "%" << endl;
}
// ================= OPT 算法 =================
void Opt(const vector<int>& A) {
int count = 0;
int n = A.size();
unordered_map<int, int> pageMap;
for (int i = 0; i < n; i++) {
int page = getPageNumber(A[i]);
if (i == 0) {
count++;
pageMap[page] = 1;
} else {
if (pageMap.find(page) == pageMap.end()) {
count++;
if (pageMap.size() < frameCount) {
pageMap[page] = pageMap.size() + 1;
} else {
int eliminate = 0;
int latest = -1;
for (auto const& [k, v] : pageMap) {
bool found = false;
int time = 0;
for (int j = i + 1; j < n; j++) {
if (k == getPageNumber(A[j])) {
found = true;
time = j;
break;
}
}
if (found) {
if (latest < time) {
latest = time;
eliminate = k;
}
} else {
eliminate = k;
break;
}
}
int phy = pageMap[eliminate];
pageMap.erase(eliminate);
pageMap[page] = phy;
}
}
}
}
cout << "访问次数:" << n << ",缺页次数:" << count << ",缺页率:";
cout << fixed << setprecision(2) << ((double)count / n * 100) << "%" << endl;
}
// ================= LRU 算法 =================
void Lru(const vector<int>& A) {
int n = A.size();
int count = 0;
unordered_map<int, int> pageMap;
for (int i = 0; i < n; i++) {
int page = getPageNumber(A[i]);
if (i == 0) {
count++;
pageMap[page] = 1;
} else {
if (pageMap.find(page) == pageMap.end()) {
count++;
if (pageMap.size() < frameCount) {
pageMap[page] = pageMap.size() + 1;
} else {
int eliminate = 0;
int latest = INT_MAX;
int k = 0;
for (int j = i - 1; j >= 0; j--) {
int pastPage = getPageNumber(A[j]);
if (pageMap.count(pastPage) && latest > j) {
latest = j;
k++;
eliminate = pastPage;
if (k == frameCount) {
break;
}
}
}
int phy = pageMap[eliminate];
pageMap.erase(eliminate);
pageMap[page] = phy;
}
}
}
}
cout << "访问次数:" << n << ",缺页次数:" << count << ",缺页率:";
cout << fixed << setprecision(2) << ((double)count / n * 100) << "%" << endl;
}
};
int main() {
srand((unsigned)time(NULL));
LruAndOpt lao;
cout << "页面大小为10,物理页框共3个" << endl;
cout << "请输入访问序列的个数:";
int n;
if (!(cin >> n)) return 0;
cout << "请输入随机数的限制:";
int limit;
cin >> limit;
// 1. 随机序列
vector<int> A1(n);
for (int i = 0; i < n; i++) {
A1[i] = rand() % limit;
}
// 2. 顺序序列 (0, 1, 2, 3...)
vector<int> A2(n);
for (int i = 0; i < n; i++) {
A2[i] = i;
}
// 3. 循环序列 (模拟局部性,如 0, 1, 2...99, 0, 1...)
vector<int> A3(n);
for (int i = 0; i < n; i++) {
A3[i] = i % 100; // 假设循环范围
}
cout << "=================OPT算法=================" << endl;
cout << "-----------------随机序列-----------------" << endl;
lao.Opt(A1);
cout << "-----------------顺序序列-----------------" << endl;
lao.Opt(A2);
cout << "-----------------循环序列-----------------" << endl;
lao.Opt(A3);
cout << endl << "=================FIFO算法=================" << endl;
cout << "-----------------随机序列-----------------" << endl;
lao.Fifo(A1);
cout << "-----------------顺序序列-----------------" << endl;
lao.Fifo(A2);
cout << "-----------------循环序列-----------------" << endl;
lao.Fifo(A3);
cout << endl << "=================LRU算法=================" << endl;
cout << "-----------------随机序列-----------------" << endl;
lao.Lru(A1);
cout << "-----------------顺序序列-----------------" << endl;
lao.Lru(A2);
cout << "-----------------循环序列-----------------" << endl;
lao.Lru(A3);
return 0;
}
- FIFO (先进先出)
- 原理:总是淘汰最早进入内存的页面。
- 实现:使用
std::queue<int> q。新页进来排在队尾,需要置换时直接弹出队首(q.front())
- OPT (最佳置换算法)
- 原理:淘汰以后永不使用,或者在最长时间内不再被访问的页面
- 实现:代码会向后遍历数组 A(
for (int j = i + 1; j < n; j++)),寻找内存中哪个页面在未来最晚才被用到。
- LRU (最近最久未使用)
- 原理:淘汰最近一段时间内最久没有被使用的页面
- 实现:代码通过向后回溯(
for (int j = i - 1; j >= 0; j--))来观察哪些页面最近刚用过,挑出那个最远没用的
task_1 截图
![[Pasted image 20251229193709.png]]
task_2 Linux 下利用 /proc/pid/pagemap 技术计算某个变量或函数虚拟地址对应的物理地址等信息
task_2.cpp
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
//获取物理地址 //va为虚拟内存地址
void VA2PA(unsigned long va) {
//页面大小
size_t pageSize = getpagesize();
//页号
unsigned long pageIndex = va / pageSize;
//页内偏移
unsigned long offset = va % pageSize;
FILE* fp;
// 定义用于接收读取结果的变量 (修复错误 A)
uint64_t it;
printf("Virtual Address: 0x%lx\n", va);
printf("Page Index: 0x%lx\nPage Offset: 0x%lx\n", pageIndex, offset);
if((fp = fopen("/proc/self/pagemap", "rb")) == NULL) {
printf("Error: Cannot open pagemap. Are you root?\n");
return;
}
unsigned long fileOffset = pageIndex * sizeof(uint64_t);
if(fseek(fp, fileOffset, SEEK_SET) != 0) {
printf("Error: fseek failed!\n");
fclose(fp);
return;
}
if(fread(&it, sizeof(uint64_t), 1, fp) != 1) {
printf("Error: fread failed!\n");
fclose(fp);
return;
}
fclose(fp);
// 第63位记录当前页面位置:1为在物理内存中,0表示不在物理内存中
// 修复错误 C: 增加括号修正优先级
if(((it >> 63) & 1) == 0) {
printf("Page Present is 0.\nNot in the Physical Memory.\n");
return;
}
// 0-54位为物理页号
uint64_t pPageIndex = (((uint64_t)1 << 55) - 1) & it;
printf("Physical Page Index: 0x%" PRIx64 "\n", pPageIndex);
// 物理地址 = 物理页号*页大小+页内偏移
unsigned long pa = pPageIndex * pageSize + offset;
printf("Physical Address: 0x%lx\n\n", pa);
}
int a = 1000;
int max(int a, int b) {
return a >= b ? a : b;
}
int main() {
printf("pid = %d\n", getpid());
printf("Global variable a:\n");
VA2PA((unsigned long)&a);
printf("Function max:\n");
VA2PA((unsigned long)(void*)&max);
return 0;
}
- main 函数
- 打印当前进程 ID
- 计算变量 a 虚拟地址对应的物理地址
- 打印函数 max 虚拟地址对应的物理地址
- VA2PA 函数
- 使用
getpagesize()获取系统页大小(通常为 4KB),计算输入地址对应的虚拟页号 (VPN) 和页内偏移 (Offset) - 打开
/proc/self/pagemap(内核提供的页表映射接口),利用fseek定位到对应页号的条目位置。计算公式为:文件偏移量 = 虚拟页号 * 8字节 - 读取 8字节(64位)的页表条目数据 (
uint64_t) - 检查读取数据的第63位(Present Bit:为 1 表明页面在内存中
- 利用位掩码 (
0-54位) 从条目中提取物理页框号 (PFN) - 通过公式
物理地址 = 物理页框号 * 页大小 + 页内偏移得出最终物理地址并打印。
- 使用
task_2 截图
![[Pasted image 20251229195404.png]]
实验四
task_1 编写一个 Linux 内核模块,并完成模块的安装/卸载等操作
- Makefile
# 动态获取当前运行的内核版本
KVER := $(shell uname -r)
# 自动指向对应的内核构建目录
KDIR := /lib/modules/$(KVER)/build
PWD := $(shell pwd)
# 指定目标文件
obj-m := ycg.o
# 默认编译动作
all:
make -C $(KDIR) M=$(PWD) modules
# 清理编译生成的文件
clean:
make -C $(KDIR) M=$(PWD) clean
- ycg.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
static char *name = "changganyin";
static int times = 1;
// 接收命令行参数
module_param(times, int, 0644);
module_param(name, charp, 0644);
static int hello_init(void)
{
int i;
for(i = 0 ; i < times; i++)
printk(KERN_ALERT "(%d) hello, %s!\n", i, name);
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, %s!\n", name);
}
MODULE_LICENSE("Dual BSD/GPL");
module_init(hello_init);
module_exit(hello_exit);
代码逻辑:
- 定义两个全局变量
name和times,并用module_param宏将这两个变量声明为模块参数 - 循环体内,调用内核日志函数
printk(级别为KERN_ALERT),多次打印带有name变量的问候语
模块的安装和卸载
make # 编译
sudo insmod ycg.ko name="ycg" times=7 # 安装模块
sudo rmmod ycg # 卸载模块
task_1 截图
![[Pasted image 20251230144542.png]]
![[Pasted image 20251230144918.png]]
task_2 在 Linux 平台编写一个字符设备的驱动程序和测试用的应用程序
- ycgDrive.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/types.h>
static struct cdev drv;
static dev_t ndev;
static char ycg[32];
static int drv_open(struct inode *nd, struct file *fp)
{
int major = MAJOR(nd->i_rdev);
int minor = MINOR(nd->i_rdev);
printk(KERN_EMERG "ycgDrive: open, major = %d, minor = %d\n", major, minor);
return 0;
}
static ssize_t drv_read(struct file* fp, char __user* u, size_t sz, loff_t* loff)
{
char res[32];
int i = 0;
int num1 = 0;
int num2 = 0;
bool flag = true; // true为加法, false为减法
printk(KERN_EMERG "ycgDrive: read operation.\n");
// 解析字符串逻辑 (例如 "10+20"),读取 ycg 缓冲区
while (ycg[i] != '+' && ycg[i] != '-' && ycg[i] != '\0')
{
if(ycg[i] >= '0' && ycg[i] <= '9')
num1 = 10 * num1 + (ycg[i] - '0');
i++;
}
if (ycg[i] == '-') flag = false;
if (ycg[i] == '+' || ycg[i] == '-') i++; // 跳过符号位
while (ycg[i] != '\0')
{
if(ycg[i] >= '0' && ycg[i] <= '9')
num2 = 10 * num2 + (ycg[i] - '0');
i++;
}
if (flag == true)
{
printk(KERN_EMERG "%d + %d = %d\n", num1, num2, num1 + num2);
sprintf(res, "%d + %d = %d\n", num1, num2, num1 + num2);
}
else
{
printk(KERN_EMERG "%d - %d = %d\n", num1, num2, num1 - num2);
sprintf(res, "%d - %d = %d\n", num1, num2, num1 - num2);
}
// 将结果复制回用户空间
if (copy_to_user(u, res, strlen(res)))
return -EFAULT;
return 0;
}
static ssize_t drv_write(struct file* fp, const char __user * u, size_t sz, loff_t* loff){
printk(KERN_EMERG "ycgDrive: write operation.\n");
// 从用户空间复制数据到内核缓冲区 ycg
if (copy_from_user(ycg, u, sz))
return -EFAULT;
ycg[sz] = '\0'; // 确保字符串结束符
return 0;
}
// 绑定操作函数
static struct file_operations drv_ops =
{
.owner = THIS_MODULE,
.open = drv_open,
.read = drv_read,
.write = drv_write,
};
static int changganyinDrv_init(void)
{
int ret;
cdev_init(&drv, &drv_ops);
// 动态申请设备号,设备名为 "ycgDrive"
ret = alloc_chrdev_region(&ndev, 0, 1, "ycgDrive");
if (ret < 0)
{
printk(KERN_EMERG "ycgDrive: alloc_chrdev_region error.\n");
return ret;
}
// 打印申请到的主设备号
printk(KERN_EMERG "ycgDrive: major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev));
ret = cdev_add(&drv, ndev, 1);
if (ret < 0)
{
printk(KERN_EMERG "ycgDrive: cdev_add error.\n");
return ret;
}
return 0;
}
static void changganyinDrv_exit(void)
{
printk("ycgDrive: exit process!\n");
cdev_del(&drv);
unregister_chrdev_region(ndev, 1);
}
module_init(changganyinDrv_init);
module_exit(changganyinDrv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YCG");
代码逻辑:
- 使用全局内核缓冲区
ycg[32]在write和read操作之间传递数据,实现对字符串算式的解析与计算 - drv_write: 使用
copy_from_user函数,将用户空间传入的算式字符串(如"10+20")安全地复制到内核空间的全局数组ycg中,并手动添加字符串结束符\0,为后续读取时的解析做准备 - drv_read: 通过
while循环遍历ycg缓冲区中的字符串,把两个数字分别赋值个num1和num2,使用sprintf将结果格式化为字符串(如"10 + 20 = 30\n"),最后通过copy_to_user将计算结果返回给用户空间 - changganyinDrv_init: 动态分配设备号
- task_2.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define CHAR_DEV_NAME "/dev/ycgDrive"
char calc[32];
char res[32];
int main()
{
int fd;
// 打开设备文件
fd = open(CHAR_DEV_NAME, O_RDWR);
if(fd < 0)
{
printf("open failed! path: %s\n", CHAR_DEV_NAME);
perror("reason");
return -1;
}
printf("Please input calculation (e.g. 1+1 or 10-5): ");
scanf("%s", calc);
// 1. 写数据给驱动
write(fd, calc, strlen(calc));
// 2. 从驱动读结果
read(fd, res, 32);
printf("Result from kernel: %s\n", res);
close(fd);
return 0;
}
- Makefile
# 动态获取当前内核版本
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
PWD := $(shell pwd)
obj-m := ycgDrive.o
all:
# 编译内核模块
make -C $(KDIR) M=$(PWD) modules
# 编译测试应用程序
gcc -o task_2 task_2.c
clean:
make -C $(KDIR) M=$(PWD) clean
rm -rf task_2
安装和卸载驱动程序
make # 编译
sudo insmod ycgDrive.ko # 安装驱动程序
sudo dmesg | tail -5
sudo mknod /dev/ycgDrive c 240 0 # 根据上一步的结果
sudo rm -rf /dev/ycgDrive
sudo rmmod ycgDrive
sudo dmesg | tail -7
taask_2 截图
![[Pasted image 20251230145821.png]]
![[Pasted image 20251230150249.png]]
![[Pasted image 20251230150451.png]]
task_3 实现 proc 接口,将应用程序和驱动程序的部分交互信息写入proc文件系统,并在应用程序快结束时读出相关文件信息并显示出来
- ycgDrive2.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
char src[256];
char dest[256];
int main() {
int length, count;
// 打开设备,注意名字改成了 ycgDrive2
int fp = open("/dev/ycgDrive2", O_RDWR);
if(fp < 0) {
printf("Open ycgDrive2 fail. Did you run mknod?\n");
return 0;
}
printf("Please write strings (Press Ctrl+D to stop writing):\n");
// 循环写入
while(scanf("%s", src) != EOF) {
length = strlen(src);
count = write(fp, src, length);
printf("Write to ycgDrive2 %dB\n", count);
}
// 清除 EOF 状态,为了后面的 scanf 能继续工作 (这是一个小技巧,否则后面的 scanf 会直接跳过)
clearerr(stdin);
// 重置文件指针到开头
printf("\nReset file pointer to beginning...\n");
printf("position: %ld\n", lseek(fp, 0, SEEK_SET));
printf("Please input the bytes to read (Press Ctrl+D to exit):\n");
// 循环读取
while(scanf("%d", &length) != EOF) {
// 读取指定长度
count = read(fp, dest, length);
if (count > 0) {
dest[count] = 0; // 添加字符串结束符
printf("Read from ycgDrive2 %dB: %s\n", count, dest);
} else {
printf("Read 0 bytes (End of Buffer)\n");
}
}
close(fp);
return 0;
}
代码逻辑:
- ycgDrive2_llseek: 根据
whence参数(SEEK_SET头、SEEK_CUR当前、SEEK_END尾),计算新的文件指针位置newpos; 修改文件结构体中的fp->f_pos,从而改变下一次读写操作的位置 - read/write: 将数据从用户空间复制到内核缓冲区。关键在于它会根据当前的文件指针偏移量 (
*pos) 进行写入,并更新文件大小 (ycgDrive2_size);根据当前的文件指针偏移量,从内核缓冲区的对应位置读取数据返回给用户 - task_3.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
char src[256];
char dest[256];
int main() {
int length, count;
int fp = open("/dev/ycgDrive2", O_RDWR);
if(fp < 0) {
printf("Open ycgDrive2 fail. Did you run mknod?\n");
return 0;
}
printf("Please write strings (Press Ctrl+D to stop writing):\n");
// 循环写入
while(scanf("%s", src) != EOF) {
length = strlen(src);
count = write(fp, src, length);
printf("Write to ycgDrive2 %dB\n", count);
}
// 清除 EOF 状态,为了后面的 scanf 能继续工作 (这是一个小技巧,否则后面的 scanf 会直接跳过)
clearerr(stdin);
// 重置文件指针到开头
printf("\nReset file pointer to beginning...\n");
printf("position: %ld\n", lseek(fp, 0, SEEK_SET));
printf("Please input the bytes to read (Press Ctrl+D to exit):\n");
// 循环读取
while(scanf("%d", &length) != EOF) {
// 读取指定长度
count = read(fp, dest, length);
if (count > 0) {
dest[count] = 0; // 添加字符串结束符
printf("Read from ycgDrive2 %dB: %s\n", count, dest);
} else {
printf("Read 0 bytes (End of Buffer)\n");
}
}
close(fp);
return 0;
}
安装和卸载
make # 编译
sudo insmod ycgDrive2.ko
sudo mknod /dev/ycgDrive2 c 222 0
sudo ./task_3
sudo rm /dev/ycgDrive2
sudo rmmod ycgDrive2
task_3 截图
![[Pasted image 20251230151345.png]]