操作系统实验指导 - 图文

更新时间:2023-09-21 03:46:01 阅读量: 自然科学 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

《计算机操作系统》

实验指导

苏州科技学院 电子与信息工程学院

计算机工程系

二O一四年九月

目录

Linux 平台

实验一 命令解释程序 ............................................................................................................................... 2 实验二 进程管理 ....................................................................................................................................... 3 实验三 进程间通信 ................................................................................................................................... 5 实验四 存储管理 ..................................................................................................................................... 14 实验五 设备管理 ..................................................................................................................................... 22 实验六 软盘I/O ...................................................................................................................................... 32 实验七 文件系统 ..................................................................................................................................... 41

Windows 平台

实验一 进程同步 ..................................................................................................................................... 42 实验二 内存管理实验 ............................................................................................................................. 44 实验三 快速文件系统实习 ..................................................................................................................... 46 实验四 进程之间通信 ............................................................................................................................. 47 实验五 Windows 应用程序与动态链接库 ............................................................................................ 49 实验六 WDM驱动程序开发 .................................................................................................................. 50

1

实验一 命令解释程序

实验名称: 命令解释程序 实验项目性质:设计性 所涉及课程:操作系统 计划学时:2 承担实验室:计算机实验室 实验环境要求:Redhat Linux 适用专业: 计算机科学与技术 一、实验目的 1、通过本实验熟悉UNIX/LINUX操作系统及C语言。 2、熟悉系统调用的编程方法。 二、实验预备内容 查阅实验中相关函数调用的用法(可用man命令): gets, strcspn, strncpy, strcmp, system等

三、实验内容 利用C语言编写一个微型命令解释程序minishell.c,该程序可接收并解释以下命令: (1) dir 列出当前目录 (2) cop file1 file2 拷贝文件 (3) era filename 删除文件 (4) disp string 显示字符串 (5) end 结束,退出 要求: (1)检查命令的合法性,如果有错误,显示出错信息,等待重新输入; (2)命令前后有空格示为合法命令。

四、示例程序minishell.c

//文 件 名 minishell.cpp

//功 能 小型SHELL命令解释程序 //开发环境

#define true 1 #define flase 0

#include #include #include

void main() { char cmdl[80];

? char *scwt[]={\ static int cmdnum=3; //可用的命令数 char cmd[80];

2

}

int j , n;

while(true) { printf(\ gets(cmdl); //取命令行输入 }

n=strcspn(cmdl,\//取命令命令部分 if (n>0||strlen(cmdl)>0)

{ strnexicpy(cmd,cmdl,n); cmd[n]='\\0'; for(j=0;j

实验二 进程管理

实验名称: 进程管理 实验项目性质:验证性 所涉及课程:操作系统 计划学时:2 承担实验室:计算机实验室 实验环境要求:Redhat Linux 适用专业: 计算机科学与技术

一、实验目的 1、加深对进程概念的理解,明确进程和程序的区别。 2、进一步认识并发执行的实质。 3、分析进程竞争资源的现象,学习解决进程互斥的方法。 二、实验预备内容 1、阅读Linux 的sched.h源代码文件,加深对进程管理概念的理解。 2、阅读Linux 的fork.c源代码文件,分析进程的创建过程。 3、查阅系统调用fork(), lockf(),exit(), wait() , sleep()等的用法。 三、实验内容

3

1、进程的创建 编写一段程序,使用系统调用fork()创建两个子进程。让父进程显示字符串‘Parent:’;两个子进程分别显示字符串‘Child1:’和‘Child2:’。多次运行此程序,观察屏幕显示的结果,并分析原因。 2、进程控制 修改已编写的程序,将输出多次重复的一句话,观察程序执行时在屏幕上显示的结果,并分析原因。

若在程序中使用系统调用lockf()来给每一个进程加锁,可以实现进程之间的互斥,观察屏幕显示的结果,并分析原因。

四、函数调用示例 1、系统调用fork()

原型: #include #include pid_t fork(void);

返回值:子进程中为0,父进程中为子进程I D,出错为-1 由fork创建的新进程被称为子进程( child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。将子进程I D返回给父进程的理由是:因为一个进程的子进程可以多于一个,所以没有一个函数使一个进程可以获得其所有子进程的进程ID。fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID (进程ID 0总是由交换进程使用,所以一个子进程的进程ID不可能为0 )。 子进程和父进程继续执行fork之后的指令。子进程是父进程的复制品。例如,子进程获得父进程数据空间、堆和栈的复制品。注意,这是子进程所拥有的拷贝。父、子进程并不共享这些存储空间部分。如果正文段是只读的,则父、子进程共享正文段。 2、fork()调用示例

#include main(){ int p1,p2; while ((p1=fork())==-1); if (p1==0) //是子进程? putchar('b'); else //父进程 { putchar('a');

} }

4

实验三 进程间通信

实验名称: 进程间通信 实验项目性质:综合性 所涉及课程:操作系统 计划学时:4 承担实验室:计算机实验室 实验环境要求:Redhat Linux 适用专业: 计算机科学与技术

一、实验目的 1、了解和熟悉Linux支持的消息通信机制、管道道通信、共享存储区机制及信息量机制。

2、掌握利用Linux系统的进程通信机制(IPC)实现进程间交换数据的方法。

二、实验预备内容

1、阅读Linux系统的msg.c、sem.c和shm.c等源码文件,熟悉Linux的三种通信机制。 2、查阅系统调用pipe(),write(),read(),exit(), wait() , sleep()等的用法。

三、实验内容 1、进程通信 编写一段程序,实现进程的管道通信。 使用系统调用pipe()建立一条管道线:两个子进程P1和P2分别向管道各写一句话: Child 1 is sending a message! Child 2 is sending a message!

父进程则从管道中读出来自两个了进程的信息,显示在屏幕上。

要求父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息。(可以通过sleep()将自身进入睡眠)

2、 消息的创建,发送和接收. (1) 使用系统调用msgget(), msgsnd(), msgsrv()及msgctl()编制一长度为1K的消息(如个人通信录信息)的发送和接收程序.

① 观察上面程序,说明控制消息队列系统调用msgctl() ② 共享存储区的发送和接收。

(2) 使用共享存储区相关的系统调用 shmget(),shmat(),sgmdt(),shmctl(),编制一个与上述功能相同的程序.

(3) 比较上述两种消息通信机制中数据传输的时间。 四、函数调用示例 1、管道

1)系统调用pipe() 原型:int pipe( int fd[2] ); 返回值:如果系统调用成功,返回0 如果系统调用失败返回- 1:errno = EMFILE (没有空闲的文件描述符) EMFILE (系统文件表已满) EFAULT (fd数组无效) 参数是一个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符。创建一个管道之后,一般情况下进程将产生一个新的进程。注意fd[0] 用于读取管道,fd[1] 用于写入管道。 一旦创建了管道,就可以创建一个新的子进程:

5

2)pipe()调用示例

#include main() {

int id,fd[2];

char buf[50],s[50]; pipe(fd);

while ((id=fork())==-1); if (id==0) {

sprintf(buf,\ write(fd[1],buf,50); exit(0); } else {

wait(0);

read(fd[0],s,50); printf(\ exit(0); }

}

2、共享内存相关

1)系统调用:shmget( ) ;

原型:int shmget ( key_t key, int size, int shmflg ); 返回值:如果成功,返回共享内存段标识符。

如果失败,则返回- 1:errno = EINVAL (无效的内存段大小) EEXIST (内存段已经存在,无法创建)

EIDRM (内存段已经被删除)

ENOENT (内存段不存在) EACCES (权限不够) ENOMEM (没有足够的内存来创建内存段)

系统调用shmget() 中的第一个参数是关键字值(它是用系统调用ftok( )返回的)。其他的操作都要依据shmflg中的命令进行。 IPC_CREAT 如果系统内核中没有共享的内存段,则创建一个共享的内存段。 IPC_EXCL 当和IPC_CREAT一同使用时,如果共享内存段已经存在,则调用失败。 当IPC_CREAT单独使用时,系统调用shmget()要么返回一个新创建的共享内存段的标识符,要么返回一个已经存在的共享内存段的关键字值。如果IPC_EXCL和IPC_CREAT一同使用,则要么系统调用新创建一个共享的内存段,要么返回一个错误值- 1。IPC_EXCL单独使用没有意义。

2)系统调用shmat()

原型:int shmat ( int shmid, char *shmaddr, int shmflg);

返回值:如果成功,则返回共享内存段连接到进程中的地址。 如果失败,则返回- 1:errno=EINVAL(无效的IPCID值或者无效的地址)

ENOMEM (没有足够的内存) EACCES (存取权限不够)

6

如果参数addr的值为0,那么系统内核则试图找出一个没有映射的内存区域。推荐使用这种方法。可指定一个地址,但这通常是为了加快对硬件设备的存取,或者解决和其他程序的冲突。 3)系统调用:shmctl() ;

原型:int shmctl ( int shmqid, int cmd, struct shmid_ds *buf ); 返回值: 0 ,如果成功。

- 1,如果失败:errno = EACCES (没有读的权限,同时命令是IPC_ STAT )

EFAULT (buf指向的地址无效,同时命令是IPC_SET

和IPC_STAT )

EIDRM (内存段被移走) EINVAL (shmqid 无效) EPERM (使用IPC_SET 或者IPC_RMID 命令,但调用

进程没有写的权限)

参数cmd 操作命令: IPC_STAT 读取一个内存段的数据结构shmid_ds,并将它存储在b u f参数指向的地址中。 IPC_SET 设置内存段的数据结构shmid_ds中的元素ipc_perm的值。从参数buf中得到要设置的值。 IPC_RMID 标志内存段为移走。 命令IPC_RMID并不真正从系统内核中移走共享的内存段,而是把内存段标记为可移除。进程调用系统调用shmdt()脱离一个共享的内存段。

4)系统调用:shmdt();

调用原型:int shmdt ( char *shmaddr );

返回值:如果失败,则返回- 1:errno = EINVAL (无效的连接地址) 当一个进程不在需要共享的内存段时,它将会把内存段从其地址空间中脱离。但这不等于将共享内存段从系统内核中移走。当进程脱离成功后,数据结构shmid_ds中元素shm_nattch将减1。当此数值减为0以后,系统内核将物理上把内存段从系统内核中移走。 用共享内存的实例: (1) 将字符串写入到内存段中

(2) 从内存段中读取字符串 (3) 改变内存段的权限 (4) 删除内存段 5)使用示例shmtool.c

#include #include #include #include #define SEGSIZE 100

main(int argc, char *argv[]) { key_t key; int shmid, cntr; char *segptr; if(argc == 1) usage(); /* Create unique key via call to ftok() */ key = ftok(\ /* Open the shared memory segment - create if necessary */ if((shmid = shmget(key, SEGSIZE, IPC_CREAT|IPC_EXCL|0666)) == -1)

7

{ printf(\ /* Segment probably already exists - try as a client */ if((shmid = shmget(key, SEGSIZE, 0)) == -1) { perror(\ exit(1); } } else { printf(\ } /* Attach (map) the shared memory segment into the current process */ if((segptr = shmat(shmid, 0, 0)) == -1) { perror(\ exit(1); } switch(tolower(argv[1][0])) { case 'w': writeshm(shmid, segptr, argv[2]); break; case 'r': readshm(shmid, segptr); break; case 'd': removeshm(shmid); break; case 'm': changemode(shmid, argv[2]); break; default: usage(); } }

writeshm(int shmid, char *segptr, char *text) { strcpy(segptr, text); printf(\ }

readshm(int shmid, char *segptr) { printf(\}

removeshm(int shmid)

8

{ shmctl(shmid, IPC_RMID, 0); printf(\}

changemode(int shmid, char *mode) { struct shmid_ds myshmds; /* Get current values for internal data structure */ shmctl(shmid, IPC_STAT, &myshmds); /* Display old permissions */ printf(\ /* Convert and load the mode */ sscanf(mode, \ shmctl(shmid, IPC_SET, &myshmds); printf(\}

usage() { fprintf(stderr, \ fprintf(stderr, \ fprintf(stderr, \ fprintf(stderr, \ fprintf(stderr, \ exit(1); }

3、消息机制相关

1)消息缓冲区 数据结构msgbuf是消息数据的模板。虽然此数据结构需要用户自己定义,但了解系统中有这样一个数据结构是十分重要的。在linux/msg.h中,此数据结构是这样定义的: /* message buffer for msgsnd and msgrcv calls */ struct msgbuf { long mtype; /* type of message */ char mtext[1]; /* message text */ }; 在数据结构msgbuf中共有两个元素: mtype 指消息的类型,它由一个整数来代表,并且,它只能是整数。 mtext 是消息数据本身。 因为程序员自己可以重新定义此数据结构。请看下面重新定义的例子: struct my_msgbuf { long mtype; /* Message type */ long request_id; /* Request identifier */ struct client info; /* Client information structure */ }; 在Linux系统中,这是在linux/msg.h中定义的:

9

#define MSGMAX 4056 /* <= 4056 */ /* max size of message (bytes) */ 消息的最大的长度是4056个字节,其中包括mtype,它占用4个字节的长度。

2)系统调用msgget() 如果希望创建一个新的消息队列,或者希望存取一个已经存在的消息队列,你可以使用系统调用msgget( )。

系统调用: msgget();

原型: int msgget ( key_t key, int msgflg ); 返回值:如果成功,返回消息队列标识符 如果失败,则返回- 1:errno =EACCESS (权限不允许) EEXIST (队列已经存在,无法创建)

EIDRM (队列标志为删除)

ENOENT (队列不存在)

ENOMEM (创建队列时内存不够)

ENOSPC (超出最大队列限制) 系统调用msgget()中的第一个参数是关键字值(通常是由ftok( )返回的)。然后此关键字值将会和其他已经存在于系统内核中的关键字值比较。这时,打开和存取操作是和参数msgflg中的内容相关的。

参数msgflg: IPC_CREAT 如果内核中没有此队列,则创建它。 IPC_EXCL 当和IPC_CREAT一起使用时,如果队列已经存在,则失败。

如果单独使用IPC_CREAT,则msgget( )要么返回一个新创建的消息队列的标识符,要么返回具有相同关键字值的队列的标识符。如果IPC_EXCL 和IPC_CREAT一起使用,则msgget()要么创建一个新的消息队列,要么如果队列已经存在则返回一个失败值- 1。IPC_EXCL单独使用是没有用处的。

3)系统调用: msgsnd();

原型: int msgsnd ( int msqid, struct msgbuf *msgp, int msgsz, int msgflg ); 返回值:如果成功, 0。 如果失败, -1 :errno = EAGAIN (队列已满,并且使用了IPC_NOWAIT ) EACCES (没有写的权限) EFAULT (msgp 地址无效)

EIDRM (消息队列已经删除) EINTR (当等待写操作时,收到一个信号) EINVAL (无效的消息队列标识符,非正数

的消息类型,或者无效的消息长度)

ENOMEM (没有足够的内存复制消息缓冲区

第一个参数是消息队列标识符,它是由系统调用msgget返回的。第二个参数是msgp,是指向消息缓冲区的指针。参数msgsz中包含的是消息的字节大小,但不包括消息类型的长度( 4个字节)。 参数msgflg可以设置为0(此时为忽略此参数),或者使用PC_NOWAIT。如果消息队列已满,那么此消息则不会写入到消息队列中,控制将返回到调用进程中。如果没有指明,调用进程将会挂起,直到消息可以写入到队列中。

4)系统调用: msgrcv( ) ;

原型: int msgrcv ( int msqid, struct msgbuf *msgp, int msgsz, long mtype, int msgflg ); 返回值:如果成功,则返回复制到消息缓冲区的字节数。 如果失败,则返回- 1: errno = E2BIG (消息的长度大于msgsz ,没有MSG_NOERROR ) EACCES (没有读的权限)

EFAULT (msgp 指向的地址是无效的)

10

EIDRM (队列已经被删除) EINTR (被信号中断)

EINVAL (msgqid 无效, 或者msgsz 小于0 )

ENOMSG (使用IPC_NOWAIT,

同时队列中的消息无法满足要求)

第一个参数用来指定将要读取消息的队列。第二个参数代表要存储消息的消息缓冲区的地址。第三个参数是消息缓冲区的长度,不包括mtype的长度,它可以按照如下的方法计算:

msgsz = sizeof(struct mymsgbuf) - sizeof(long);

第四个参数是要从消息队列中读取的消息的类型。如果此参数的值为0,那么队列中最长时间的一条消息将返回,而不论其类型是什么。

5)系统调用: msgctl( ) ;

调用原型: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf ); 返回: 0 ,如果成功。 1,如果失败:errno = EACCES (没有读的权限同时cmd 是IPC_STAT ) EFAULT (buf 指向的地址无效)

EIDRM (在读取中队列被删除) EINVAL (msgqid无效, 或者msgsz 小于0 )

EPERM (IPC_SET或者IPC_RMID 命令被使用,

但调用程序没有写的权限)

参数 cmd : IPC_STAT 读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中。 IPC_SET 设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。

这个值取自buf参数。

IPC_RMID 从系统内核中移走消息队列。 6)使用示例

(1) message.h

/* message.h*/

#ifndef MESSAGE_H #define MESSAGE_H struct mymsgbuf { long mtype; char mtext[256]; }; #endif

(2) shm_server.c

/*shm_server.c*/ #include #include #include #include #include #include #include \

11

int msqid=-1;

void sig_handle(int signo) {/*软中断*/ if (msqid!=-1) msgctl(msqid,IPC_RMID,NULL); printf(\ exit(0); }

int main() {

struct mymsgbuf msgbuf; int left,right; char c;

int length;

if ((msqid=msgget(999,0666))!=-1) { msgctl(msqid,IPC_RMID,NULL); }

if ((msqid=msgget(999,IPC_CREAT|0666))==-1) { printf(\ exit(1); }

signal(SIGINT,sig_handle); /*LINUX置软中断—CTRL-D*/ for (;;) {

if (msgrcv(msqid,&msgbuf,256,1L,0)==-1) {

printf(\ exit(1); }

length=strlen(msgbuf.mtext); left=0;

right=length-1; while(left

c=msgbuf.mtext[left];

msgbuf.mtext[left]=msgbuf.mtext[right]; msgbuf.mtext[right]=c; left++; right--; }

12

msgbuf.mtext[length]='\\0'; msgbuf.mtype=2;

if (msgsnd(msqid,&msgbuf,256,0)==-1) { printf(\ exit(1); } }

msgctl(msqid,IPC_RMID,NULL); exit(0); }

(3) msg_client.c

/*msg_client.c*/ #include #include #include #include #include #include \

int main() { struct mymsgbuf msgbuf; int msqid; if ((msqid=msgget(999,0666))==-1) {

printf(\ is not running\\n\ exit(1); } printf(\ scanf(\ msgbuf.mtype=1;

if (msgsnd(msqid,&msgbuf,256,0)==-1) { printf(\ exit(1); }

if (msgrcv(msqid,&msgbuf,256,2L,0)==-1) { printf(\ exit(1); }

printf(\

13

exit(0); }

实验四 存储管理

实验名称: 存储管理 实验项目性质:设计性 所涉及课程:操作系统 计划学时:2 承担实验室:计算机实验室 实验环境要求:Redhat Linux 适用专业: 计算机科学与技术

一、实验目的

1、了解虚拟存储技术的特点,掌握请求页式存储管理的主要页面置换算法原理。 2、掌握请求页式存储管理中页面置换算法的模拟设计方法。

二、实验内容

设计一个虚拟存储区和内存工作区,并使用下述方法计算访问命中率。 ①先进先出的算法(FIFO); ②最近最少少使用算法(LRR);

③最佳淘汰算法(OPT):选淘汰最不常用的页地址; ④最少访问页面算法(LFR);

⑤最近最不经常使用算法(NUR). (其中③④为选择内容) 命中率= 1 - 页面失效次数 / 页地址流长度

三、实验指导

1、通过随机数产生一个指令序列,共320条指令。指令的地址按下述原则生成: ①50%的指令是顺序执行的;

②25%的指令是均匀分布在前地址部分; ③25%的指令是均匀分布在后地址部分。 具体的实施方法是:

①在[1,319]指令地址之间随机选取一起点m;

②顺序执行一条指令,即执行地址为m十1的指令;

③在前地址[0,m十1]中随机选取一条指令并执行,该指令的地址为 m’; ④顺序执行一条指令,其地址为m‘+1;

⑤在后地址[m’+2,319]中随机选取一条指令并执行; ③重复上述步骤①~⑤,直到执行320次指令。 2、将指令序列变换成为页地址流 设:①页面大小为IK;

②用户内存容量为4页到32页; ③用户虚存容量为32K。

在用户虚存中,按每K存放10条指令排列虚存地址,即320条指令在虚存中的存放方 式为:

14

第0条~第9条指令为第0页(对应虚存地址为[0,9]); 第10条~第19条指令为第1页(对应虚存地址为[10,19]);

第 310条~第 319条指令为第 31页(对应虚存地址为[310,319])。 按以上方式,用户指令可组成32页。

3、按实验要求计算并输出各种算法在不同内存容量下的命中率。 在本实验中,页地址流长度为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]中的随机数(rand()产生一个介于0—32767之间的整数)。 5、相关定义

(1)数据结构 1)、页面类型 typedef struct{ int pn,pfn,counter,time; }pl_type;

其中pn为页号,pfn为面号,counter为一个周期内访问该页面次数,time为访问时间。 2)、页面控制结构 struct pfc_struct {

int pn, pfn;

struct pfc_struct *next; }

typedef struct pfc_struct pfc_type;

pfc_type pfc[total_vp], *freepf_head, *busypf_head; pfc_type *busypf_tail;

其中pfc[total_vp]为定义用户进程虚页控制结构, *freepf_head为空页面头的指针, *busypf_head为忙页面的头指针, *busypf_tail为忙页面的尾指针。 (2)函数定义

1) void initialize( ):初始化函数,给每个相关的页面赋值。 2) void FIFO ( ):计算使用FIFO()算法时的命中率。 3) void LRU( ) :计算使用LRU()算法时的命中率。 4) void OPT( ) :计算使用OPT()算法时的命中率。 5) void LFU( ) :计算使用LFU()算法时的命中率。 6) void NUR( ) :计算使用NUR()算法时的命中率。 (3)变量定义

1) int a[total_instruction]:指令流数组。

2) int page[total_instruction]:每条指令所属页号。

15

3) int offset[total_instruction]:每页装入10条指令后取模运算页号偏移值。 4) int total_pf:用户进程的内存页面数。 5) int diseffect:页面失效次数。

6、程序流程图〈略〉 7、示例程序 #define TRUE 1 #define FALSE 0 #define INVALID -1 #define null 0

#define total_instruction 320 /*指令流长*/ #define total_vp 32 /*虚页长*/ #define clear_period 50 /*清零周期*/

typedef struct{ /*页面结构*/ int pn,pfn,counter,time; }pl_type;

pl_type pl[total_vp]; /*页面数组*/ struct pfc_struct{ /*页面控制结构*/ int pn,pfn; struct pfc_struct *next; };

typedef struct pfc_struct pfc_type;

pfc_type pfc[total_vp],*freepf_head,*busypf_head,*busypf_tail;

int diseffect,a[total_instruction];

int page[total_instruction], offset[total_instruction]; void initialize(int total_pf); void FIFO(int total_pf); void LRU(int total_pf); void OPT(int total_pf); void LFU(int total_pf); void NUR(int total_pf);

#include #include #include main() { int S,i; srand( (unsigned)time( NULL ) ); /* srand(getpid()*10);*/ /*由于每次运行时进程号不同,故可用来作为初始化随机数的\种子\ S=(float)319*rand()/32767+1;

16

for(i=0;i

//void FIFO(total_pf) /*FIFO(First In First Out) ALOGRITHM*/ //int total_pf; /*用户进程的内存页面数*/ void FIFO(int total_pf) //int total_pf; { int i; pfc_type *p; initialize(total_pf); busypf_head=busypf_tail=NULL; for (i=0;inext; pl[busypf_head->pn].pfn=INVALID; freepf_head=busypf_head; freepf_head->next=NULL;

17

busypf_head=p; } p=freepf_head->next; freepf_head->next=NULL; freepf_head->pn=page[i]; pl[page[i]].pfn=freepf_head->pfn; if (busypf_tail==NULL) busypf_head=busypf_tail=freepf_head; else { busypf_tail->next=freepf_head; busypf_tail=freepf_head; } freepf_head=p; } } printf(\}

void LRU(int total_pf) //int total_pf; { int min,minj,i,j,present_time; initialize(total_pf); present_time=0; for(i=0;ipl[j].time && pl[j].pfn!=INVALID) { min=pl[j].time; minj=j; } freepf_head=&pfc[pl[minj].pfn]; pl[min].pfn=INVALID; pl[min].time=-1; freepf_head->next=NULL; }

18

pl[page[i]].pfn=freepf_head->pfn; pl[page[i]].time=present_time; freepf_head=freepf_head->next; } else pl[page[i]].time=present_time; present_time++; } printf(\}

void NUR(int total_pf) //int total_pf; { int i,j,dp,cont_flag,old_dp; // pfc_type *t; initialize(total_pf); dp=0; for(i=0;inext=NULL; }

19

} SYSTEM_INFO, *LPSYSTEM_INFO;

函数VOID GlobalMemoryStatus (LPMEMORYSTATUS lpBuffer); 数据结构MEMORYSTATUS 定义如下: typedef struct _ MEMORYSTATUS {

DWORD dwLength; DWORD dwMemoryLoad; DWORD dwTotalPhys; DWORD dwAvailPhys; DWORD dwTotalPageFile; DWORD dwAvailPageFile; DWORD dwTotalVirtual; DWORD dwAvailVirtual;

} MEMORYSTATUS, * LPMEMORYSTATUS;

函数DWORD VirtualQuery ( LPCVOLD lpAddress,PMEMORY_BASIC_INFORMATION lpBuffer, DWORD dwLength);

主要数据结构MEMORY_BASIC_INFORMATION 定义如下: typedef struct _ MEMORY_BASIC_INFORMATION {

PVOID BaseAddress; PVOID AllocationBase; DWORD AllocationProtect; DWORD RegionSize; DWORD State; DWORD Protect; DWORD Type;

} MEMORY_BASIC_INFORMATION;

typedef MEMORY_BASIC_INFORMATION * PMEMORY_BASIC_INFORMATION;

还有一些函数,例如VirtualAlloc,VirtualAllocEx,VirtualFree 和VirtualFreeEx 等,用于虚拟内存的管理,详情请见Microsoft 的Win32 API Reference Manual。

四、实验内容

使用这些API 函数,编写一个包含两个线程的进程,一个线程用于模拟内存分配活动,一个线程用于跟踪第一个线程的内存行为。模拟内存活动的线程可以从一个文件中读出要进 行的内存操作,每个内存操作包含如下内容:

??时间:开始执行的时间; ??块数:分配内存的粒度;

??操作:包括保留一个区域、提交一个区域、释放一个区域、回收一个区域以及锁与解锁一个区域;可以将这些操作编号,存放于文件中。

??大小:指块的大小;

??访问权限:共五种PAGE_READONLY、PAGE_READWRITE、PAGE_EXCUTE、

PAGE_EXECUTE_READ 和PAGE_ EXECUTE_READWRITE。可以将这些权限编号,存放于文件中。

跟踪线程将页面大小、已使用的地址范围、物理内存总量以及虚拟内存总量等信息显示出来。

45

实验三 快速文件系统实习

一、基本知识介绍

众所周知,CPU 是整个计算机系统中运算速度最快的部分,而外部设备是最慢的部分,它们之间存在着很大的差别。然而,CPU 却时时刻刻可能要求访问外设。如果CPU 的每次操作都必须等待外设完成,那么CPU 宝贵的运行时间就会大大浪费。随着现代计算机技术的发展,大多数现代操作系统都对这个问题进行了处理。下面就介绍两种Windows2000 中解决这个不匹配问题的方法:高速缓存和异步传输。

1 文件高速缓存

文件高速缓存是CPU 访问外设的一个“中间设备”。说是设备,其实它不是真正物理上的“设备”,而是一种核心级内存映象机制。由于它被设置在内存中,因此速度非常快,可以部分解决CPU 与硬盘速度差异的问题。文件系统的驱动程序通过调用“高速缓存管理程序”来使用文件高速缓存,然后高速缓存管理程序执行高速缓存的处理工作。

文件高速缓存的原理是:假设一个进程读了文件的第一个字节,它常常会按照顺序读第二个、第三个字节,一直到读出所有的字节。利用这个原理可以进行“预取”,也就是说,在进程没有要求读磁盘之前就先把文件读出来了并放到高速缓存中。这样,当进程请求访盘时,高速缓存可以快速地把已经取到内存中的文件内容直接送给进程使用。从而,大大加速了访盘速度。另外,由于一个文件可能会被多

次读入,因此可以在第一次读入后,将文件数据保存在高速缓存中。这样,下次再读时就不必再从硬盘而可以从缓存中读取。利用LRU(Least Recently Used,最近最少使用)的原则,可以将不常使用的文件从缓存中删除以节省高速缓存空间。

另外,文件高速缓存还有一个“事后写”的机制。具体地讲,如果一个进程要求写磁盘,它首先把要写的内容交给高速缓存。而高速缓存并不马上把它写到磁盘上,而是寻找CPU 空闲的时间来进行写操作。这样,要写磁盘的进程就可以不必等待磁盘写完毕以后再继续工作,这也就节省了整个进程的执行时间。这里需要说明的是,如果有另外一个进程要访问还没有被写入磁盘的文件时,高速缓存管理程序

可以使这个进程直接读高速缓存里面新的即将要写入的文件内容,而不是磁盘上的旧内容,从而保证了文件内容的一致性。

2.异步传输

与文件高速缓存不同,文件的异步传输是一种改变指令执行顺序的机制。在以往的操作系统中,指令都是顺序执行的,下一条指令必须在上一条指令执行完毕后才能执行。因此,如果CPU 遇到一条访盘指令,那么它就必须等待缓慢的磁盘访问结束以后才能进行后续的工作。如果它后面的指令并不依赖于访盘操作时,这个等待就显得很没有必要。Windows 2000 中使用了一种异步传输的机制来解决这个问题。它通过设置打开文件时的一个标志位(见后)来使进程不等待读写文件操作而继续执行。当后续指令必须用到磁盘访问的结果数据时,它再通过一条Wait 指令进行等待。这样,在访盘指令和等待指令之间的指令就可以与磁盘访问同时进行了,从而大大加快了系统的整个速度。

二、相关的API 函数

(这里只是简单说明,具体函数应用请参见MSDN 2000) CreateFile 函数说明:

在MSDN 中,CreateFile 说明如下:

HANDLE CrateFile ( . . . .

46

DWORD dwFlagsAndAttributes , // file attributes . . . .

);

dwFlagAndAttributes 参数可能用到的几个值:

FILE_FLAG_NO_BUFFERING:没有文件高速缓存 FLAG_FLAG_SEQUENTIAL_SCAN:使用文件高速缓存 FILE_FLAG_OVERLAPPED:使用异步传输

其中FILE_FLAG_NO_BUFFERING| FILE_FLAG_OVERLAPPED(两个标志的“或”)是用在异步传输实验中的参数,系统将提供尽可能最好的异步传输性能。

三、实验内容

设计一个函数:

int filter(char source,char *sink ,int f , char *fArg) 其中:

source:源文件,即从哪个文件读。 sink:目标文件,即写到哪个文件。

f:一个对文件的操作(可以指定任何操作,如:对每个字节都加一的操作)。 fArg:f 操作需要的参数(如果需要的话)。

这个函数的作用是从source 文件中读数据,通过操作f 后,将结果写入sink 文件中去。

整个过程需要三个部分完成:

(1)建立N 个(0

(2)使用CreateFile 的FILE_FLAG_SEQUENTIAL_SCAN 标志位建立文件,这样系统会给你的文件访问加上文件高速缓存。用(1)中同样的方法计算平均时间,并与(1)中结果做比较。 (3) 使用FILE_FLAG_OVERLAPPED 标志位,并将ReadFile 和WriteFile 与wait函数一起使用,这样系统会实现异步传输。你可以改变程序中语句的顺序,并记录改变顺序对速度的影响。和(1)、(2)一样计算平均时间,并与(1)、(2)的结果进行比较。

实验四 进程之间通信

一、实验要求

请在属于两个不同进程的线程之间实现通话功能。假设这种通话是非对称的,一个线程作为发起者,另一个作为接受者。发起者要建立一个会话时,它向接受者发出一个创建虚链路的请求(即发起者作为客户端,接收者作为服务器端)。每个进程都有一个控制台终端用于发送和接受消息,消息以符号>开头表示发出的消息,以<开头表示收到的消息。请使用WinSock 2.0 实现这个程序。

这个系统应该通过两个不同的进程演示,比如进程在不同的窗口以及不同的机器时无需改动源代码。系统的服务器端应该可以选择端口号,客户端在运行时确定端口号以及服务器的地址或者网络名称。

二、实验环境

47

当使用WinSock 包时,请确定链接了WinSock2.0 库和C 运行库,这需把wsock32.lib 和libc.lib 加入库列表。

三、实验内容

通过WinSock 编程可以实现互联网上的数据传输。只使用本机的的进程和线程实际上就可以编写和调试这样的程序了。一旦程序可以在本机运行(使用IP 地址),一般只需修改网络名称就可以使程序在网络上运行。

实习中,只需在控制台终端环境下键入hostname 命令就可以知道自己所使用的机器的主机名称。如果使用实验室的机器,主机名称可能会根据不同的会话而改变,可以在程序中通过调用gethostname 函数来获取相应的机器名。这个函数的声明如下:

struct HOSTENT FAR * gethostname( const char FAR * name); 传入ASCII 字符参数就可以获得指向HOSTENT 结构的指针。 struct hostent{

char FAR * h_name;

char FAR * FAR * h_aliases; short h_addrtype; short h_length;

char FAR * FAR * h_addr_list; };

查看联机文档了解HOSTENT 结构各字段的含义。

在填写sockadd_in 结构的字段时要注意一点。阅读以下的代码可能会有一定帮助。 LPHOSTENT host;

SOCKADDR_IN aServer; …

host = gethostbyname(serverHostName);

ZeroMemory(&aServer,sizeof(SOCKADDR_IN)); aServer.sin_family = AF_INET;

aServer.sin_port = htons((u_short)port);

CopyMemory(&aServer.sin_addr,host->h_addr_list[0],host->h_length);

这个作业的难点之一就是编写的程序既要处理网络上传来的消息,又要处理来自控制台的标准输入,处理的先后应该是取决于哪一个事件先发生的。这要求程序在socket 和stdin上进行无阻塞的操作。这方面的内容可以查阅一些相关的联机文档。(提示:可以在控制台终端使用CreateFile 函数。查阅MSDN 的“Console and Port I/O‖章节,其中对控制台I/O 下一些例程的介绍可能提供一些有价值的思路。)

48

实验五 Windows 应用程序与动态链接库

1.编写一个Wndows 应用程序,要求产生下图所示的窗口。在该窗口的菜单栏中一个Menu菜单,其中包含三个菜单项:Menu1、Menu2 和Exit。单击菜单项Menu1 在窗口的客户区显示―Hello from menu1‖,单击Menu2 在窗口的客户区显示―Hello from menu2‖, 单击Exit 退出程序。

2.编写一个DLL,其中含有两个函数Func1 和Func2 可供应用程序调用,这两个函数的功能均为返回一个字符串。Func1 返回的字符串是―Hello from Func1, this function was called from …‖,其中… 为调用该函数的应用程序的名称与路径,Func2 返回的字符串与次相似。

3.修改2 项创建的应用程序,使得当单击菜单项Menu1 时调用3 项创建的DLL 中的Func1,获得Func1 返回的字符串,并将其显示在窗口的客户区中,如下图所示。单击菜单项Menu2时调用Func2,并完成类似的操作。

要求使用C 编程,不允许使用MFC,以便体会Wndows 应用程序的消息机制。关于GUI 编程,请参考相关书籍。

49

pl[page[i]].pfn=freepf_head->pfn; freepf_head=freepf_head->next; } else pl[page[i]].counter=1; if (i%clear_period==0) for(j=0;j

void OPT(int total_pf) /*OPT(Optional Replacement) ALOGRITHM*/ //int total_pf; { int i,j,max,maxpage,d,dist[total_vp]; pfc_type *t; initialize(total_pf); for(i=0;i

20

freepf_head=&pfc[pl[maxpage].pfn]; freepf_head->next=NULL; pl[maxpage].pfn=INVALID; } pl[page[i]].pfn=freepf_head->pfn; freepf_head=freepf_head->next; } } printf(\}

void LFU(int total_pf) /*LFU(leat Frequently Used) ALOGRITHM*/ //int total_pf; { int i,j,min,minpage; pfc_type *t; initialize(total_pf); for(i=0;ipl[j].counter&&pl[j].pfn!=INVALID) { min=pl[j].counter; minpage=j; } pl[j].counter=0; } freepf_head=&pfc[pl[minpage].pfn]; pl[minpage].pfn=INVALID; freepf_head=freepf_head->next; } else pl[page[i]].counter++; } }

21

printf(\}

void initialize(int total_pf) /*初始化相关数据结构*/ //int total_pf; /*用户进程的内存页面数*/ { int i; diseffect=0; for(i=0;i

实验五 设备管理

实验名称: 设备管理 实验项目性质:设计性 所涉及课程:操作系统 计划学时:4 承担实验室:计算机实验室 实验环境要求:Redhat Linux 适用专业: 计算机科学与技术

一、实验目的 1、通过实验,进一步了解设备独立性的概念;

2、通过实验,掌握Linux下字符设备驱动程序的设计方法 3、掌握Linux下可装入模块的设计与实现方法。

二、实验内容

1、编写一个可读写的字符设备驱动程序,并作为可装入模块加载到系统中去。 2、设计相应的示例程序,在用户进程中使用该设备驱动程序进行字符数据的读写。

三、实验指导

22

有关Linux下驱动程序的设计方法及可装载模块的设计与实现方法见附录中的文档:《LINUX系统下的设备驱动程序设计》一文。

示例程序mychardev.c 创建一个只读的文件系统,它由以下函数组成: static int device_open()

/* 当进程试图打开设备文件时调用该函数*/

static void device_release()

/* 当一个进程要关闭这个设备时,该函数被调用,这个调用不允许失败.*/ static int device_read()

/* 当一进程要从已打开的设备文件读数据时该函数被调用.*/ static int device_write()

/* 当进程向这设备写(目前不支持)时调用本函数*/ int init_module()

/* 初始化模块--注册字符设备*/ void cleanup_module()

/* 清除内核模块 ,从/proc 注销相应文件 */

四、实验步骤: 1、编写用户设备驱动程序模块mychardev.c,编译该模块:

#gcc -D_ _KERNEL_ _ -DMODULE -O2 -g -Wall -c mychardev.c [

Redhat 9( 内核2.4.20 ) 编译方法为:

#gcc -D_ _KERNEL_ _ -DMODULE -O2 -g -Wall –I/usr/src/linux-2.4.20-8/include -c mychardev.c ]

2、加载设备驱动程序 mychardev.o: #insmod mychardev.o

内核将提示主设备号及设备文件名的建立方法。如果有提示内核版不一致的错误,加上参数 –f。可用lsmod 检查新模块是否装入及系统中安装的模块的情况,要卸载该模块,用 rmmod 模块名 (不要扩展名.o)。 3、建立设备文件节点(仅需一次) #cd /dev #mknod /dev/mychardev c 254 0

其中mychardev是设备驱动程序中用register_chrdev()注册的设备名,c表示字符设备,数字254是主设备号,它是在设备驱动程序安装时提示的数据,也可以从文件/proc/devices中获取主设备号(这里是254)。对同一个物理设备,可以用同一主设备号建立多个不同的逻辑设备,也就是我们还可以用mknod /dev/mydev c 254 0命令建立另一个逻辑名为mydev的设备文件,通过该文件去使用相同的物理设备。删除该结点用命令 rm /dev/mychardev 。

用ls命令查看/dev目录下的设备文件名的建立情况。也可查看/proc下proc文件系统记录的设备模块文件中modules中有无加载的模块(用法 : #cat /proc/modules)。

由于现在建立的是一个字符设备,因此可以用less 等命令查看文件的内容:

less /dev/mydev。

4、编制用户程序read_dev.c, 在用户进程中使用该设备(从设备中读取数据),反复运行,观察结果。 5、修改设备驱动程序中的write()函数,实现对设备的写操作;同时修改用户程序,在用户程序中对设备进行读写。

23

注意:设备驱动程序修改后,在重新装入前,应先把装入的驱动程序卸载。 6、卸载设备驱动程序 #rmmod mychardev

五、示例程序:一个只读的字符设备。 1、mychardev.c

/* mychardev.c 创建一个只读的文件系统 */ #ifndef MODULE #define MODULE #endif

//Redhat 9下 #include #include #include

#include /* printk() */ #include #include

/* 字符设备所需*/

#include

#include /* 为兼容后续版本*/ //#include #include #include #include #include //#include #include

#define SUCCESS 0

/* 设备定义 **********************/ /* 设备名, 它将出现在 /proc/devices */ #define DEVICE_NAME \

/* 该设备最大信息长度 */ #define BUF_LEN 80

/* 设备否打开?利用它防止当前进程使用同一设备*/ static int Device_Open = 0;

/* 提示信息 */

char Message[BUF_LEN];

/* 进程读取的信息的指针 */

24

char *Message_Ptr;

/* 当进程试图打开设备文件时调用该函数*/

static int device_open(struct inode *inode, struct file *file) {

static int counter = 0;

#ifdef DEBUG

printk (\#endif

/* 两个进程不得同时对同一设备操作.*/ if (Device_Open) return -EBUSY;

Device_Open++;

/* 初始化信息. */ sprintf(Message,

\ counter++);

/* 仅在输出信息的最大长度大于 BUF_LEN, (这儿是80)使用sprintf. * 注意不得超过缓冲区长度,尤其是在内核中!! */

Message_Ptr = Message;

/* 当文件打开时确信这个模块存在.*/ MOD_INC_USE_COUNT;

return SUCCESS; }

/* 当一个进程要关闭这个设备时,该函数被调用,这个调用不允许失败.*/ static int device_release(struct inode *inode, struct file *file) {

#ifdef DEBUG

printk (\#endif

Device_Open --;

/* 设备文件使用计数器减1*/ MOD_DEC_USE_COUNT; return 0; }

25

/* 当一进程要从已打开的设备文件读数据时该函数被调用.*/ static ssize_t device_read(struct file *file,

char *buffer, /* 获得填充数据的缓冲区*/ size_t length,loff_t *fops) /* 缓冲区中的数据长度

(绝对不能越界!) */ {

/* 已写入到缓冲区buffer中的确切字节数*/ int bytes_read = 0; char *ptr; int len=0; #ifdef DEBUG

printk(\ inode, file, buffer, length); #endif

/* 如果已位于信息尾部,返回0*/ if (*Message_Ptr == 0) return 0;

ptr=Message_Ptr; len=0 ;

/*取字符串的长度*/ while (*(ptr++)) len++;

if (length

copy_to_user(buffer,Message_Ptr,len); /* 准确地把数据送到缓冲区buffer*/

bytes_read=len;

#ifdef DEBUG

printk (\ bytes_read, length); #endif

/* 返回读取的实际字节数据 */ return bytes_read; }

/* 当进程向这设备写(目前不支持)时调用本函数*/ static ssize_t device_write(struct file *file, const char *buffer,

size_t length,loff_t *fops)

26

{

#ifdef DEBUG

printk (\ inode, file, buffer, length); #endif int len ;

if (length>80) len=80; else len=length;

copy_from_user(Message_Ptr,buffer,len); return -EINVAL; }

/* 模块定义 ********************** */

/* 设备的主设备号*/ static int Major;

/* 当进程要对创建的设备进行某些操作时,这个结构存放了要调用的函数的入口 * 这个结构有系统设备表的指针指向。NULL 表示未实现该功能。*/

struct file_operations Fops = { read: device_read, write: device_write, open: device_open, release:device_release /* a.k.a. close */ };

/* 初始化模块--注册字符设备 */ int init_module() {

/* 注册字符设备(至少一次) */ Major = register_chrdev(0,

DEVICE_NAME, &Fops);

/* 负值意味出错 */ if (Major < 0) {

printk (\ Major); return Major; }

printk (\

27

Major);

printk (\ printk (\ printk (\

return 0; }

/* 清除模块 --从 /proc 中注销合适的文件*/ void cleanup_module() {

int ret;

/* 注销设备 */

ret = unregister_chrdev(Major, DEVICE_NAME); /* 如果出错,报告错误*/ if (ret < 0)

printk(\}

/*在Redhat7.3以下版本请将其注释掉*/ MODULE_LICENSE(\

2、read_dev.c

/*read_dev.c 使用用户设备驱动程序的示例*/ #include #include main() {

int i,j,num; int f;

char buffer[80];

f=open(\

printf(\ scanf(\ i=read(f,buffer,num); j=0;

while (j

28

六、UNIX/LUNIX下设备驱动程序的基本结构

在UNIX系统里,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口,把设备映射为一个特殊的设备文件,用户程序可以象对其它文件一样对此设备文件进行操作。UNIX对硬件设备支持两个标准接口:块特别设备文件和字符特别设备文件,通过块(字符)特别 设备文件存取的设备称为块(字符)设备或具有块(字符)设备接口。设备由一个主设备号和一个次设备号标识。主设备号唯一标识了设备类型, 即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。次设备号仅由设备驱动程序解释,一般用于识别在若干可能的硬件设备中,I/O请求所涉及到的那个设备。

设备驱动程序可以分为三个主要组成部分: (1) 自动配置和初始化子程序,负责检测所要驱动的硬件设备是否存在和是否能正常工作。如果该设备正常,则对这个设备及其相关的、设备驱动程序需要的软件状态进行初始化。这部分驱动程序仅在初始化的时候被调用一次。

(2) 服务于I/O请求的子程序,又称为驱动程序的上半部分。调用这部分是由于系统调用的结果。这部分程序在执行的时候,系统仍认为是和进行调用 的进程属于同一个进程,只是由用户态变成了核心态,具有进行此系统调 用的用户程序的运行环境,因此可以在其中调用sleep()等与进程运行环 境有关的函数。

(3) 中断服务子程序,又称为驱动程序的下半部分。在UNIX系统中,并不是直接从中断向量表中调用设备驱动程序的中断服务子程序,而是由UNIX 系统来接收硬件中断,再由系统调用中断服务子程序。中断可以产生在任何一个进程运行的时候,因此在中断服务程序被调用的时候,不能依赖于任何进程的状态,也就不能调用任何与进程运行环境有关的函数。因为设备驱动程序一般支持同一类型的若干设备,所以一般在系统调用中断服务子程序的时候,都带有一个或多个参数,以唯一标识请求服务的设备。

在系统内部,I/O设备的存取通过一组固定的入口点来进行,这组入口点是由每个设备的设备驱动程序提供的。一般来说,字符型设备驱动程序能够提供如下几个入口点:

(1) open入口点。打开设备准备I/O操作。对字符特别设备文件进行打开操作,都会调用设备的open入口点。open子程序必须对将要进行的I/O操作做好必要的准备工作,如清除缓冲区等。如果设备是独占的,即同一时刻只能有一个程序访问此设备,则open子程序必须设置一些标志以表示设备处于忙状态。

(2) close入口点。关闭一个设备。当最后一次使用设备终结后,调用close 子程序。独占设备必须标记设备可再次使用。

(3) read入口点。从设备上读数据。对于有缓冲区的I/O操作,一般是从缓冲区里读数据。对字符特别设备文件进行读操作将调用read子程序。

(4) write入口点。往设备上写数据。对于有缓冲区的I/O操作,一般是把数据写入缓冲区里。对字符特别设备文件进行写操作将调用write子程序。

(5) ioctl入口点。执行读、写之外的操作。

(6) select入口点。检查设备,看数据是否可读或设备是否可用于写数据。select系统调用在检查与设备特别文件相关的文件描述符时使用select入口点。

如果设备驱动程序没有提供上述入口点中的某一个,系统会用缺省的子程序来代替。对于不同的系统,也还有一些其它的入口点。

在LINUX系统里,设备驱动程序所提供的这组入口点由一个结构来向系统进行说明,此结构定义为:

#include struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int);

29

ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); };

其中,struct inode提供了关于特别设备文件/dev/driver(假设此设备名为driver)的信息。

struct file主要用于与文件系统对应的设备驱动程序使用。当然,其它设备驱动程序也可以使用它。它提供关于被打开的文件的信息。

在结构file_operations里,指出了设备驱动程序所提供的入口点位置,分别是: (1) lseek,移动文件指针的位置,显然只能用于可以随机存取的设备。

(2) read,进行读操作,参数buf为存放读取结果的缓冲区,count为所要读取的数据长度。返回值为负表示读取操作发生错误,否则返回实际读取的字节数。对于字符型,要求读取的字节数和返回的实际读取字节数都必须是inode->i_blksize的的倍数。

(3) write,进行写操作,与read类似。

(4) readdir,取得下一个目录入口点,只有与文件系统相关的设备驱动程序才使用。

(5) selec,进行选择操作,如果驱动程序没有提供select入口,select操作将会认为设备已经准备好进行任何的I/O操作。

(6) ioctl,进行读、写以外的其它操作,参数cmd为自定义的的命令。

(7) mmap,用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用。

(8) open,打开设备准备进行I/O操作。返回0表示打开成功,返回负数表示失败。如果驱动程序没有提供open入口,则只要/dev/driver文件存在就认为打开成功。

(9) release,即close操作。

设备驱动程序所提供的入口点,在设备驱动程序初始化的时候向系统进行登记,以便系统在适当的时候调用。LINUX系统里,通过调用register_chrdev 向系统注册字符型设备驱动程序,使用完毕后用unregister_chrdev注销。register_chrdev/ unregister_chrdev定义为:

#include #include

int register_chrdev(unsigned int major, const char *name,

struct file_operations *fops);

int unregister_chrdev(unsigned int major, const char *name);

其中,major是为设备驱动程序向系统申请的主设备号,如果为0则系统为此驱动程序动态地分配一个主设备号。name是设备名。fops就是前面所说的对各个调用的入口点的说明。此函数返回0表示

30

成功。返回-EINVAL表示申请的主设备号非法,一般来说是主设备号大于系统所允许的最大设备号。返回-EBUSY表示所申请的主设备号正在被其它设备驱动程序使用。如果是动态分配主设备号成功,此函数将返回所分配的主设备号。如果register_chrdev操作成功,设备名就会出现在/proc/devices文件里。

初始化部分一般还负责给设备驱动程序申请系统资源,包括内存、中断、时钟、I/O端口等,这些资源也可以在open子程序或别的地方申请。在这些资源不用的时候,应该释放它们,以利于资源的共享。

在UNIX/LINUX系统里,对中断的处理是属于系统核心的部分,因此如果设备与系统之间以中断方式进行数据交换的话,就必须把该设备的驱动程序作为系统核心的一部分。设备驱动程序通过调用request_irq函数来申请中断,通过free_irq 来释放中断。

作为系统核心的一部分,设备驱动程序在申请和释放内存时不是调用malloc 和free,而代之以调用kmalloc和kfree,它们被定义为:

#include

void * kmalloc(unsigned int len, int priority); void kfree(void * obj);

参数len为希望申请的字节数,obj为要释放的内存指针。priority为分配内存操作的优先级,即在没有足够空闲内存时如何操作,一般用GFP_KERNEL。

在用户程序调用read 、write时,因为进程的运行状态由用户态变为核心态,地址空间也变为核心地址空间。而read、write中参数buf是指向用户程序的私有地址空间的,所以不能直接访问,必须通过上述两个系统函数来访问用户程序的私有地址空间。memcpy_fromfs由用户程序地址空间往核心地址空间复制,memcpy_tofs则反之。参数to为复制的目的指针,from为源指针,n 为要复制的字节数。新的内核则使用copy_to_user() copy_from_user(),用法见示例程序。

在设备驱动程序里,可以调用printk来打印一些调试信息,用法与printf 类似。printk打印的信息不仅出现在屏幕上,同时还记录在文件syslog里。

在LINUX里,除了直接修改系统核心的源代码,把设备驱动程序加进核心里以外,还可以把设备驱动程序作为可加载的模块,由系统管理员动态地加载它,使之成为核心的一部分。也可以由系统管理员把已加载地模块动态地卸载下来。

LINUX中,模块可以用C语言编写,用gcc编译成目标文件(不进行链接,作为*.o文件存在),为此需要在gcc命令行里加上-c的参数。在编译时,还应该在gcc的命令行里加上这样的参数:-D__KERNEL__ -DMODULE。由于在不链接时,gcc只允许一个输入文件,因此一个模块的所有部分都必须在一个文件里实现。 如: gcc -D__KENERL__ -DMODULE -O2 -g -Wall -c filename.c

编译好的模块*.o放在/lib/modules/xxxx/misc下(xxxx表示核心版本,如在核心版本为2.0.30时应该为/lib/modules/2.0.30/misc),然后用depmod -a 使此模块成为可加载模块(高版本的内核不需要)。模块用insmod命令加载,用rmmod命令来卸载,并可以用lsmod命令来查看所有已加载的模块的状态。

编写模块程序的时候,必须提供两个函数,一个是int init_module(void),供insmod在加载此模块的时候自动调用,负责进行设备驱动程序的初始化工作。init_module返回0以表示初始化成功,返回负数表示失败。另一个函数是void cleanup_module (void),在模块被卸载时调用,负责进行设备驱动程序的清除 工作。

在成功的向系统注册了设备驱动程序后(调用register_chrdev成功后),就可以用mknod命令来把设备映射为一个特别文件,其它程序使用这个设备的时候,只要对此特别文件进行操作就行了。

31

实验六 软盘I/O

实验名称: 软盘I/O 实验项目性质:设计性 所涉及课程:操作系统 计划学时:2 承担实验室:计算机实验室 实验环境要求:Redhat Linux 适用专业: 计算机科学与技术

一、实验目的 通过实验了解磁盘的物理组织结构,掌握通过用户态程序直接读写磁盘信息的方法。

二、实验内容

1、编写一个用于判定逻辑驱动器A 中磁盘的基本信息的函数,原型如下: Disk physicalDisk(char driveLetter);

2、编写一个用于读取指定磁盘扇区的信息的函数,原型如下; BOOL sectorRead(

Disk the Disk,

Unsigned logicalSectorNumber, Char *buffer

);

3、编写一个用于把指定磁盘扇区的信息输出到标准输出流中,原型如下: Void sectorDump(Disk theDisk, int logicalSectorNumber)。

输出格式参考调试工具Debug使用-d命令时的输出格式,即左边为每个扇区十六进制表示的数据,右侧为相应的字符,如果是不可以显示的则用‖.‖表示。 注:

函数physicalDisk 用于初始化磁盘,为随后的操作做好准备,参数driveLetter 代表磁盘驱动器,在其实现代码中使用API函数CreateFile 打开设备并获取磁盘的基本信息。

函数sectorRead 从指定的磁盘中读取一个给定扇区(logicalSectorNumber)的信息到一个缓冲区(buffer)中,如果读取操作成功,该函数应该返回TRUE。

函数sectorDump 调用函数sectorRead,然后把结果输出到stdout 中。可以考虑根据扇区的不同对得到的信息进行格式化输出,例如逻辑扇区0,1,10 和19 分别对应于引导扇区,FAT 表1,FAT 表2 和根目录。

三、实验步骤

整个实验需要一张MSDOS 格式的软盘,并且软盘上已经存放有一些文件。实验可按如下次序进行:

1. 实现函数physicalDisk。

2. 实现函数sectorRead 和segmentDump,获取磁盘的内容,可以得到任何给定扇区的16 进制输出。

3. 编写程序调用physicalDisk、sectorRead 和segmentDump 三个函数,验证其正确性。

四、实验指导

从早期的MSDOS 系统开始到现在的Windows2000/XP 系统,软盘一直都是广泛应用的辅助存储设备。Windows2000/XP 的文件管理系统可以包容多种不同的磁盘设备、文件系统和磁盘格式。Windows2000/XP 自己定义并且使用的文件系统是NTFS;对于软盘,Windows2000/XP 可使用

32

MSDOS 格式。

MSDOS 格式是一种比较早的文件系统格式,也叫FAT 格式,最初的FAT 设计只能管理最大为32M 的磁盘,关于FAT 格式的详细信息可以参考相关资料。

1、Linux 下通过访问设备节点文件/dev/fd0,实现对磁盘的读写。 示例代码如下: int fp,count; char buffer[512]; fp = fopen(―/dev/fd0‖,0); //以读方式打开设备

count = read(fp, buffer, 512); //读入逻辑扇区1,共512Byte至buffer,

//返回值为实际读取的字节数

//其他对buffer的处理代码 2、Win32下的相关API 函数:

CreateFile 函数用于打开一个设备,例如软盘,该设备可以以虚拟文件的方式访问,例如,如果要打开驱动器A 中的软盘并且按字节流的方式访问其内容,可以使用

CreateFile(

―\\\\\\\\.\\\\A:‖,

GENERIC_READ, FILE_SHARE_REDA, NULL,

OPEN_ALWAYS, 0,

NULL);

CeateFile 返回一个句柄,可以供其他函数如ReadFile 和WriteFile 等使用,可以在执行读写操作前通过调用SetFilePointer 函数确定读写的位置。DeviceIoControl 函数可以用于获取软盘的基本信息,以上函数的详细说明和使用示例请参阅MSDN。以下是一个简单的示例。 #include

#include // From the Win32 SDK \\Mstools\\Include #include /*

This code reads 19th sector from a Floppy disk and writes

the contents to a disk file named Sector.dat */

void main() {

HANDLE hFloppy, hFile; DWORD dwNotUsed;

// Disk file that will hold the Floppy sector data. hFile = CreateFile (\

GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

// For the purposes of this sample, drive A: is the Floppy

// drive. You can use hard disk.About more information you read MSDN. hFloppy = CreateFile (\

33

GENERIC_READ, FILE_SHARE_READ,

NULL, OPEN_ALWAYS,0 , NULL);

// If the drive A: was successfully opened, read sectors 1 // from it and write their contents out to a disk file. if (hFloppy != INVALID_HANDLE_VALUE) {

DISK_GEOMETRY dgFloppy;

// Get sector size of Floppy disk

DeviceIoControl (hFloppy, IOCTL_STORAGE_GET_MEDIA_TYPES , NULL, 0, &dgFloppy, sizeof(dgFloppy), &dwNotUsed, NULL); {

LPVOID lpSector;

DWORD dwSize = 1 * dgFloppy.BytesPerSector; // 1 sectors

// Allocate buffer to hold sectors from Floppy disk. lpSector = VirtualAlloc (NULL, dwSize,

MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); // Move to 19th sector for something interesting to read. SetFilePointer (hFloppy, dgFloppy.BytesPerSector * 19 , NULL, FILE_BEGIN);

// Read sectors from the Floppy disk and write them to a file. if (ReadFile (hFloppy, lpSector, dwSize, &dwNotUsed, NULL)) WriteFile (hFile, lpSector, dwSize, &dwNotUsed, NULL); VirtualFree (lpSector, 0, MEM_RELEASE); }

CloseHandle (hFloppy); CloseHandle (hFile); } }

附MS-DOS磁盘格式

34

INT13读入软盘的0面0道1扇,即引导扇区(Boot Area)其意义如下: BPB表:

保留扇区数 ━━━━━━━━┓ 每簇扇区数 ━━━━━━━┓ ┃ 每扇区字节数 ━━━━━┓ ┃ ┃ ━┻ ┻ ┻━ 0B75:0000 EB 3C 90 4D 53 44 4F 53-35 2E 30 00 02 08 01 00 0B75:0010 02 00 02 00 00 F8 97 00-39 00 0F 00 39 00 00 00 ┏ ┳━ ┳━┏ ┳━ ━┳ ━┳ ━┳ FAT表数 ┛ ┃ ┃ ┃ ┃ ┃ ┃ ┃ 根目录项数━ ┛ ┃ ┃ ┃ ┃ ┃ ┃ 逻辑扇区数━━━━┛ ┃ ┃ ┃ ┃ ┃ 磁盘介质说明━━━━━┛ ┃ ┃ ┃ ┃ FAT表占用扇区数━━━━━┛ ┃ ┃ ┃ 每道扇区数 ━━━━━━━━━━━┛ ┃ ┃ 磁头数 ━━━━━━━━━━━━━━━┛ ┃ 隐藏扇区数 ━━━━━━━━━━━━━━━━┛

0B75:0020 1F B2 04 00 80 00 29 D4-0D 17 18 4E 4F 20 4E 41 0B75:0030 4D 45 20 20 20 20 46 41-54 31 36 20 20 20 FA 33 ━┳━ ┳━ FAT(ASC源码) ━━━━━━┛ ┃ FAT每项位数(ASC源码) ━━━━━┛

根目录表占扇区数=根目录表项数×每项字节数÷每扇区字节数

系统占用扇区数=引导扇区数+FAT表数×FAT表占用扇区数+根目录表占扇区数 某簇第一逻辑扇区号=(簇号-2)?每簇扇区数+系统占用扇区数

35

36

37

38

39

40

实验七 文件系统

实验名称: 文件管理 实验项目性质:验证性 所涉及课程:操作系统 计划学时:2 承担实验室:计算机实验室 实验环境要求:Redhat Linux 适用专业: 计算机科学与技术

一、实验目的 通过实验,掌握MSDOS文件系统中文件目录管理的实现方法。

二、实验内容(4选做)

1、设计并实现一个用于实现目录列表(类似DOS下的DIR命令或Linux的Shell命令ls)的函数。

2、设计并实现一个用于实现文件更名的函数。 3、设计并实现一个用于实现文件删除的函数。

4*、设计并实现创建新目录、把现有的文件拷贝到新文件的函数。 这些函数的原型如下: int fd_ls();

int fd_rename(char *oldfilename,char *newfilename); int fd_rm(char *name); int fd_mkdir(char *name);

int fd_cp(char *source, char *destination);

三、实验指导

本实验在MSDOS格式的软盘上完成。要实现这些函数需要一个使物理磁盘准备的函数(该函数调用磁盘驱动程序的初始化代码以及选择的其他初始化代码来判断磁盘几何结构): int fd_load(char driveLetter); 文件的更名与删除,需要查找文件,文件删除还要遍历FAT中的链接,设置FAT中每个簇项并将其标记为未使用,更新目录项。在删除的情况中,要注意文件的隐藏、只读和系统属性。任何具有这种属性的文件都不能删除。 此外,任务4中还需要fd_open()、fd_close()、fd_read()、fd_write()函数实现字节流的读写。

41

第二部分 WINDOWS 操作系统平台

实验一 进程同步

一、实验要求

在Windows 2000 环境下,创建一个包含n 个线程的控制台进程。用这n 个线程来表示n个读者或写者。每个线程按相应测试数据文件的要求,进行读写操作。请用信号量机制分别实现读者优先和写者优先的读者-写者问题。

读者-写者问题的读写操作限制:

1)写-写互斥; 2)读-写互斥; 3)读-读允许;

读者优先的附加限制:如果一个读者申请进行读操作时已有另一读者正在进行读操作,则该读者可直接开始读操作。

写者优先的附加限制:如果一个读者申请进行读操作时已有另一写者在等待访问共享资源,则该读者必须等到没有写者处于等待状态后才能开始读操作。

运行结果显示要求:要求在每个线程创建、发出读写操作申请、开始读写操作和结束读写操作时分别显示一行提示信息,以确信所有处理都遵守相应的读写操作限制。

二、测试数据文件格式

测试数据文件包括n 行测试数据,分别描述创建的n 个线程是读者还是写者,以及读写操作的开始时间和持续时间。每行测试数据包括四个字段,各字段间用空格分隔。第一字段为一个正整数,表示线程序号。第一字段表示相应线程角色,R 表示读者是,W 表示写者。第二字段为一个正数,表示读写操作的开始时间。线程创建后,延时相应时间(单位为秒)后发出对共享资源的读写申请。第三字段为一个正数,表示读写操作的持续时间。当线程读写申请成功后,开始对共享资源的读写操作,该操作持续相应时间后结束,并释放共享资源。

下面是一个测试数据文件的例子:

1 R 3 5 2 W 4 5 3 R 5 2 4 R 6 5 5 W 5.1 3

三、与实验相关的API 介绍

在本实验中可能涉及的API 有: 线程控制:

CreateThread 完成线程创建,在调用进程的地址空间上创建一个线程,以执行指定的函数;它的返回值为所创建线程的句柄。 HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD DWORD dwStackSize, // initial stack size

LPTHREAD_START_ROUTINE lpStartAddress, // threadfunction

42

LPVOID lpParameter, // thread argument DWORD dwCreationFlags, // creation option LPDWORD lpThreadId // thread identifier );

ExitThread 用于结束当前线程。 VOID ExitThread(

DWORD dwExitCode // exit code for this thread );

Sleep 可在指定的时间内挂起当前线程。 VOID Sleep(

DWORD dwMilliseconds // sleep time

);

信号量控制:

CreateMutex 创建一个互斥对象,返回对象句柄; HANDLE CreateMutex(

LPSECURITY_ATTRIBUTES lpMutexAttributes, // SD BOOL bInitialOwner, // initial owner LPCTSTR lpName // object name );

OpenMutex 打开并返回一个已存在的互斥对象句柄,用于后续访问; HANDLE OpenMutex(

DWORD dwDesiredAccess, // access

BOOL bInheritHandle, // inheritance option LPCTSTR lpName // object name );

ReleaseMutex 释放对互斥对象的占用,使之成为可用。 BOOL ReleaseMutex(

HANDLE hMutex // handle to mutex

);

WaitForSingleObject 可在指定的时间内等待指定对象为可用状态; DWORD WaitForSingleObject(

HANDLE hHandle, // handle to object

DWORD dwMilliseconds // time-out interval );

43

实验二 内存管理实验

一、实验要求

在本次实验中,需要从不同的侧面了解Windows 2000/XP 的虚拟内存机制。在Windows2000/XP 操作系统中,可以通过一些API 操纵虚拟内存。主要需要了解以下几方面: ??Windows 2000/XP 虚拟存储系统的组织 ??如何控制虚拟内存空间

??如何编写内存追踪和显示工具

??详细了解与内存相关的API 函数的使用

二、Windows 2000/XP 虚拟内存机制简介

内存管理是Windows2000/XP 执行体的一部分,位于Ntoskrnl.exe 文件中,是整个操作系统的重要组成部分。

默认情况下,32 位Windows 2000/XP 上每个用户进程可以占有2GB 的私有地址空间,操作系统占有剩下的2GB。Windows 2000/XP 在x86 体系结构上利用二级页表结构来实现虚拟地址向物理地址的变换。一个32 位虚拟地址被解释为三个独立的分量——页目录索引、页表索引和字节索引——它们用于找出描述页面映射结构的索引。页面大小及页表项的宽度决定了页目录和页表索引的宽度。比如,在x86 系统中,因为一页包含4096 字节,于是字节索引被确定为12 位宽(212 = 4096)。

应用程序有三种使用内存方法:

??以页为单位的虚拟内存分配方法,适合于大型对象或结构数组;

??内存映射文件方法,适合于大型数据流文件以及多个进程之间的数据共享; ??内存堆方法,适合于大量的小型内存申请。 本次实验主要是针对第一种使用方式。应用程序通过API 函数VirtualAlloc 和VirtualAllocEx 等实现以页为单位的虚拟内存分配方法。首先保留地址空间,然后向此地址空间提交物理页面,也可以同时实现保留和提交。

保留地址空间是为线程将来使用保留一块虚拟地址。在已保留的区域中,提交页面必须指出将物理存储器提交到何处以及提交多少。提交页面在访问时会转变为物理内存中的有效页面。

三、相关的API 函数

可以通过GetSystemInfo,GlobalMemoryStatus 和VirtualQuery 来查询进程虚空间的状态。主要的信息来源如下:

VOID GetSystemInfo ( LPSYSTEM_INFO lpSystemInfo ); 结构SYSTEMINFO 定义如下:

typedef struct _SYSTEM_INFO {

DWORD dwOemld; DWORD dwPageSize;

LPVOID lpMinimumApplicationAddress; LPVOID lpMaximumApplicationAddress; DWORD dwActiveProcessorMask; DWORD dwNumberOfProcessors; DWORD dwProcessorType;

DWORD dwAllocationGranularity; DWORD dwReserved;

44

本文来源:https://www.bwwdw.com/article/ph6h.html

Top