Files
Operating-System/Experiment/OS_exp/os_exp_conclude.md
2026-06-25 00:09:09 +08:00

1368 lines
38 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 操作系统实验指导Linux 进程/线程/调度/存储/文件/设备/同步/通信)
## 实验一 进程与线程——Linux 进程与线程通信
### 1.1 实验目的
深刻理解线程和进程的概念,掌握线程与进程在组成成分上的差别,以及与其相适应的通信方式和应用目标。
Linux 系统的 fork() 保持了 UNIX 的经典语义,被创建进程具有独立于父进程的地址空间,二者之间的通信通常可以采用 pipe 机制。clone() 是 Linux 系统特有的系统调用,可以通过参数确定父子之间是否共享存储空间等资源,在地址空间等资源共享的情况下 clone() 实质上相当于创建了一个轻进程或线程,这是 clone() 的通常用法。实际在 Linux 系统中fork() 以及用户级线程 pthread 都是基于 clone() 实现的。
### 1.2 实验内容
以 Linux 系统进程和线程机制为背景,掌握 fork() 和 clone() 系统调用的形式和功能,以及与其相适应的高级通信方式。由 fork() 派生的子进程之间通过 pipe 通信,由 clone() 创建的线程之间通过共享内存通信,对于后者需要考虑互斥问题。
以生产者-消费者问题为例,通过实验理解 fork() 和 clone() 两个系统调用的区别。程序要求能够创建 4 个进程或线程,其中包括两个生产者和两个消费者,生产者和消费者之间能够传递数据。
### 1.3 实验准备
1. **fork() 系统调用**
```cpp
pid = fork();
```
创建一个子进程,子进程是父进程的完整复制,正常返回值为非负整数。对于父进程返回值大于 0子进程 pid对于子进程返回 0利用返回值差别执行不同逻辑。
2. **clone() 系统调用**
```cpp
int clone(int (*fn)(void *arg), void *stack, int flags, void *arg);
```
- fn轻进程执行函数
- stack轻进程使用栈
- flagsCLONE_VM/CLONE_FS/CLONE_FILES/CLONE_SIGHAND/CLONE_PID 组合
- arg传递参数
3. **pipe() 系统调用**
```cpp
int pipe(int fd[2]);
```
创建管道,返回 fd[0]、fd[1](写),可被 fork() 子进程继承共享。
4. **信号量与互斥锁**
- sem_wait(&s)P 操作sem_post(&s)V 操作
- pthread_mutex_lock(&mutex)加锁pthread_mutex_unlock(&mutex):解锁
### 1.4 实验设计
1. 用 pipe() 创建管道fork() 创建 2 生产者 + 2 消费者进程,管道通信。
2. 用 clone() 创建 4 个轻进程(线程),共享内存,互斥锁保护共享区。
### 1.5 参考代码
#### 基于 fork()
```cpp
#include "sys/types.h"
#include "sys/file.h"
#include "unistd.h"
char r_buf[4];
char w_buf[4];
int pipe_fd[2];
pid_t pid1, pid2, pid3, pid4;
int producer(int id);
int consumer(int id);
int main(int argc, char **argv) {
if (pipe(pipe_fd) < 0) {
printf("pipe create error \n");
exit(-1);
} else {
printf("pipe is created successfully!\n");
if ((pid1 = fork()) == 0) producer(1);
if ((pid2 = fork()) == 0) producer(2);
if ((pid3 = fork()) == 0) consumer(1);
if ((pid4 = fork()) == 0) consumer(2);
}
close(pipe_fd[0]);
close(pipe_fd[1]);
int i, pid, status;
for (i = 0; i < 4; i++)
pid = wait(&status);
exit(0);
}
int producer(int id) {
printf("producer %d is running!\n", id);
close(pipe_fd[0]);
int i = 0;
for (i = 1; i < 10; i++) {
sleep(3);
if (id == 1)
strcpy(w_buf, "aaa\0");
else
strcpy(w_buf, "bbb\0");
if (write(pipe_fd[1], w_buf, 4) == -1)
printf("write to pipe error\n");
}
close(pipe_fd[1]);
printf("producer %d is over!\n", id);
exit(id);
}
int consumer(int id) {
close(pipe_fd[1]);
printf("consumer %d is running!\n", id);
if (id == 1)
strcpy(w_buf, "ccc\0");
else
strcpy(w_buf, "ddd\0");
while (1) {
sleep(1);
strcpy(r_buf, "eee\0");
if (read(pipe_fd[0], r_buf, 4) == 0)
break;
printf("consumer %d get %s, while the w_buf is %s\n", id, r_buf, w_buf);
}
close(pipe_fd[0]);
printf("consumer %d is over!\n", id);
exit(id);
}
```
#### 基于 clone()
```cpp
#include "sched.h"
#include "pthread.h"
#include "stdio.h"
#include "stdlib.h"
#include "semaphore.h"
int producer(void *args);
int consumer(void *args);
pthread_mutex_t mutex;
sem_t product;
sem_t warehouse;
char buffer[8][4];
int bp = 0;
int main(int argc, char ** argv) {
pthread_mutex_init(&mutex, NULL);
sem_init(&product, 0, 0);
sem_init(&warehouse, 0, 8);
int clone_flag, arg, retval;
char *stack;
clone_flag = CLONE_VM | CLONE_SIGHAND | CLONE_FS | CLONE_FILES;
int i;
for (i = 0; i < 2; i++) {
arg = i;
stack = (char *)malloc(4096);
retval = clone((void *)producer, &(stack[4095]), clone_flag, (void *)&arg);
stack = (char *)malloc(4096);
sleep(1);
retval = clone((void *)consumer, &(stack[4095]), clone_flag, (void *)&arg);
}
exit(1);
}
int producer(void *args) {
int id = *((int *)args);
int i;
for (i = 0; i < 10; i++) {
sleep(i + 1);
sem_wait(&warehouse);
pthread_mutex_lock(&mutex);
if (id == 0)
strcpy(buffer[bp], "aaa\0");
else
strcpy(buffer[bp], "bbb\0");
bp++;
printf("producer%d produce %s in %d\n", id, buffer[bp - 1], bp - 1);
pthread_mutex_unlock(&mutex);
sem_post(&product);
}
printf("producer%d is over!\n", id);
return 0;
}
int consumer(void *args) {
int id = *((int *)args);
int i;
for (i = 0; i < 10; i++) {
sleep(10 - i);
sem_wait(&product);
pthread_mutex_lock(&mutex);
bp--;
printf("consumer%d get %s in %d\n", id, buffer[bp], bp);
strcpy(buffer[bp], "zzz\0");
pthread_mutex_unlock(&mutex);
sem_post(&warehouse);
}
printf("consumer%d is over!\n", id);
return 0;
}
```
### 1.6 实验结果
- fork() 进程独立地址空间,通过管道通信,数据互不影响。
- clone() 线程共享内存,需互斥与同步,数据一致。
### 1.7 思考问题
1. 用 shm/msg 实现生产者-消费者。
2. 对比 pipe、clone、shm、msg 优缺点与适用场景。
---
## 实验二 处理器调度——实时调度算法 EDF 和 RMS
### 2.1 实验目的
深入理解处理器调度算法,了解硬实时概念,掌握周期性任务调度算法 EDFEarliest Deadline First和 RMSRate Monotonic Scheduling的可调度条件并能在可调度情况下给出具体调度结果。
### 2.2 实验内容
在 Linux 环境采用用户级线程模拟 EDF 和 RMS。给定实时任务判断可调度性创建线程按算法调度用字符绘制 Gantt 图。
### 2.3 实验准备
- EDF 可调度条件:∑(Ci/Ti) ≤ 1可抢占
- RMS 可调度条件:∑(Ci/Ti) ≤ n·(exp(ln2/n) 1)(不可抢占)
- pthread_create 创建用户级线程
### 2.4 实验设计
用 task 结构体描述实时任务,实现 select_proc() 调度算法,主线程按算法唤醒线程,线程执行一个时间单位后交还控制权。
### 2.5 参考代码
```cpp
#include "math.h"
#include "sched.h"
#include "pthread.h"
#include "stdlib.h"
#include "semaphore.h"
#include "stdio.h"
typedef struct {
char task_id;
int call_num;
int ci;
int ti;
int ci_left;
int ti_left;
int flag;
int arg;
pthread_t th;
} task;
void proc(int *args);
void *idle();
int select_proc();
int task_num = 0;
int idle_num = 0;
int alg;
int curr_proc = -1;
int demo_time = 100;
task *tasks;
pthread_mutex_t proc_wait[100];
pthread_mutex_t main_wait, idle_wait;
float sum = 0;
pthread_t idle_proc;
int main(int argc, char ** argv) {
pthread_mutex_init(&main_wait, NULL);
pthread_mutex_lock(&main_wait);
pthread_mutex_init(&idle_wait, NULL);
pthread_mutex_lock(&idle_wait);
printf("Please input number of real time tasks:\n");
scanf("%d", &task_num);
tasks = (task *)malloc(task_num * sizeof(task));
int i;
for (i = 0; i < task_num; i++) {
pthread_mutex_init(&proc_wait[i], NULL);
pthread_mutex_lock(&proc_wait[i]);
}
for (i = 0; i < task_num; i++) {
printf("Please input task id, followed by Ci and Ti:\n");
getchar();
scanf("%c,%d,%d,", &tasks[i].task_id, &tasks[i].ci, &tasks[i].ti);
tasks[i].ci_left = tasks[i].ci;
tasks[i].ti_left = tasks[i].ti;
tasks[i].flag = 2;
tasks[i].arg = i;
tasks[i].call_num = 1;
sum += (float)tasks[i].ci / (float)tasks[i].ti;
}
printf("Please input algorithm, 1 for EDF, 2 for RMS:");
getchar();
scanf("%d", &alg);
printf("Please input demo time:");
scanf("%d", &demo_time);
double r = 1;
if (alg == 2) {
r = (double)task_num * (exp(log(2) / (double)task_num) - 1);
printf("r is %lf\n", r);
}
if (sum > r) {
printf("(sum = %lf > r = %lf), not schedulable!\n", sum, r);
exit(2);
}
pthread_create(&idle_proc, NULL, (void *)idle, NULL);
for (i = 0; i < task_num; i++)
pthread_create(&tasks[i].th, NULL, (void *)proc, &tasks[i].arg);
for (i = 0; i < demo_time; i++) {
int j;
if ((curr_proc = select_proc(alg)) != -1) {
pthread_mutex_unlock(&proc_wait[curr_proc]);
pthread_mutex_lock(&main_wait);
} else {
pthread_mutex_unlock(&idle_wait);
pthread_mutex_lock(&main_wait);
}
for (j = 0; j < task_num; j++) {
if (--tasks[j].ti_left == 0) {
tasks[j].ti_left = tasks[j].ti;
tasks[j].ci_left = tasks[j].ci;
pthread_create(&tasks[j].th, NULL, (void *)proc, &tasks[j].arg);
tasks[j].flag = 2;
}
}
printf("\n");
sleep(1);
}
}
void proc(int * args) {
while (tasks[*args].ci_left > 0) {
pthread_mutex_lock(&proc_wait[*args]);
if (idle_num != 0) {
printf("idle(%d)", idle_num);
idle_num = 0;
}
printf("%c%d", tasks[*args].task_id, tasks[*args].call_num);
tasks[*args].ci_left--;
if (tasks[*args].ci_left == 0) {
printf("(%d)", tasks[*args].ci);
tasks[*args].flag = 0;
tasks[*args].call_num++;
}
pthread_mutex_unlock(&main_wait);
}
}
void *idle() {
while (1) {
pthread_mutex_lock(&idle_wait);
printf("->");
idle_num++;
pthread_mutex_unlock(&main_wait);
}
}
int select_proc(int alg) {
int j;
int templ, temp2;
templ = 10000;
temp2 = -1;
if ((alg == 2) && (curr_proc != -1) && (tasks[curr_proc].flag != 0))
return curr_proc;
for (j = 0; j < task_num; j++) {
if (tasks[j].flag == 2) {
switch (alg) {
case 1:
if (templ > tasks[j].ti_left) {
templ = tasks[j].ti_left;
temp2 = j;
}
break;
case 2:
if (templ > tasks[j].ti) {
templ = tasks[j].ti;
temp2 = j;
}
break;
}
}
}
return temp2;
}
```
### 2.6 实验结果
编译:`gcc -lpthread -lm test_scheduler.c -o scheduler.out`
- EDF按截止时间优先调度
- RMS按周期短优先不可抢占
### 2.7 思考问题
1. 改进调度:仅需重调度时返回主线程,减少开销。
2. 统计线程切换次数。
---
## 实验三 存储管理——动态不等长存储资源分配算法
### 3.1 实验目的
理解动态异长存储分区资源管理,掌握所需数据结构和管理程序,了解各种存储分配算法的优点和缺点。
### 3.2 实验内容
1. 分析 UNIX 最先适应FFmap 结构、malloc、mfree。
2. 实现最佳适应BF、最坏适应WF
### 3.3 实验准备
掌握动态不等长存储管理、UNIX 存储资源管理。
### 3.4 实验设计
编写 BF、WF 分配与释放函数,初始化存储表,处理请求/释放并显示。
### 3.5 参考代码
```cpp
#include <stdio.h>
#include <stdlib.h>
#define MAPSIZE 100
struct map {
int m_addr;
int m_size;
};
struct map map[MAPSIZE];
int BF_malloc(struct map *mp, int size) {
register int a, s;
register struct map *bp, *bpp;
for (bp = mp; bp->m_size; bp++) {
if (bp->m_size >= size) {
a = bp->m_addr;
s = bp->m_size;
for (bpp = bp; bpp->m_size; bpp++) {
if (bpp->m_size >= size && bpp->m_size < s) {
a = bpp->m_addr;
s = bpp->m_size;
bp = bpp;
}
}
bp->m_addr += size;
if ((bp->m_size -= size) == 0) {
do {
bp++;
(bp - 1)->m_addr = bp->m_addr;
} while (((bp - 1)->m_size = bp->m_size));
}
return a;
}
}
return -1;
}
int WF_malloc(struct map *mp, int size) {
register int a, s;
register struct map *bp, *bpp;
for (bp = mp; bp->m_size; bp++) {
if (bp->m_size >= size) {
a = bp->m_addr;
s = bp->m_size;
for (bpp = bp; bpp->m_size; bpp++) {
if (bpp->m_size > s) {
a = bpp->m_addr;
s = bpp->m_size;
bp = bpp;
}
}
bp->m_addr += size;
if ((bp->m_size -= size) == 0) {
do {
bp++;
(bp - 1)->m_addr = bp->m_addr;
} while (((bp - 1)->m_size = bp->m_size));
}
return a;
}
}
return -1;
}
void mfree(struct map *mp, int aa, int size) {
register struct map *bp;
register int t, a;
a = aa;
for (bp = mp; bp->m_addr <= a && bp->m_size != 0; bp++);
if (bp > mp && (bp - 1)->m_addr + (bp - 1)->m_size == a) {
(bp - 1)->m_size += size;
if (a + size == bp->m_addr) {
(bp - 1)->m_size += bp->m_size;
while (bp->m_size) {
bp++;
(bp - 1)->m_addr = bp->m_addr;
(bp - 1)->m_size = bp->m_size;
}
}
} else {
if (a + size == bp->m_addr && bp->m_size) {
bp->m_addr -= size;
bp->m_size += size;
} else if (size) {
do {
t = bp->m_addr;
bp->m_addr = a;
a = t;
t = bp->m_size;
bp->m_size = size;
bp++;
} while ((size = t));
}
}
}
void init() {
struct map *bp;
int addr, size;
bp = map;
printf("Please input starting addr and total size:");
scanf("%d,%d", &addr, &size);
bp->m_addr = addr;
bp->m_size = size;
(++bp)->m_size = 0;
}
void show_map() {
system("clear");
printf("\nCurrent memory map...\n");
printf("Address\t\tSize\n");
struct map *bp = map;
while (bp->m_size != 0) {
printf("<%d\t\t%d>\n", bp->m_addr, bp->m_size);
bp++;
}
printf("\n");
}
int main() {
int a, s, i;
char c;
init();
printf("Please input, b for BF, w for WF:");
scanf("%c", &c);
do {
show_map();
printf("Please input, 1 for request, 2 for release, 0 for exit:");
scanf("%d", &i);
switch (i) {
case 1:
printf("Please input size:");
scanf("%d", &s);
if (c == 'b')
a = BF_malloc(map, s);
else
a = WF_malloc(map, s);
if (a == -1)
printf("request can not be satisfied\n");
else
printf("alloc memory at address:%d,size:%d\n", a, s);
break;
case 2:
printf("Please input addr and size:");
scanf("%d,%d", &a, &s);
mfree(map, a, s);
break;
case 0:
exit(0);
}
} while (1);
}
```
### 3.6 实验结果
按输入分配/释放,实时显示空闲块。
### 3.7 思考问题
1. 按大小排序空闲块,优化分配速度,重写释放。
2. 设计伙伴堆buddy heap数据结构与算法。
---
## 实验四 文件系统——散列结构文件
### 4.1 实验目的
理解 Linux 文件系统内部技术,掌握文件系统调用,建立面向随机检索的散列结构文件。
### 4.2 实验内容
设计散列文件创建、打开、关闭、读、写、删除等函数,编写测试程序验证。
### 4.3 实验准备
散列算法、Linux 文件系统调用 creat/open/close/read/write/lseek。
### 4.4 实验设计
文件头保存散列信息,实现冲突处理,提供随机检索接口。
### 4.5 参考代码
#### HashFile.h
```cpp
#include <unistd.h>
#define COLLISIONFACTOR 0.5
struct HashFileHeader {
int sig;
int reclen;
int total_rec_num;
int current_rec_num;
};
struct CFTag {
char collision;
char free;
};
int hashfile_creat(const char *filename, mode_t mode, int reclen, int recnum);
int hashfile_open(const char *filename, int flags, mode_t mode);
int hashfile_close(int fd);
int hashfile_read(int fd, int keyoffset, int keylen, void *buf);
int hashfile_write(int fd, int keyoffset, int keylen, void *buf);
int hashfile_delrec(int fd, int keyoffset, int keylen, void *buf);
int hashfile_findrec(int fd, int keyoffset, int keylen, void *buf);
int hashfile_saverec(int fd, int keyoffset, int keylen, void *buf);
int hash(int keyoffset, int keylen, void *buf, int recnum);
int checkHashFileFull(int fd);
int readHashFileHeader(int fd, struct HashFileHeader *hfh);
```
#### HashFile.c
```cpp
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include "HashFile.h"
int hashfile_creat(const char *filename, mode_t mode, int reclen, int total_rec_num) {
struct HashFileHeader hfh;
int fd, rtn;
char *buf;
hfh.sig = 31415926;
hfh.reclen = reclen;
hfh.total_rec_num = total_rec_num;
hfh.current_rec_num = 0;
fd = creat(filename, mode);
if (fd != -1) {
rtn = write(fd, &hfh, sizeof(struct HashFileHeader));
if (rtn != -1) {
buf = (char *)malloc((reclen + sizeof(struct CFTag)) * total_rec_num);
memset(buf, 0, (reclen + sizeof(struct CFTag)) * total_rec_num);
rtn = write(fd, buf, (reclen + sizeof(struct CFTag)) * total_rec_num);
free(buf);
}
close(fd);
return rtn;
}
close(fd);
return -1;
}
int hashfile_open(const char *filename, int flags, mode_t mode) {
int fd = open(filename, flags, mode);
struct HashFileHeader hfh;
if (read(fd, &hfh, sizeof(struct HashFileHeader)) != -1) {
lseek(fd, 0, SEEK_SET);
if (hfh.sig == 31415926)
return fd;
}
return -1;
}
int hashfile_close(int fd) {
return close(fd);
}
int hashfile_read(int fd, int keyoffset, int keylen, void *buf) {
struct HashFileHeader hfh;
readHashFileHeader(fd, &hfh);
int offset = hashfile_findrec(fd, keyoffset, keylen, buf);
if (offset != -1) {
lseek(fd, offset + sizeof(struct CFTag), SEEK_SET);
return read(fd, buf, hfh.reclen);
}
return -1;
}
int hashfile_write(int fd, int keyoffset, int keylen, void *buf) {
return hashfile_saverec(fd, keyoffset, keylen, buf);
}
int hashfile_delrec(int fd, int keyoffset, int keylen, void *buf) {
int offset = hashfile_findrec(fd, keyoffset, keylen, buf);
if (offset != -1) {
struct CFTag tag;
read(fd, &tag, sizeof(struct CFTag));
tag.free = 0;
lseek(fd, offset, SEEK_SET);
write(fd, &tag, sizeof(struct CFTag));
struct HashFileHeader hfh;
readHashFileHeader(fd, &hfh);
int addr = hash(keyoffset, keylen, buf, hfh.total_rec_num);
offset = sizeof(struct HashFileHeader) + addr * (hfh.reclen + sizeof(struct CFTag));
lseek(fd, offset, SEEK_SET);
read(fd, &tag, sizeof(struct CFTag));
tag.collision--;
lseek(fd, offset, SEEK_SET);
write(fd, &tag, sizeof(struct CFTag));
hfh.current_rec_num--;
lseek(fd, 0, SEEK_SET);
write(fd, &hfh, sizeof(struct HashFileHeader));
return 0;
}
return -1;
}
int hashfile_findrec(int fd, int keyoffset, int keylen, void *buf) {
struct HashFileHeader hfh;
readHashFileHeader(fd, &hfh);
int addr = hash(keyoffset, keylen, buf, hfh.total_rec_num);
int offset = sizeof(struct HashFileHeader) + addr * (hfh.reclen + sizeof(struct CFTag));
lseek(fd, offset, SEEK_SET);
struct CFTag tag;
read(fd, &tag, sizeof(struct CFTag));
char count = tag.collision;
if (count == 0) return -1;
recfree:
if (tag.free == 0) {
offset += hfh.reclen + sizeof(struct CFTag);
lseek(fd, offset, SEEK_SET);
read(fd, &tag, sizeof(struct CFTag));
goto recfree;
}
char *p = (char *)malloc(hfh.reclen);
read(fd, p, hfh.reclen);
char *p1 = (char *)buf + keyoffset;
char *p2 = p + keyoffset;
int j = 0;
while (*p1 == *p2 && j < keylen) {
p1++; p2++; j++;
}
if (j == keylen) {
free(p);
return offset;
}
free(p);
offset += hfh.reclen + sizeof(struct CFTag);
lseek(fd, offset, SEEK_SET);
read(fd, &tag, sizeof(struct CFTag));
goto recfree;
}
int hashfile_saverec(int fd, int keyoffset, int keylen, void *buf) {
if (checkHashFileFull(fd)) return -1;
struct HashFileHeader hfh;
readHashFileHeader(fd, &hfh);
int addr = hash(keyoffset, keylen, buf, hfh.total_rec_num);
int offset = sizeof(struct HashFileHeader) + addr * (hfh.reclen + sizeof(struct CFTag));
lseek(fd, offset, SEEK_SET);
struct CFTag tag;
read(fd, &tag, sizeof(struct CFTag));
tag.collision++;
lseek(fd, sizeof(struct CFTag) * (-1), SEEK_CUR);
write(fd, &tag, sizeof(struct CFTag));
while (tag.free != 0) {
offset += hfh.reclen + sizeof(struct CFTag);
if (offset >= lseek(fd, 0, SEEK_END))
offset = sizeof(struct HashFileHeader);
lseek(fd, offset, SEEK_SET);
read(fd, &tag, sizeof(struct CFTag));
}
tag.free = 1;
lseek(fd, sizeof(struct CFTag) * (-1), SEEK_CUR);
write(fd, &tag, sizeof(struct CFTag));
write(fd, buf, hfh.reclen);
hfh.current_rec_num++;
lseek(fd, 0, SEEK_SET);
return write(fd, &hfh, sizeof(struct HashFileHeader));
}
int hash(int keyoffset, int keylen, void *buf, int total_rec_num) {
int i = 0, addr = 0;
char *p = (char *)buf + keyoffset;
for (i = 0; i < keylen; i++) {
addr += (int)(*p);
p++;
}
return addr % (int)(total_rec_num * COLLISIONFACTOR);
}
int readHashFileHeader(int fd, struct HashFileHeader *hfh) {
lseek(fd, 0, SEEK_SET);
return read(fd, hfh, sizeof(struct HashFileHeader));
}
int checkHashFileFull(int fd) {
struct HashFileHeader hfh;
readHashFileHeader(fd, &hfh);
return hfh.current_rec_num >= hfh.total_rec_num;
}
```
#### 测试程序
```cpp
#include "HashFile.h"
#define RECORDLEN 32
struct jtRecord {
int key;
char other[RECORDLEN - sizeof(int)];
};
#define KEYOFFSET 0
#define KEYLEN sizeof(int)
#define FILENAME "jing.hash"
void showHashFile() {
int fd = open(FILENAME, O_RDWR);
lseek(fd, sizeof(struct HashFileHeader), SEEK_SET);
struct jtRecord jt;
struct CFTag tag;
while (1) {
if (read(fd, &tag, sizeof(struct CFTag)) <= 0) break;
printf("Tag is <%d,%d>\t", tag.collision, tag.free);
if (read(fd, &jt, sizeof(struct jtRecord)) <= 0) break;
printf("Record is {%d,%s}\n", jt.key, jt.other);
}
close(fd);
}
int main() {
struct jtRecord rec[6] = {
{1,"jing"},{2,"wang"},{3,"li"},{4,"zhang"},{5,"qing"},{6,"yuan"}
};
int fd = hashfile_creat(FILENAME, O_RDWR | O_CREAT, RECORDLEN, 6);
fd = hashfile_open(FILENAME, O_RDWR, 0);
for (int i = 0; i < 6; i++)
hashfile_saverec(fd, KEYOFFSET, KEYLEN, &rec[i]);
hashfile_close(fd);
showHashFile();
return 0;
}
```
### 4.6 实验结果
成功创建散列文件,支持增删查改。
### 4.7 思考问题
1. 实现 hashfile_update() 记录更新函数。
2. 内核级实现散列文件的方案与困难。
---
## 实验五 设备管理——Linux 设备驱动程序安装
### 5.1 实验目的
认识 Linux 设备种类与工作方式,理解设备驱动原理,掌握驱动编写规范,安装简单字符设备驱动。
### 5.2 实验内容
编写字符设备驱动模块,实现 open/close/read/write动态注册/卸载,创建设备文件 /dev/mydev。
### 5.3 实验准备
- device_struct、file_operations 结构体
- register_chrdev/unregister_chrdev
- init_module/cleanup_module
- copy_to_user/copy_from_user
### 5.4 实验设计
编写驱动模块与测试程序,编译→加载→创建设备→测试→卸载。
### 5.5 参考代码
#### 设备驱动 mydev.c
```cpp
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define SUCCESS 0
#define DEVICE_NAME "kueng_char_dev"
#define BUF_LEN 50
static int Device_Open = 0;
static char Message[BUF_LEN];
static int Major;
static int mydev_open(struct inode *inode, struct file *file) {
if (Device_Open) return -EBUSY;
Device_Open = 1;
return 0;
}
static int mydev_release(struct inode *inode, struct file *file) {
Device_Open = 0;
return 0;
}
static ssize_t mydev_read(struct file *file, char *buffer, size_t length, loff_t *f_pos) {
return copy_to_user(buffer, Message, length);
}
static ssize_t mydev_write(struct file *file, const char *buffer, size_t length, loff_t *f_pos) {
int len = BUF_LEN < length ? BUF_LEN : length;
return copy_from_user(Message, buffer, len);
}
static struct file_operations fops = {
.open = mydev_open,
.release = mydev_release,
.read = mydev_read,
.write = mydev_write,
};
int init_module(void) {
Major = register_chrdev(0, DEVICE_NAME, &fops);
if (Major < 0) {
printk("Register char device failed\n");
return Major;
}
printk("Registered with major %d\n", Major);
return 0;
}
void cleanup_module(void) {
unregister_chrdev(Major, DEVICE_NAME);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("KUENG");
```
#### 测试程序 test.c
```cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main() {
int testdev = open("/dev/mydev", O_RDWR);
if (testdev == -1) {
printf("can not open file\n");
exit(0);
}
char buf[50] = "pear to dev!";
write(testdev, buf, 50);
printf("write: %s\n", buf);
strcpy(buf, "apple to dev!");
read(testdev, buf, 50);
printf("read: %s\n", buf);
close(testdev);
return 0;
}
```
### 5.6 实验结果
加载模块→查看主设备号→mknod→运行测试→读写正常→卸载。
### 5.7 思考问题
1. 仿照 /dev/null 编写 sink 设备驱动。
2. 源码级编译驱动与模块方式对比。
---
## 实验六 同步机制——信号量集与哲学家就餐问题
### 6.1 实验目的
理解 Linux SystemV 信号量集同步机制,掌握 semget/semctl/semop解决哲学家就餐死锁问题。
### 6.2 实验内容
创建 5 个信号量(叉子),哲学家同时申请左右叉子,避免死锁。
### 6.3 实验准备
- semget创建信号量集
- semctl设置初值
- semop原子操作多个信号量
### 6.4 实验设计
semget 创建 5 信号量semctl 置 1fork 5 进程semop 同时申请/释放两把叉子。
### 6.5 参考代码
```cpp
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
} arg;
int semid;
void philosopher(int i) {
struct sembuf sbuf[2];
sbuf[0].sem_num = i;
sbuf[0].sem_flg = SEM_UNDO;
sbuf[1].sem_num = (i + 1) % 5;
sbuf[1].sem_flg = SEM_UNDO;
while (1) {
printf("philosopher %d is thinking\n", i);
sleep(2);
printf("philosopher %d is hungry\n", i);
sbuf[0].sem_op = -1;
sbuf[1].sem_op = -1;
semop(semid, sbuf, 2);
printf("philosopher %d is eating\n", i);
sleep(2);
sbuf[0].sem_op = 1;
sbuf[1].sem_op = 1;
semop(semid, sbuf, 2);
}
}
int main() {
key_t key = ftok("fname", 1);
semid = semget(key, 5, IPC_CREAT | 0666);
arg.val = 1;
for (int i = 0; i < 5; i++)
semctl(semid, i, SETVAL, arg);
for (int i = 0; i < 5; i++) {
if (fork() == 0) {
philosopher(i);
}
}
while (wait(NULL) > 0);
return 0;
}
```
### 6.6 实验结果
无相邻哲学家同时进食,无死锁。
### 6.7 思考问题
1. 用 clone/vfork 替代 fork。
2. 用信号量集解决吸烟者问题。
---
## 实验七 进程通信——消息方式
### 7.1 实验目的
理解 Linux 消息队列通信机制,掌握 msgget/msgsnd/msgrcv/msgctl。
### 7.2 实验内容
创建消息队列P1/P2 发消息P3 收 P1、P4 收 P2大消息拆分传输。
### 7.3 实验准备
消息队列系统调用,按类型收发。
### 7.4 实验设计
msgget 创建队列fork 4 进程P1 拆分矩阵发送P3 合并P2 发短消息P4 接收。
### 7.5 参考代码
```cpp
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msgbuf {
long mtype;
char mtext[1024];
};
int main() {
key_t key = ftok(IPC_PRIVATE, 1);
int msgid = msgget(key, IPC_CREAT | 0666);
if (fork() == 0) {
char a[64][64];
for (int i = 0; i < 64; i++)
for (int j = 0; j < 64; j++)
a[i][j] = i < 32 ? (j < 32 ? 'a' : 'b') : (j < 32 ? 'c' : 'd');
struct msgbuf mgb;
mgb.mtype = 1;
int k = 0;
for (int i = 0; i < 32; i++) for (int j = 0; j < 32; j++) mgb.mtext[k++] = a[i][j];
msgsnd(msgid, &mgb, sizeof(mgb), 0);
k = 0;
for (int i = 0; i < 32; i++) for (int j = 32; j < 64; j++) mgb.mtext[k++] = a[i][j];
msgsnd(msgid, &mgb, sizeof(mgb), 0);
k = 0;
for (int i = 32; i < 64; i++) for (int j = 0; j < 32; j++) mgb.mtext[k++] = a[i][j];
msgsnd(msgid, &mgb, sizeof(mgb), 0);
k = 0;
for (int i = 32; i < 64; i++) for (int j = 32; j < 64; j++) mgb.mtext[k++] = a[i][j];
msgsnd(msgid, &mgb, sizeof(mgb), 0);
exit(1);
}
if (fork() == 0) {
struct msgbuf mgb;
mgb.mtype = 2;
strcpy(mgb.mtext, "ijkl");
msgsnd(msgid, &mgb, sizeof(mgb), 0);
strcpy(mgb.mtext, "mnop");
msgsnd(msgid, &mgb, sizeof(mgb), 0);
exit(2);
}
if (fork() == 0) {
char a[64][64];
struct msgbuf mgb;
msgrcv(msgid, &mgb, sizeof(mgb), 1, 0);
int k = 0;
for (int i = 0; i < 32; i++) for (int j = 0; j < 32; j++) a[i][j] = mgb.mtext[k++];
msgrcv(msgid, &mgb, sizeof(mgb), 1, 0);
k = 0;
for (int i = 0; i < 32; i++) for (int j = 32; j < 64; j++) a[i][j] = mgb.mtext[k++];
msgrcv(msgid, &mgb, sizeof(mgb), 1, 0);
k = 0;
for (int i = 32; i < 64; i++) for (int j = 0; j < 32; j++) a[i][j] = mgb.mtext[k++];
msgrcv(msgid, &mgb, sizeof(mgb), 1, 0);
k = 0;
for (int i = 32; i < 64; i++) for (int j = 32; j < 64; j++) a[i][j] = mgb.mtext[k++];
for (int i = 0; i < 64; i++) {
for (int j = 0; j < 64; j++) printf("%c", a[i][j]);
printf("\n");
}
exit(3);
}
if (fork() == 0) {
struct msgbuf mgb;
msgrcv(msgid, &mgb, sizeof(mgb), 2, 0);
printf("p4 received %s\n", mgb.mtext);
msgrcv(msgid, &mgb, sizeof(mgb), 2, 0);
printf("p4 received %s\n", mgb.mtext);
exit(4);
}
wait(NULL);
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
```
### 7.6 实验结果
矩阵拆分传输成功合并,短消息按类型收发。
### 7.7 思考问题
1. 传输杨辉三角矩阵。
2. 测试系统消息最大长度与队列容量。
---
## 实验八 进程通信——共享内存
### 8.1 实验目的
理解共享内存通信,掌握 shmget/shmat/shmdt/shmctl信号量同步。
### 8.2 实验内容
创建共享内存fork 子进程写,父进程读入文件,信号量同步。
### 8.3 实验准备
共享内存系统调用,信号量同步。
### 8.4 实验设计
shmget 创建共享内存shmat 附加fork 子进程输入写入,父进程读入文件,信号量同步。
### 8.5 参考代码
```cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
#define BUFFERSIZE 200
#define SEMKEY 251
#define SHMKEY 231
#define SHMSIZE 1024
#define FILENAME "abc.txt"
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main() {
char buf[BUFFERSIZE];
int sem_id = semget(SEMKEY, 1, IPC_CREAT | 0600);
union semun sem_val;
sem_val.val = 0;
semctl(sem_id, 0, SETVAL, sem_val);
int shm_id = shmget(SHMKEY, SHMSIZE, IPC_CREAT | 0600);
char *shm_addr = (char *)shmat(shm_id, NULL, 0);
memset(shm_addr, 0, SHMSIZE);
char *cur = shm_addr;
FILE *fp = fopen(FILENAME, "wb");
if (fork() == 0) {
int isend = 0;
int line = 1;
printf("\n#line%d$ ", line);
while (!isend && fgets(buf, BUFFERSIZE, stdin) != NULL) {
line++;
printf("\n#line%d$ ", line);
if (buf[0] == 'Q' && strlen(buf) <= 2) {
isend = 1;
printf("\nExit...\n");
} else {
memcpy(cur, buf, strlen(buf));
cur += strlen(buf);
struct sembuf sem_op = {0, 1, 0};
semop(sem_id, &sem_op, 1);
}
}
*cur = -1;
shmdt(shm_addr);
exit(0);
} else {
while (1) {
struct sembuf sem_op = {0, -1, 0};
semop(sem_id, &sem_op, 1);
if (*cur == -1) break;
int i;
for (i = 0; *cur != '\n'; cur++, i++)
buf[i] = *cur;
cur++;
buf[i] = '\n';
buf[++i] = 0;
fwrite(buf, strlen(buf), 1, fp);
}
fclose(fp);
wait(NULL);
shmdt(shm_addr);
shmctl(shm_id, IPC_RMID, NULL);
}
return 0;
}
```
### 8.6 实验结果
子进程输入,父进程写入文件,共享内存高效传输。
### 8.7 思考问题
1. 循环使用共享内存缓冲区。
2. 改用 pthread 线程的修改点。
---
## 实验九 内存与文件——内存映射文件
### 9.1 实验目的
理解内存映射文件机制,掌握 mmap/munmap/msync提高文件访问效率。
### 9.2 实验内容
将文本文件映射内存,小写转大写,反序写入目标文件。
### 9.3 实验准备
mmap 映射文件到进程地址空间,直接内存操作。
### 9.4 实验设计
打开源文件与目标文件mmap 映射修改内容反序写入munmap 解除映射。
### 9.5 参考代码
```cpp
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
int main(int argc, char **argv) {
int fd1 = open(argv[1], O_RDONLY);
int fd2 = open(argv[2], O_RDWR | O_CREAT, 0666);
int flength = lseek(fd1, 0, SEEK_END);
write(fd1, "\0", 1);
lseek(fd1, 0, SEEK_SET);
char *mapped_mem = mmap(NULL, flength, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd1, 0);
char *p = mapped_mem;
for (int i = 0; i <= flength; i++) {
if (*p >= 'a' && *p <= 'z')
*p = toupper(*p);
p++;
}
p = mapped_mem + flength;
for (int i = flength; i >= 0; i--) {
write(fd2, p, 1);
p--;
}
printf("%s\n", mapped_mem);
munmap(mapped_mem, flength + 1);
close(fd1);
close(fd2);
return 0;
}
```
### 9.6 实验结果
源文件小写转大写,目标文件为反序结果。
### 9.7 思考问题
1. 用数组下标访问映射内存。
2. 内存映射文件优势场景。
---
## 实验十 文件通信——命名管道
### 10.1 实验目的
理解命名管道FIFO与匿名管道差异掌握 mkfifo/open/read/write实现无亲缘进程通信。
### 10.2 实验内容
设计双管道聊天程序Peter 与 Jack 双向对话。
### 10.3 实验准备
命名管道可永久存在,支持无亲缘进程,阻塞/非阻塞读写。
### 10.4 实验设计
A 端创建 fa/fbfork 子进程读 fb、父进程写 faB 端子进程读 fa、父进程写 fb。
### 10.5 参考代码
#### A 端Peter
```cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
int main() {
unlink("fa");
unlink("fb");
mkfifo("fa", 0666);
mkfifo("fb", 0666);
printf("I am Peter...\n");
if (fork() == 0) {
int fd2 = open("fb", O_RDONLY);
char buf[100];
while (1) {
memset(buf, 0, 100);
if (read(fd2, buf, 100) == 0) {
kill(getppid(), 9);
return 0;
}
printf("\rJack says:%s", buf);
fflush(stdout);
}
} else {
int fd1 = open("fa", O_WRONLY);
char buf[100];
while (1) {
fgets(buf, sizeof(buf), stdin);
write(fd1, buf, sizeof(buf));
}
}
return 0;
}
```
#### B 端Jack
```cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
int main() {
printf("I am Jack...\n");
if (fork() == 0) {
int fd1 = open("fa", O_RDONLY);
char buf[100];
while (1) {
memset(buf, 0, 100);
if (read(fd1, buf, 100) == 0) {
kill(getppid(), 9);
return 0;
}
printf("\rPeter says:%s", buf);
fflush(stdout);
}
} else {
int fd2 = open("fb", O_WRONLY);
char buf[100];
while (1) {
fgets(buf, sizeof(buf), stdin);
write(fd2, buf, sizeof(buf));
}
}
return 0;
}
```
### 10.6 实验结果
先运行 A 端,再运行 B 端,双向聊天。
### 10.7 思考问题
1. 单进程实现双向通信,匿名管道能否实现。
2. 三方聊天程序设计。