【SEU-OS-Lab1】东南大学操作系统专题实践一教程

实验内容

进行Linux环境搭建,熟悉Linux基本操作。

  • 搭建Linux环境:可选择使用虚拟机(VirtualBox、VMware)或之间安装Linux系统。也可以选择使用wsl便捷搭建Linux环境。
  • Linux发行版版本选择:可选择 Fedora 或 Ubuntu。
    • Fedora 是由 Red Hat 社区支持的开源操作系统 ,它由 Fedora 项目社区开发和维护,并得到 Red Hat 的支持。作为一个独立的发行版,Fedora 提供了最新的开源软件和技术,常被用作服务器和开发环境,是许多其他发行版(例如 Red Hat Enterprise Linux,简称 RHEL)的上游版本。
    • Ubuntu 是由 Canonical 公司开发的基于 Debian 的 Linux 发行版,首次发布于 2004 年。它以“用户友好性”为核心理念,提供长期支持(LTS)版本,广泛应用于桌面、服务器和云计算领域,是目前最受欢迎的 Linux 发行版之一。
特性 Fedora Ubuntu
目标用户 开发者、高级用户 初学者、桌面用户、服务器用户
稳定性 较新(测试新技术,有一定不稳定性) 稳定(LTS 版本长期支持)
包管理器 DNF(RPM) APT(DEB)
桌面环境 默认 GNOME 默认 GNOME(支持其他版本)
适用场景 开发测试、最新技术 日常使用、云计算、服务器
社区支持 专注技术驱动 用户友好性强,社区支持广泛
  • 搭建C语言开发环境:使用Linux系统中各自的包管理工具安装gcc,g++编译工具
  • 熟悉Linux基本操作:在本次使用中常用到的Linux指令操作包括文件管理、权限管理、系统管理,如常用的cd,ls,rm,mv,cat,chmod指令。

实验目的

  • 熟悉Linux基本环境的搭建
  • 熟悉C语言编程
  • 熟悉UNIX的shell/终端/命令行
  • 学习如何使用适当的代码编辑器,如emacs
  • 了解UNIX utilities是如何实现的

实验1——Reverse程序实现

要求:通过命令行调用一个简单的Reverse程序(C语言)

设计思路

  1. 编写程序

    • 编写一个 C 程序 reverse.c,从输入文件读取内容并将其按行反向写入输出文件。
  2. 编译程序

    • 使用 GCC 编译 reverse.c,生成可执行文件 reverse

      1
      gcc reverse.c -o reverse
  3. 测试程序

    • 使用提供的测试脚本对编译的可执行文件进行测试。

      1
      ./test-reverse.sh

代码流程图

画板

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

void reverse_lines(FILE *input, FILE *output);
void print_error_and_exit(const char *message, const char *file);

int main(int argc, char *argv[]) {
FILE *input = NULL;
FILE *output = NULL;

// 处理命令行参数
switch (argc) {
case 1: // 无参数,使用标准输入和标准输出 test 7
reverse_lines(stdin, stdout);
break;
case 2: // 一个参数,提供了输入文件
input = fopen(argv[1], "r");
if (!input) {
// test 2
print_error_and_exit("reverse: cannot open file", argv[1]);
}
reverse_lines(input, stdout);
fclose(input);
break;
case 3: // 两个参数,提供了输入和输出文件
if (strcmp(argv[1], argv[2]) == 0) {
// test 4
print_error_and_exit("reverse: input and output file must differ", NULL);
}
input = fopen(argv[1], "r");
if (!input) {
// test 3
print_error_and_exit("reverse: cannot open file", argv[1]);
}
output = fopen(argv[2], "w");
if (!output) {
fclose(input);
print_error_and_exit("reverse: cannot open file", argv[2]);
}
// 获取两个文件指向的元数据
struct stat stat1, stat2;
stat(argv[1], &stat1);
stat(argv[2], &stat2);
// 检查 inode 号和设备号是否相同
if(stat1.st_ino == stat2.st_ino && stat1.st_dev == stat2.st_dev){
// test 5
print_error_and_exit("reverse: input and output file must differ", NULL);
}
reverse_lines(input, output);
fclose(input);
fclose(output);
break;
default: // 参数过多 test 1
fprintf(stderr, "usage: reverse <input> <output>\n");
exit(1);
}

return 0;
}

// 从 'input' 读取行并逆序写入 'output'
void reverse_lines(FILE *input, FILE *output) {
char *line = NULL;
char **lines = NULL;
size_t len = 0;
int num_lines = 0;
int capacity = 1;

lines = malloc(capacity * sizeof(char *));
// 读取行到动态扩展数组中
while ((getline(&line, &len, input)) != -1) {
// 将input一行放入缓冲区中,getline会自动分配缓冲区line的长度len
if (num_lines >= capacity) {
capacity *= 2;
// 重新分配内存
lines = realloc(lines, capacity * sizeof(char *));
}
lines[num_lines++] = strdup(line); // 根据缓冲区内的数据创建新的字符串
}

// 逆序输出行
for (int i = num_lines - 1; i >= 0; i--) {
fprintf(output, "%s", lines[i]);
free(lines[i]);
}
free(lines);
free(line);
}

// 打印错误消息到 stderr 并以代码1退出
void print_error_and_exit(const char *message, const char *file) {
if (file) {
fprintf(stderr, "%s '%s'\n", message, file);
} else {
fprintf(stderr, "%s\n", message);
}
exit(1);
}

运行结果

实验2——seucat程序实现

要求:通过命令行调用一个简单的seucat程序(C语言)

设计思路

  1. 编写程序

    • 编写一个 C 程序 seucat.c,读取用户指定的文件并打印其内容。
  2. 编译程序

    • 使用 GCC 编译 seucat.c,生成可执行文件 seucat。

      1
      gcc seucat.c -o seucat
  3. 测试程序

    • 使用提供的测试脚本对编译的可执行文件进行测试。

      1
      ./test-seucat.sh

代码流程图

画板

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>

void print_file_content(const char *filename);

int main(int argc, char *argv[]) {
// 如果没有指定文件,正常退出并返回状态码0
if (argc == 1) {
return 0;
}

// 遍历命令行参数中的每个文件名
for (int i = 1; i < argc; i++) {
print_file_content(argv[i]);
}

return 0;
}

// 打印文件内容
void print_file_content(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
printf("cannot open file\n");
exit(1);
}

char buffer[1024];
// 使用 fgets 按行读取并打印文件内容
while (fgets(buffer, sizeof(buffer), file)) {
printf("%s", buffer);
}

fclose(file);
}

运行结果

实验3——seugrep程序实现

要求:通过命令行调用一个简单的seugrep程序(C语言)

设计思路

1. 编写程序

+ 编写一个 C 程序 `seugrep.c`,逐行查看一个文件,并在该行内找到用户指定的搜索词。如果存在,则打印该行。
  1. 编译程序

    • 使用 GCC 编译 seugrep.c,生成可执行文件 seugrep。

      1
      gcc seugrep.c -o seugrep
  2. 测试程序

    • 使用提供的测试脚本对编译的可执行文件进行测试。

      1
      ./test-seugrep.sh

代码流程图

画板

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void search_in_file(const char *search_term, FILE *file);

int main(int argc, char *argv[]) {
if (argc == 1) {
// test 3
printf("searchterm [file ...]\n");
exit(1);
}

const char *search_term = argv[1]; // 搜索项

// 如果只提供了搜索项,则从标准输入读取数据
if (argc == 2) {
// test 4
search_in_file(search_term, stdin);
return 0;
}

// 遍历命令行中指定的文件
for (int i = 2; i < argc; i++) {
FILE *file = fopen(argv[i], "r");
if (file == NULL) {
// test 2
printf("cannot open file\n");
exit(1);
}
search_in_file(search_term, file);
fclose(file);
}
return 0;
}

// 在指定文件中搜索字符串
void search_in_file(const char *search_term, FILE *file) {
char line[1024];
while (fgets(line, sizeof(line), file)) {
if (strstr(line, search_term)) {
printf("%s", line);
}
}
}

运行结果

实验4——seuzip程序实现

要求:通过命令行调用一个简单的seuzip程序(C语言)

设计思路

  1. 编写程序

    • 编写一个 C 程序 seuzip.c,压缩文件工具。这里的压缩方式使用RLE压缩算法:当在一行中遇到n个相同类型的字符时,seuzip 将其变成数字n和一个该字符示例。
  2. 编译程序

    • 使用 GCC 编译 seuzip.c,生成可执行文件 seuzip。

      1
      gcc seuzip.c -o seuzip
  3. 测试程序

    • 使用提供的测试脚本对编译的可执行文件进行测试。

      1
      ./test-seuzip.sh

代码流程图

画板

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>
#include <stdlib.h>

void zip_file(FILE *input, FILE *output);

int main(int argc, char *argv[]) {
if (argc < 2) {
// test 3
printf("seuzip: file1 [file2 ...]\n");
exit(1);
}

// 汇总所有输入文件的内容 test 2
FILE *temp_file = tmpfile();

// 将所有输入文件内容写入临时文件
for (int i = 1; i < argc; i++) {
FILE *file = fopen(argv[i], "r");

char buffer[1024];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
fwrite(buffer, 1, bytes_read, temp_file);
}

fclose(file);
}

// 回到临时文件的开头
rewind(temp_file);

// 压缩临时文件内容并输出到标准输出
zip_file(temp_file, stdout);

fclose(temp_file);
return 0;
}

// 压缩文件内容
void zip_file(FILE *input, FILE *output) {
int count = 0;
char current, previous;

if ((previous = fgetc(input)) == EOF) {
return; // 空文件
}

count = 1;
while ((current = fgetc(input)) != EOF) {
if (current == previous) {
count++;
}
else {
fwrite(&count, sizeof(int), 1, output); // 写入重复计数
fwrite(&previous, sizeof(char), 1, output); // 写入字符
previous = current;
count = 1;
}
}

// 写入最后一组字符(current == previous is true)
fwrite(&count, sizeof(int), 1, output);
fwrite(&previous, sizeof(char), 1, output);
}

运行结果

实验5——seuunzip程序实现

要求:通过命令行调用一个简单的seuunzip程序(C语言)

设计思路

  1. 编写程序

    • 编写一个 C 程序 seuunzip.c,与seuzip工具相反,接收压缩文件并写入未压缩结果。
  2. 编译程序

    • 使用 GCC 编译 seuunzip.c,生成可执行文件 seuunzip。

      1
      gcc seuunzip.c -o seuunzip
  3. 测试程序

    • 使用提供的测试脚本对编译的可执行文件进行测试。

      1
      ./test-seuunzip.sh

代码流程图

画板

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>

void unzip_file(FILE *input, FILE *output);

int main(int argc, char *argv[]) {
if (argc < 2) {
// test 3
printf("seuunzip: file1 [file2 ...]\n");
exit(1);
}

// 对每个输入文件进行解压
for (int i = 1; i < argc; i++) {
// test 2
FILE *file = fopen(argv[i], "rb");
unzip_file(file, stdout);
fclose(file);
}

return 0;
}

void unzip_file(FILE *input, FILE *output) {
int count;
char character;

// 逐组读取压缩数据
while (fread(&count, sizeof(int), 1, input) == 1) {
fread(&character, sizeof(char), 1, input);
for (int i = 0; i < count; i++) {
fputc(character, output);
}
}
}

运行结果

实验体会

  • Linux环境搭建的选择
    • 初始我选择使用VirtualBox虚拟机来安装Fedora 7.0 作为我的Linux开发环境,第一次打开Fedora时会自动安装gcc等开发环境,这对完成本次实验来说是一个便利。但在后续使用时,由于虚拟机的性能问题和一些窗口分辨率,键鼠的bug,导致我使用的时候感到卡顿和不适。并且Fedora的图形化界面其实在本次实验中是没有必要的,图形化界面反而使得进行简单操作的步骤增加。因此最终我选择在WSL2中安装Ubuntu作为Linux的开发环境。
    • WSL (Windows Subsystem for Linux) 是由 Microsoft 开发的一种在 Windows 操作系统中运行 Linux 环境的兼容层。它允许用户直接在 Windows 上运行 Linux 命令行工具、应用程序和开发环境,而无需额外安装虚拟机或双系统。WSL的性能损耗小,并且搭配vscode的开发体验十分便利,可以便捷地使用vscode的文件管理系统和终端系统。
  • 编译程序中的小插曲

    • 在编译 .c 文件时,一开始我使用了如下指令

      1
      gcc -c reverse.c -o reverse
      • 因此在运行测试脚本时无法通过。原因在于上面的指令使用了-c 参数,仅编译代码生成目标文件(object file),不会链接生成可执行文件。输出文件是机器可读的二进制目标文件,通常带 .o 后缀。这种方法通常用在多文件项目中,每个源文件单独编译生成目标文件,最后将所有目标文件链接成一个可执行文件。
      • 将编译指令换回如下后就正确了。
      1
      gcc -o reverse reverse.c
命令 特点 输出文件 用途
gcc -c reverse.c -o reverse 只编译,不链接,生成目标文件(非可执行文件)。 reverse.o(目标文件) 多文件编译项目的中间步骤。
gcc -o reverse reverse.c 编译并链接,生成完整的可执行文件。 reverse(可执行文件) 用于生成单文件的可执行程序。
  • 本次实验部分应该是参考了MIT的Xv6操作系统实验,这些实验内容提供了标准样例来检测程序的正确性。这些示例程序有些是为了检验对文件打开的异常处理,有些是为了检验对命令行参数错误的异常处理,检测样例固定了输入输出,因此在实验过程中很容易因为输出的格式错误导致不通过。在面对这样的检验方法时,最好的方法是查看其提供的标准输入输出并且一边尝试一边修改代码。
    • 例如在实现seuzip时,当命令行参数给的是多个文件时,是分文件压缩还是将所有文件中的内容整合起来再压缩?当没有看明白实验要求的时候,查看示例输入输出就能很好的解决这个问题。