操作系统综合实验1

本文详细介绍了Linux系统中进程间通信的各种机制,包括无名管道、命名管道、SystemVIPC的消息队列、共享内存和信号量。通过实例展示了如何使用这些通信方式实现数据交换和同步。此外,还探讨了POSIX信号量在生产者-消费者问题中的应用,并给出了一段简易Shell程序的实现,该程序支持内部命令、外部命令、重定向和管道操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、实验目的与要求

综合利用进程控制的相关知识,结合对shell功能的和进程间通信手段的认知,编写简易shell程序,加深操作系统的进程控制和shell接口的认识。

二、实验内容

  • 使用Linux操作系统;学习使用Linux进程间控制,进程间通信,管道,消息队列,共享内存等手段以及处理机调度
  • 学会使用POSIX信号量实现生产者与消费者间同步关系
  • 编写简易shell程序

三、实验步骤与过程

1 Linux学习与实践

1.1 管道

进程间的管道通信有两种形式,无名管道用于父子进程间,命名管可以用于任意进程间— —命名管道在文件系统中有可访问的路径名。管道通信方式主要用于单向通信,如果需要双向 通信则建立两条相反方向的管道。管道实质是由内核管理的一个缓冲区(一边由进程写入,另 一边由进程读出),因此要注意,如果缓冲区满了则写管道的进程将会阻塞。另外管道内部没 有显式的格式和边界,需要自行处理消息边界,如果多进程间共享还需要处理传送目标等工作。

无名管道

管道(pipe),或称无名管道,是所有 Unix 都提供的一种进程间通信机制。管道是单向的 信道,进程从管道的写端口写入数据,需要数据的进程从读端口中获取数据,数据在管道中按 到达顺序流动。Unix 命令中使用“|”来连接两个命令时使用的就是管道,例如“ls | more”将 ls命令的标准输出内容写入到管道中,管道的输出内容作为 more 命令的标准输入。注意,重定向技术虽然看起来和管道很相似,例如“ls > temp”,但重定向并不使用管道。

如下面图 1,以 pipe()为例展示父子进程间使用管道进行通信的方法,pipe()将通过两个文件描述符(整数)来指代管道缓冲区的读端和写端(代码中用 fds[] 变量记录)。其中父进程关闭管道的读端 fds[0]并往管道的写端 fds[1]写出信息,子进程关闭了管道的写端 fds[1]并从管道的读端 fds[0]读回信息。

1 pipe-demo.c 代码

2 pipe-demo.c输出

图 2是图 1中pipe-demo运行的输出,其表明父进程发送了消息到管道,子进程成功接收到了“Message from parent”。

命名管道

前面提到的无名管道有一个主要缺点,只能通过父子进程之间(及其后代)使用文件描述符的继承来访问,无法在任意的进程之间使用。命名管道(named pipe)或者叫 FIFO 则突破了这个限制。可以说 FIFO 就是无名管道的升级版——有可访问的磁盘索引节点,即 FIFO 文件将出现在目录树中(不像无名管道那样只存在于 pipefs 特殊文件系统中)。

下面图 3我们用 mkfilo 命令来创建命名管道 os-exp-fifo,如屏显 4-2 所示,其中 ls 命令查看时可以看出其类型是管道“p”。

3 mkfifo 创建命名管道

此时可以用 cat os-exp-fifo 命令尝试从管道中读入数据,但是此时管道中还没有写入任何数据,因此 cat 将进入阻塞状态,如图 4:

4 用 cat 尝试读取空的管道文件(阻塞)

如果此时在另一个终端上用“echo Hello, Named PIPE! >os-exp-fifo”则cat 会被唤醒并读入管道数据回显字符串“Hello, Named PIPE!”,如图 5

5 用 echo 向管道写入数据

1.2 System V IPC

Linux 的进程通信继承了 System V IPC。System V IPC 指的是 AT&T 在 System V.2 发行版中 引入的三种进程间通信工具:

  1. 信号量,用来管理对共享资源的访问
  2. 共享内存,用来高效地实现进程间的数据共享
  3. 消息队列,用来实现进程间数据的传递。

我们把这三种工具统称为 System V IPC 的对象,每个对象都具有一个唯一的IPC标识符 ID。为了使不同的进程能够获取同一个 IPC 对象,必须提供一个IPC 关键字(IPCkey),内核负责把 IPC 关键字转换成 IPC 标识符 ID。下面我们观察这三种IPC 工具。

在Linux中执行ipcs命令可以查看到当前系统中所有的System V IPC对象,如图 6所示。

6 ipcs 命令的输出

查看这些 IPC 对象时还可以带上参数,ipcs -a 是默认的输出全部信息、ipcs -m 显示共享内 存的信息、ipcs -q 显示消息队列的信息、ipcs -s 显示信号量集的信息。另外用还有一些格式控 制的参数,–t 将会输出带时间信息、-p 将输出进程 PID 信息、-c 将输出创建者/拥有者的 PID、 -l 输出相关的限制条件。例如用 ipcs -ql 将显示消息队列的限制条件,如图 7所示。

7 ipcs -ql 的输出

删除这些 IPC 对象的命令是 ipcrm,它会将与 IPC 对象及其相关联的数据也一起删除, 管 理员或者 IPC 对象的创建者才能执行删除操作。该命令可以使用 IPC 键或者 IPC 的 ID 来指定 IPC 对象:ipcrm -M shmkey 删除用 shmkey 创建的共享内存段而 ipcrm -m shmid 删除用 shmid 标识的共享内存段、ipcrm -Q msgkey 删除用 msqkey 创建的消息队列而 ipcrm -q msqid 删除用 msqid 标识的消息队列、ipcrm -S semkey 删除用 semkey 创建的信号而 ipcrm -s semid 删除用 semid 标识的信号。

1.2.1 消息队列

消息队列有些项邮政中的邮箱,里面的消息有点像信件——有信封以及写有内容的信纸。 由于各条消息可以通过类型(type)进行区分,因此可以用于多个进程间通信。比如一个任务 分派进程,创建了若干个执行子进程,不管是父进程发送分派任务的消息,还是子进程发送任务执行的消息,都将 type 设置为目标进程的 PID,目标进程只接收消息类型为 type 的消息就 实现了子进程只接收自己的任务,父进程只接收任务结果。

下面图 8图 9给出代码 msgtool.c,其每次启动都是以新的进程形式运行,因此各次运行都是相互独立的。其中发送消息的核心函数是 msgsnd(),第一个参数是消息队列的 ID,第二个参数时被发送消息的起始地址(消息的第一个成员是一个整数用于指出消息类型),第三个参数时消息长度,第四个参数指定写消息时的一些行为(此例子用 0);接受消息的函数是 msgrcv(),第一个参数用于指定消息队列的ID,第二个参数是接受缓冲区地址,第三个参数指出希望接受的消息类型(0 表示接受任意类型的一条消息,>0表示接受指定类型的消息,

8 msgtool.c 代码

9 msgtool.c 代码

执行 msgtool s 1 Hello,my_msg_queue!发送类型为 1 的消息,然后用ipcs -q 查看新创建的消息队列,里面有 20 个字节的一条消息。此时再执行 msgtool -r 1(另一个进程了)读走类型为 1 的消息,然后再用 ipcs -q 可以看到该消息队列为空(0 字节)了。上述操作的输出如图 10:

10 msgtool 的执行结果

1.2.2 共享内存

System V IPC 的共享内存是由内核提供的一段内存,可以映射到多个进程的续存空间上, 从而通过内存上的读写操作而完成进程间的数据共享。我们首先来看看如何创建共享内存的, 示例代码如图 11所示,它创建了一个 4096 字节的共享内存区。shmget()的第一个参数 IPC_PRIVATE(=0,表示创建新的共享内存),第二个参数是共享内存区的大小,第三个是访问 模式。虽然也可以像前面的消息队列的例子那样通过 ftok()将键值转换成 ID,但这里没有指定 ID,而是创建共享内存后由系统返回一个 ID 值(后面的进程要使用该共享内存时需要指定该 ID)。

11 shmget-demo.c代码

执行该程序,其输出如图 12所示。输出结果表明新创建的共享内存的 ID为24,长度为 4096 字节,当前还没有进程将他映射到自己的进程空间(连接数列为 0)。

12 shmget-demo.c的运行结果

下面展示另一个进程通过影射该共享内存而使用它的过程,具体如图 13所示

13 shmatt-write-demo.c 代码

我们运行 shatt-demo 24(命令行参数中指出共享内存的 ID 为 24),其第一段输出结果如图 14所示。完成共享内存的映射后,shmatt-write-demo 往共享内存中写入一个 字符串“Hello shared memory!”。shmatt-write-demo 还通过 system()执行了“ipcs -m”,因此 也输出了当前的共享内存信息,可以看到 ID 为 24 的共享内存已经有被映射了一次 (nattach 列为 1)。

14 shmatt-write-demo.c运行结果

接下来使用 ps -a 指令,可以看到 shmatt-write-de 的 PID为4898,如图 15

15  ps -a 指令查看matt-write-de 的 PID

使用 cat /proc/4898/maps 查看进程的进程空间,可以看到进程布局如图 16所示

16 运行之后的进程布局

在 shmatt-write-demo击键回车后将解除共享内存的映射,此时ipcs -m显示对应的共享内存区没有人使用(连接数为 0),如图 17所示。此时如果检查内存布局可以发现原来区间的虚存已经消失。

17 运行write后的共享内存

此时再尝试用另一个程序去映射该共享内存并从中读取数据,shmatt-read-demo 代码如图 18所示。

18 shmatt-read-demo.c代码

如图 19,可以看到虽然创建该共享内存的进程已经结束了,可是shmatt-read-demo映射 ID 为 24 的共享内存后仍读出了原来写入的字符串。

19 运行read后的共享内存

从上面实验看出共享内存是比较灵活的通信方式,不需要像管道一样用文件接口read()、write()等函数,也不需要像消息队列那样用 msgsend()/msgrcv()等函数来操作,直接用内存指针的方式就可以操作。虽然实验中没有验证其容量,但是共享内存的容量远比管道和消息队列大。

1.2.3 信号量数组/信号量集

在操作系统原理性课程中我们以及学习过信号量和信号量集机制。Linux 支持的System V IPC 中的信号量实际上是信号量数组(信号量集),一次可以创建多个信号量。创建或者获得信号量集之后,可以对各个信号量进行 P/V 操作(或者称up/down 操作),进程进行 P/V 操作时遵循信号的同步约束关系——由操作系统完成进程的阻塞或唤醒。

1.3 进程间同步

Linux 同时支持 System V IPC 中的信号量集和 POSIX 信号量。前者常用于进程间通信、是基于内核实现的(不随进程结束而消失);而后者是常用于线程间同步、方便使用且仅含一个信号量。POSIX 信号量分成有名信号量和无名信号量,前者和一个文件的路径名相关联,创建 后不随进程结束而消失(可用于进程间通信),反之无名信号量则只在进程生命周期内存在且 只能在该进程创建的线程间使用。 上述两种信号量的编程接口函数是很容易被区分:对于所有 System V信号量函数,在它们的名字里面没有下划线(例如,有semget()而不是sem_get()),然而所有的 POSIX 信号量 函数都有一个下划线(例如,有sem_post()而不是sempost())。Linux操作系统内核内部也有多个并发的执行流,它们之间使用内核的信号量,和这里讨论的用户态信号量又不相同。

1.3.1 System V IPC 信号量集

进程间的 System V IPC 信号量集的同步机制已经在前面 System V IPC 中和进程间通信主题一并讨论过,这里不再重复。

1.3.2 POSIX 信号量

POSIX 信号量又分成有名信号量和无名信号量,前者可以用于在多个进程间或多个线程间的同步,无名信号量只能用于线程间同步。两者的创建函数不同,但是响应的P/V操作函数是一样的。有名信号量由于可以通过标识来访问,因此可以同时用于进程间同步和线程间同步。有名信号量的创建使用 sem_open()完成,代码 psem-named-open.c 如图 20所示,其先用 sem_open()创建了一个信号量,该信号量由一个字符串所标识。

20 psem-named-open.c代码

然后用 gcc psem-named-open.c -o psem-named-open -lpthread(参数-lpthread 用于指出链接时所用的线程库)完成编译,然后运行 psem-named-open。如果没有输入作为标识的文件名字符串,则给出提示要求用户输入;如果输入一个文件名字符串,正常情况将完成创建过程,如图 21所示。

21 psem-named-open 的输出

之后尝试执行 P/V 操作中的 V 操作(即对信号量进行减 1 操作,可能引发阻塞),程序psem-named-wait-demo.c如图 22所示,它通过 sem_wait()来执行V 操作(减1 操作),并且通过 sem_getvalue()来查看信号量的值。同样出于代码简洁的考虑,这里的代码也是没有检查 sem_open()是否成功获得了信号量。因此,如果输入错误的标识字符串,则无法成功获得所指定的信号量,sem_wait()引用无效的信号量而引发段错误。

22 psem-named-wait-demo.c 代码

编译并执行psem-named-wait-demo,输入前面创建信号量时使用的文件名标识,此时打印出当前信号量值为 0,如果再运行一遍,由于此时信号量的值已经为0,再进行V操作(减 1 操作)将阻塞该进程。程序运行情况如图 23所示。

23 psem-named-wait-demo 的运行结果

图 23显示该程序第二次运行后并没有返回到 shell 提示符,如果此时用另一个终端执行 ps 命令可以看到该进程处于S+状态,如图 24所示。

24 查看 psem-named-wait-demo 的运行状态

再接下来看 P 操作,使得前面的 psem-named-wait-demo 进程从原来的阻塞状态唤醒并执行结束。程序如图 25所示,这里也要注意实际上代码并没有对sem_open()的返回值进行判断。

25 psem-named-post-demo.c 代码

编译并执行 psem-named-post-demo(与前面不在同一个终端 shell 上),可以看到此时信号量的值增加到 1,并使得原来阻塞的psem-named-wait-demo被唤醒并执行完毕,如图 26所示。

26 psem-named-post-demo 的运行输出

同时可以看到,原来阻塞的 psem-named-wait-demo 被唤醒并执行完毕,如图 27所示。

27 唤醒阻塞的 psem-named-wait-demo 进程

最后,如果不希望使用这个信号量可以通过 sem_unlink()撤销该信号量,如图 28所示。

28 psem-named-unlink-demo.c 代码

而 POSIX 无名信号量适用于线程间通信,如果无名信号量要用于进程间同步,信号量要放在共享内存中(只要该共享内存区存在,该信号灯就可用)。无名信号量使用sem_init()创建。

互斥量是信号量的一个退化版本,仅用于并发任务间的互斥访问。下面先用一个代码来展示多线程并发且没有用互斥量保护共享变量的情形,如代码4-10 所示,此时结果可能会出现错误。该程序对一个缓冲区(缓冲区内是数值为 3、4、3、4......交织的整数)内的每个整数进行检查,并对数值为 3 的整数进行计数统计,统计工作由16 个线程并发完成(每个线程负责缓冲区的 1/16 的数据)。

下面的代码 no-mutex-demo.c 展示多线程并发且没有使用互斥量保护共享变量的状况,代码如图 29所示,编译后运行 no-mutex-demo,所得结果如图 30所示。可以看到,每次运行的结果并不唯一,共享变量没有被互斥访问。

29 no-mutex-demo.c 代码

30 no-mutex-demo 的运行结果

如果对 count++这个临界区加以保护,即增加一个互斥量 mutex m;就能避免出现这个问题。编译运行no-mutex-demo1,每次运行都能获得相同的结果。代码如图 31所示,结果如所示。由于实现了共享变量的互斥访问,因此每次运行的结果都是确定的值。

31 no-mutex-demo1.c 代码

图 32 no-mutex-demo1.c 结果

       至此,自行完成的Linux学习与实践全部结束。

2 使用POSIX信号量完成生产者与消费者的同步关系

根据前面的学习与本地的题目要求,我们可以得到设计的思路:

(1)要创建一个生产者消费者共用的缓冲区;

(2)对于生产者程序,首先要获取共享内存区并且挂入内存,之后创建三个信号量,将读取的行写入缓冲区(信号量在过程中要有对应的操作),然后释放信号量,结束内存映射并删除共享内存区域;

(3)对于消费者程序,同样要获取共享内存区并挂入内存,之后获取三个信号量,将缓冲区中的行字符串打印(信号量在过程中要有对应的操作),然后释放信号量。这里需要创建两个进程并发的进行上述操作。

头文件:

首先,定义如图 33的头文件,定义NUM_LINE=16作为共享内存的行数(可存储的行数),每行的内存大小为256;并且定义三个信号量分别用来判断是否互斥以及共享内存的空、满。

33 头文件

生产者代码:

       由于本次实验已经给出了代码框架,只需要补充框架中对应的内容即可。补全代码如图 34,定义共享内存指针、共享内存id以及访问共享内存的信号量指针,然后获取共享内存区,并将共享内存映射到内存空间。

34 获取共享内存区并放入内存

之后需要创建三个信号量,sem_queue、sem_queue_empty、sem_queue_full 的信号量初始值分别为 1、NUM_LINE 和 0,具体如图 35:

35 创建信号量

接下来如图 36,将读写指针初始化,开始时都指向第0行,将输入的行写入缓冲区,并且要有信号量操作。对共享区域加锁,输出信号量的值,把输入的内容存入共享区域,更新写操作的行并判断:如果是quit 则跳出循环。

36 将输入的行写入缓冲区

最后一部分如图 37,为释放信号量,结束共享内存在本进程的挂载映象,删除共享内存区域。该部分仿照前面的实验指导部分完成。

37 释放信号量

生产者代码:

如图 38,消费者代码与生产者代码相似,首先获得生产者代码中的已创建的共享内存,然后将共享内存区映射到本进程的进程空间。

38 生产者获取共享内存区

       如图 39,然后要获取 生产者创建的 3 个信号量。创建两个进程,当进入子进程时先等待信号量,进行 P 操作,当成功后打印消费内容及进程号,发现 quit 后退出,释放信号量。父子进程都采用相同的处理方式。

39 生产者信号量操作

       如图 40,同图 39,父子进程采用相同的处理方式。此处要注意的是,父进程释放信号量时,最后要加上 waitpid(fork_result,NULL,0); 用以等待所有的子进程结束后再退出,防止有孤儿进程生成。

40 生产者父进程信号量操作

       完成代码的编写后,将进行测试。如图 41,左侧为producer的运行,右侧为customer的运行,可以看到,程序完成了同步与通信功能,producer的输入通过共享内存,customer能够进行读取,producer输入的产品内容与customer输出的消费信息一一对应,其中产品号为奇数的内容对应父进程的运行,产品号为偶数的内容对应子进程的运行。当producer输入 quit 之后,两边都正常退出。

41 生产者/消费者问题的代码运行结果

3 设计简单Shell程序

根据前面的学习,我们可以知道,首先需要将输入的命令进行读入,并进行内部命令与外部命令的解析,如果成功解析出对应的命令则进行执行,否则视为无效命令。

首先,如图 42,引入必须的头文件,借助宏定义完成几个命令的分类并预先定义好几个函数。具体作用将在下面介绍。

42 shell程序的头文件以及函数

然后,定义如图 43的主函数。主函数的主要作用为申请存命令的空间,循环读入命令,并根据命令的不同做出对应的执行。在此,如“help”,“exit”等的内部命令将使用字符串比较的形式进行解析,并直接进行执行。

43 shell程序的主函数

由于shell需要打印提示符信息,在图 44中,我定义了一个函数获取当前目录并输出提示符。

44 shell程序的提示信息

对于帮助信息,将调用图 45中的函数直接输出帮助信息即可。

45 shell程序的帮助信息函数

由于要完成命令行的输入,我定义了如图 46的输入函数。利用循环将字符一个一个读入,当读入换行符或者超过长度时则终止循环。每次读入的时候都将读到的字符存入命令数组中。

46 shell程序的读入函数

由于需要对命令进行解析,我定义了如图 47的函数。他将利用空格,对输入的命令进行拆分,分别存入数组中,直到遇到了换行符。

47 shell程序的命令解析函数

Shell中最重要的是执行命令。执行命令中最重要的是判断命令是否合法。并根据命令的类型(重定向命令,管道命令等)对命令进行分类,代码如图 48,首先需要将命令取出,并判断是否含有后台运行符。

48 shell程序的命令判断

接着,判断是否为重定向或者管道命令。如果非法则flag++,如果合法将进行分类。首先,对重定向符号以及管道符号进行判断,具体如图 49。在此将判断对重定向符号以及管道符号的个数,并对命令进行分类。然后对于重定向命令,将取出重定向命令的目标,并存入file变量中。如果是管道命令,则将管道符号后的可执行shell命令取出,以便进行执行。

49 shell程序的重定向与管道命令的判断

完成了命令的分类,将进行命令的执行。首先,如图 50,如果不含有重定向及管道符号的最常规命令,直接调用execvp函数进行执行即可。

50 shell程序的常规命令执行

对于有输出重定向符号的命令,如图 51,利用dup2函数进行重定向即可。

51 shell 程序的输出重定向命令执行

对于有输入重定向符号的命令,如图 52,利用dup2函数进行重定向即可,与输出重定向类似。

52 shell程序的输入重定向命令执行

对于管道命令,则相对复杂,需要利用子进程将管道符前面的命令执行完毕后再调用父进程完成管道符右侧命令的执行。具体代码如图 53,首先,使用fork开辟子进程,并利用子进程将管道符左边的命令的输出写入到借助中间文件里。这个过程中父进程需要等待子进程完成执行后再行执行。接着,父进程将由子进程写入的中间文件作为输入,运行管道符右侧的命令。最后删除暂存的文件即可。

53 shell程序的管道命令执行

此外,如果有后台运行符,则父进程直接返回而不需等待子进程。此时代码如图 54

54 shell程序的后台运行符处理

此外,在每次执行的过程中都需要对命令进行查找,因此使用了如图 55的函数对命令进行查找。将分别在当前目录,bin目录,以及user下的bin目录进行查找。

55 shell程序的命令查找

所有代码如上,接下来进行测试如下。首先,测试内部命令“help”,效果如图 56,将打印帮助信息

56 shell程序输出帮助信息

       如果输入非法命令,则也会进行提示,具体如图 57

57 shell程序非法命令

对外部命令的支持,首先,对“ls”进行测试,结果如图 58

58 shell程序ls运行结果

接着,对“ps”进行测试,结果如图 59

59 shell程序ps运行结果

接着,对“cp”命令进行测试,在此,我测试使用cp命令复制“helloworld.c”文件,结果如图 60和图 61

60 shell 程序的cp命令

61 shell 程序的cp命令结果

此外,还可以在我的shell里对c程序进行编译并运行,结果如图 62

62 shell程序对c代码的编译与运行

接着,测试管道功能,使用 ls | grep helloworld 和 ls | grep shell 作为指令。grep 指令是用于查找文件里符合条件的字符串,ls | grep helloworld 就是找出当下文件夹里含有helloworld的文件名并打印出来。在 myshell 下的运行结果如图 63所示,可以看到管道命令可以正常执行。

63 shell程序的管道命令测试

接着,测试重定向功能。首先,对输入重定向进行测试。为了完成输入重定向的测试,我编写了一个简单的a+b程序,代码如图 64,并创建了用于重定向的文件,如图 65所示。

64 a+b程序

65 输入重定向的文件

接着,在我的shell中,进行输入重定向的测试,结果如图 66,可以看到程序执行正确

66 shell的输入重定向测试

最后,对输出重定向进行测试,如图 67,使用ps命令,将结果重定向到“log.txt”中,结果如图 68。

67 shell的输出重定向测试

68 shell输出重定向测试的结果

可以看到输出重定向的结果也符合预期。至此,shell编写完毕,我的shell可以接受内部,外部命令并以包含路径的信息作为提示符,可以在shell内部循环读取执行命令。此外,我的shell也实现了输入输出的重定向以及管道命令。

1、图书管理系统 以UNIX系统文件部分系统调用为基础设计一个简易的图书管理系统。要求实现:图书的录入、查询、借阅、清理、统计等功能、还要实现对每天的借阅情况进行统计并打印出统计报表,操作界面要尽量完善。图书资料信息必须保存在文件中。 2、信号通信与进程控制 (l)进程的创建:编写一段程序,使用系统调用fork()创建两个或多个子进程。当此程序运行时,在系统中有一个父进程和其余为子进程在活动。 (2)进程的控制:在程序中使用系统调用lockf()来给每一个进程加锁,实现进程之间的互斥。 (3)进程通信:①软中断通信;②在程序中使用实例signal(SIGINT,SIG_IGN)和signal(SIGQUIT,SIG_IGN)进行通信操作,观察执行结果,并分析原因。 (4)软中断的捕获与重定义。首先定义一个服务函数function(),然后利用signal(sig,function)系统调用来实现中断的捕获与改道。 (5)使用操作系统保留给用户的信号SIGUSR1和SIGUSR2进行通信。 (6)扩展程序,使之成为信号或事件驱动的应用程序。 3、管道通信 利用UNIX系统提供的管道机制实现进程间的通信。 (1)管道通信。利用pipe()和lockf()系统调用,编写程序,实现同族进程间的通信。使用系统调用pipe()建立一条管道线;创建子进程P1、P2、…。子进程Pi分别向管道各写信息,而父进程则从管道中读出来自于各子进程的信息,实现进程家族间无名管道通讯。 扩展之,使之成为客户/服务器模式,并完成一定的任务(自己定义)。 (2)命名管道通信:利用mkfifo(name,mode)或mknod(name,mode,0)创建一个命名管道,然后利用它和文件部分系统调用实现不同进程间的通信。 改造之,使之成为客户/服务器模式,并完成一定的任务(自己定义)。 4、进程间通信(IPC):消息机制 (1)消息的创建、发送和接收 使用系统调用msgget(),msgsnd(),msgget(),及msgctl()编制一长度为1K的消息发送和接收的程序。 1)为了便于操作和观察结果,用一个程序作为“引子”,先后fork()两个子进程,SERVER和CLIENT,进行通信。SERVER和CLIENT也可分别为2个各自独立的程序。 2)SERVER端建立一个Key为175的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER。SERVER每接收到一个消息后显示一句“(server)received”。 3)CLIENT端使用key为175的消息队列,先后发送类型从10到1的消息,然后退出。最后的一个消息,即是SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(client)sent”。 4)父进程在SERVER和CLIENT均退出后结束。 (2)功能扩展:在sever端创建一个服务函数,从而实现C/S通讯 要求SERVER每接收到一次数据后不仅仅显示“(server)received”,而是做一些其它事情,比如读取或查询某个文件,或者执行一个shell命令等。此功能可由设计者自己定义。 在此基础上可以扩展客户端,比如设计一个菜单界面,接收不同的选项,并发送到服务器端,请求对方提供服务。 5、进程间通信(IPC):共享内存机制 (1) 共享存储区的创建,附接和断接 使用系统调用shmget(),shmat(),msgdt(),shmctl(),编制一长度为1K的消息发送和接收的程序。 1)为了便于操作和观察结果,用一个程序作为“引子”,先后fork()两个子进程,SERVER和CLIENT,进行通信。SERVER和CLIENT也可分别为2个各自独立的程序。 2)SERVER端建立一个Key为375的共享区,并将第一个字节置为-1,作为数据空的标志,等待其他进程发来的消息。当该字节的值发生变化时,表示收到了信息,并进行处理。然后再次把它的值设为-1。如果遇到的值为0,则视为结束信号,取消该队列,并退出SERVER。SERVER每接收到一次数据后显示“(server)received”。 3)CLIENT端建立一个Key为375的共享区,当共享取得第一个字节为-1时,SERVER端空闲,可发送请求。CLIENT随即填入9到0。期间等待Server端的再次空闲。进行完这些操作后,CLIENT退出。CLIENT每发送一次数据后显示“(client)sent”。 4)父进程在SERVER和CLIENT均退出后结束。 (2)功能扩展:在sever端创建一个服务函数,从而形成C/S通讯模式 要求SERVER每接收到一次数据后不仅仅显示“(server)received”,而是做一些其它事情,比如读取或查询某个文件等。此功能可由设计者自己定义。 在此基础上可以扩展客户端,比如设计一个菜单界面,接收不同的选项,并发送到服务器端,请求对方提供服务。 6、文件加密存储 利用文件系统的系统调用编程对文件的内容进行加、解密。 要求程序从环境的命令行携带4个参数。第一个是文件名,第二个是操作方式,第三个是密钥,第四个是加密钥循环使用长度。其中后两个参数是可以忽略,但对忽略的情况要提供缺省值。 要求最后实现对文件的加密转储,或通过改道的办法进行转储。对于已加密的文件可以进行解密显示或解密后转储。形成加密或解密文件后要删除原来的文件。 建议加密过程使用按字符进行异或的方式处理,也可以是仿射加密方式,比如把所有的字符做一个平移变换:A-A+C(A为任意字母表中的字母,C为常数,为了防止越界或溢出,可以改造其为A-(A+C)MOD 256),这里要提醒的是,要注意逆变换。 建议,设计者也提供自己的加密方式。 7、存储管理 存储管理的主要功能之一是合理地分配空间。请求页式管理是一种常用的虚拟存储管理技术。本设计的目的是通过请求页式存储管理中页面置换算法模拟设计,了解虚拟存储技术的特点,掌握请求页式存储管理的页面置换算法。要求: (1)通过随机数产生一个指令序列,共320条指令。指令的地址按下述原则生成: ①50%的指令是顺序执行的;②25%的指令是均匀分布在前地址部分;③25%的指令是均匀分布在后地址部分。 具体的实施方法是:①在[0,319]的指令地址之间随机选取一起点m;②顺序执行一条指令,即执行地址为m+l的指令;③在前地址[0,m+1]中随机选取一条指令并执行,该指令的地址为m’;④顺序执行一条指令,其地址为m’+1;⑤在后地址[m’+2,319]中随机选取一条指令并执行;⑥重复上述步骤①~⑤,直到执行320次指令。 (2)将指令序列变换成为页地址流。设:①页面大小为1K;②用户内存容量为4页到32页;③用户虚存容量为32K。 在用户虚存中,按每页存放10条指令排列虚存地址,即320条指令在虚存中的存放方式为: 第0条~第9条指令为第0页(对应虚存地址为[0,9]); 第10条~第19条指令为第1页(对应虚存地址为[10,19]); … … … 第310条~第319条指令为第31页(对应虚存地址为[310,319])。 按以上方式,用户指令可组成32页。 (3)计算并输出下述各种算法在不同内存容量下的命中率(要为以下各种算法定义数据结构)。 ①先进先出的算法(FIFO); ②最近最少使用算法(LRU); ③最近最不经常使用算法(NUR/NRU/CLOCK)。 命中率=1-页面失效次数/页地址流长度 在本设计中,页地址流长度为320,页面失效次数为每次访问相应指令时,该指令所对应的页不在内存的次数。 (4)关于随机数产生办法,Linux/UNIX系统提供函数srand()和rand(),分别进行初始化和产生随机数。例如:srand()语句可初始化一个随机数: a[0]=10*rand()/32767*319+1, a[1]=10*rand()/32767*a[0]; … … … 语句可用来产生a[0]、a[1]、…中的随机数。 8、shell程序模拟设计 shell是UNIX系统的命令解释程序。Shell的基本功能是:命令解释执行、shell编程、系统环境设置、文件名替换、I/O重定向、连通管道建立。试按照shell程序的基本功能,利用UNIX系统提供的进程控制的系统调用,设计一个程序来模拟shell功能。要求至少要做到: 1)从终端键盘接收命令,若是合法,则执行之; 2)设置一条内部命令,比如print,用于显示被执行命令的返回状态和它自己的参数; 3)实现shell命令替换。 9、Windows文件系统分析 在Linux系统下,使用与文件相关的系统调用实现对物理设备文件的读写,参照Linux系统源代码,对不同介质上的FAT格式文件系统进行分析。要求在Linux环境下设计出C语言程序,实现以下功能: 1)分析DOS/Windows系统引导记录DBR(DOS Boot Record)和引导机制; 2)通过DBR中的BPB(BIOS Parameter Block)信息分析,构建相关信息的数据结构,比较FAT16、FAT32和VFAT等文件系统的区别与联系。 3)至少要实现对给出第一FAT入口文件的只读访问。 4)建议根据文件名读取文件。 10、UNIX/Linux文件系统分析 在Linux系统下,使用与文件相关的系统调用实现对物理设备文件的读写,参照Linux系统源代码以及Grub系统的源代码,对不同介质上的FAT格式文件系统进行分析。要求在Linux环境下设计出C语言程序,实现以下功能: 1)分析UNIX SysV/Linux系统引导记录的作用; 2)分析UNIX SysV/Linux的超级块及其结构,并建立相关数据结构,通过编程实现UNIX SysV/Linux文件系统内各部分的定位。 3)至少要实现对给定i节点文件的只读访问。 4)建议根据文件名读取文件。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

上山打老虎D

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值