《操作系统上机实验报告.docx》由会员分享,可在线阅读,更多相关《操作系统上机实验报告.docx(27页珍藏版)》请在三一办公上搜索。
1、操作系统上机实验报告操作系统课程设计 实验报告册 班级: 130812 学号: 13081175 姓名: 崔 愉 目 录 实验1 Linux系统调用.2 实验2 实验3 实验4 实验5 实现spinlock以及共享内存调用.5 内核模块.14 设备驱动.18 文件系统.22 实验编号 1 题目 Linux系统调用 加深对系统函数库、操作系统提供的系统调用的共同点和差异的认实验识,通过认识、了解Linux系统调用的实现方式,强化操作系统为用目的 户提供接口方式的理性认识。 为Linux内核增加一个系统调用,并编写用户进程的程序来测试。 要求该系统调用能够完成以下功能: 1. 该系统调用有1个整型
2、参数。 实验2. 若参数大于0,则返回自己学号的最后一位。如学号为248,则返内容 回8。 3. 若参数小于或等于0,则返回自己的学号。如学号为248,则返回248。 报告(1) 程序实现方法和思路 内容(2) 测试及结果 要求 报 告 正 文 一、实验准备 联网下载版本为2.6.25.14的linux内核压缩包,使用U盘放入WMware中安装的Fedora9操作系统中。解压后文件夹:/usr/src/linux-2.6.25.14。 二、实验步骤 、修改内核文件 修改内核文件:/usr/src/linux-2.6.25.14/kernel/sys.c 在sys.C文件中添加新的系统调用实现,
3、如下所示: asmlinkage int sys_mycall(int a) if(a0) return 5; else return 13081175; 截图: 2 (2) 在system call table中增加新的系统调用表项,寻找文件/usr/src/linux-2.6.25.14/arch/x86/kernel/syscall_table_32.h,在最后一行添加: .long sys_mycall /*327*/ 截图: (3) 增加新的系统调用编号: 修改文件/usr/src/linux-2.6.25.14/include/asm-x86/unistd_32.h的最后一行,增加
4、: #define _NR_mycall 327 /*my new syscall*/ 数字紧跟上一行最后一个系统调用的编号。 截图: 、构建新内核 进入/usr/src/linux-2.6.25.14文件夹,执行make mrproper命令,清除之前编译产生的文件。 执行make menuconfig命令,开始内核配置。 配置完成后,执行make bzImage命令生成内核,生成的内核在/usr/src/linux-2.6.25.14/arch/x86/boot/下面。 执行make modules生成模块。 复制5中生成的bzImage到/boot/下并重命名为vmlinuz-2.6.2
5、5.14来备份。 执行make modules_install命令安装模块,系统会生成initrd文件并自动修改启动文件grub。 执行make install。 重启系统。 选择新编译的内核启动。 、使用新增加的系统调用 编写测试函数test.c: 3 #include #include #include #define _NR_mycall 327 int main int i=syscall(327,-1); printf(My name is CuiYu, my number is %d.n,i); i=syscall(327,1); printf(The last of my num
6、ber is %d.n,i); return 0; 截图: 运行结果: 4 实验编号 2 题目 实现spinlock以及共享内存调用 初步了解C实验从实践中理解OS内核的加锁机制和进程调度算法的设计,目的 语言和汇编语言的混合编程 实现说明: i. 环境: 系统中共有a、b、c 3个应用进程、一个监控进程M和一个初始化进程Init, 此外系统中还有一个共享内存SHM。 SHM中有la,lb,lc 3个表示运行的锁变量,分属于A、B、C 3个应用进程,每个应用进程都通过spinlock试图获得锁,当获得锁后,它就开始正常运行。 SHM中还有有3个分属于各个进程用来表示进程是否结束的互斥变量ta,
7、tb,tc,以及3个分别用来控制访问ta、tb、tc的锁变量lta、实验ltb、ltc. 内容 ii. 实现过程:Init进程初始化共享内存,并设置la、lb、lc、ta、tb、tc、lta、ltb、ltc的初值。接着Init进程推出运行。 进程i分别通过spinlock不停地试图获得li,一旦获得,进程就开始正常运行。在进程i退出前,通过spinlock试图获得lti,获得lti后,进程i将ti置为表示进程结束的值,然后退出。 监控进程M按照指定的调度顺序逐步释放li。每释放一个li,M都要通过spinlock获得lti,然后通过访问ti,判断i进程是否结束,如果结束,那么M按照调度顺序释放
8、下一个进程j对应的锁变量lj。 报告 内容(1) 程序实现方法和思路 要求 (2) 测试及结果 报 告 正 文 5 一、实现思路: 本实验是一个使用自旋锁(spinlock)来实现进程调度的问题,此外还会牵涉到共享内存(SHM)的知识,在多个进程之间共享数据,以前只知道利用管道,但管道只能用于父子进程之间,所以必须使用共享内存来实现数据的共享。 对于spinlock,为了更好的理解OS内核的加锁机制,要求使用汇编指令实现,因为这样更贴近系统层。所以得用语言与汇编语言混合编程,这个是本实验的一个重点,同时也是难点所在。 有了spinlock,就能够对进程调度了,先使用fork及execl调用产生
9、init、monitor、processA、processB和processC等五个进程,其中init进程是对共享内存进行初始化,processA、processB和processC进程是三个应用进程,这三个应用进程受monitor监控进程控制运行,是按照先processA然后processB最后processC依次运行。 二、实验步骤: 开辟共享内存空间: /init.c #includespinlock.h int main int i; ad_sh_space = (int*)&sh_space; shm_key = ftok(share,a); shm_id = shmget(shm_
10、key,sizeof(sh_space),IPC_CREAT|0666); pos = shmat(shm_id, 0, 0); for(i = 0;i 9;i+) *ad_sh_space=1; *(pos+) = *(ad_sh_space+); return 0; 其中,函数shmget: 原型:#include #include #include int shmget(key_t key , size_t size , int oflag); 参数:第一个参数为key_t型; 第二个参数size为欲创建的共享内存端大小; 第三个参数shmflg用来标识共享内存段的创建标识。 功能:创建
11、或打开共享内存区。 返回值:成功返回非负内存区标识符,出错返回-1。 说明:1、size指明共享内存区大小; 2、key和oflag的说明基本和semget相同; 6 3、打开或创建一个共享内存区,并没提供调用进 程访问该内存区的手段。必须调用shmat。 函数shmat: 原型:#include #include #include void * shmat(int shmid , const void * shmaddr , int flag); 参数:第一个参数为要操作的共享内存标识符; 第二个参数指定共享内存的映射地址。如果该值非零,则将此值作为映射共享内存的地址,如果此值为零,则由系统
12、来选择映射的地址; 第三个参数用来指定共享内存段的访问权限和映射条件。 功能:用于将一个打开的共享内存区附接到调用进程的地址空间。 返回值:出错返回-1,shmat成功返回映射区的起始地址。 说明:shmat中shmaddr和flag可用来影响映射区起始地址的选取。一般应该分别提供NULL和0。 sh_space是共享内存的数据结构,里面定义本题要求的几个锁变量和标识进程是否完成的变量。 实现spinlock: /spinlock.h #include #include #include #include #include int spin_lock(int *p) asm( pushl %e
13、ax;nt pushl %ecx;nt wat:movl %1,%ect;nt movl %2,%eax;nt lock cmpxchgl %ecx,%0;nt jne wat;nt popl %ecx;nt popl %eax;nt :+m(*lock):i(0),i(1) :%eax,%ecx ); return 0; /*上锁函数,只能当*p的值为才能通过这个锁,否则在函数内部自旋。而且由于汇编原子指令cmpxchg的功能,它能在使用一个进程通过锁后同时将锁变量置,也就防止其它进程进入由锁变量形成的临界区。*/ 7 int spin_unlock(int *p) asm( pushl %
14、eax;nt movl %1,%eax;nt movl %eax,%0;nt popl %eax;nt :+m(*lock):i(1) :%eax ); return 0; /将锁变量*p的值置1,即等待进入的进程能够通过。 进程A、B、C: /processA #includespinlock.h int main printf(A startn); shm_key = ftok(share,a); shm_id = shmget(shm_key,sizeof(sh_space),IPC_CREAT|0666); pos = shmat(shm_id, 0, 0); spin_lock(po
15、s); printf(This is processAn); printf(This is processAn); printf(This is processAn); spin_lock(pos+6); *(pos+3)=0; spin_unlock(pos+6); printf(A finishn); return 0; 首先访问在init进程中创建的共享内存空间,试图获得锁li,获得锁之后,运行本进程,继连续打印三次“This is processI”。进程结束前,试图获得锁变量lti,获得锁之后,将标识本进程是否结束的变量ti置为零,表示进程结束,再释放锁变量lti。 8 调度进程:
16、/minotor #includespinlock.h int main shm_key = ftok(share,a); shm_id = shmget(shm_key,sizeof(sh_space),IPC_CREAT|0666); pos = shmat(shm_id, 0, 0); while(1) spin_lock(pos+6); /锁住lta 修改ta if(*(pos+3) != 1) /释放ta ta为0时A运行完 /若A运行完,解锁la spin_unlock(pos+6); /解锁lta spin_unlock(pos); /解锁la,令 la = 1 表示未上锁 sp
17、in_unlock(pos+6); /解锁lta spin_lock(pos+7); /锁ltb spin_lock(pos+6); /锁lta if(*(pos+4) != 1 & *(pos+3) = 1) /若A正在运行 B运行完 spin_unlock(pos+6); /解锁lta spin_unlock(pos+7); /解锁ltb spin_unlock(pos+1); / 解锁lb lb=1 未上锁 spin_unlock(pos+6); /解锁lta spin_unlock(pos+7); /解锁ltb spin_lock(pos+8); /锁ltc spin_lock(pos
18、+7); /锁ltb if(*(pos+5) != 1 & *(pos+4) = 1) /B正在运行 C运行完 spin_unlock(pos+7); /解锁ltb spin_unlock(pos+8); /解锁ltc spin_unlock(pos+2); / 解锁lc lc=1 未上锁 spin_unlock(pos+7); /解锁ltb spin_unlock(pos+8); /解锁ltc 9 spin_lock(pos+6); /锁lta spin_lock(pos+7); /锁ltb spin_lock(pos+8); /锁ltc if(*(pos+3) = 1 & *(pos+4)
19、 = 1 & *(pos+5) = 1) *(pos+3) = 0; /A未完成 *(pos+4) = 0; /B未完成 *(pos+5) = 0; /C未完成 spin_unlock(pos+8); /解锁ltc spin_unlock(pos+7); /解锁ltb spin_unlock(pos+6); /解锁lta sleep(1); return 0; 测试进程: 前面的工作做完后就可以写测试函数来运行了,进程就是一个程序,进程运行是需要有载体的,所以我打算使用fork产生子进程的方式,但fork出来的子进程是对父进程fork时的一个完整copy,这时得用到execl函数,此函数是li
20、nux的一个系统调用,它的功能是调用另外一个进程将它的调用者完全替换。 execl函数的原型如下: int execl(const char *path, const char *arg, ); 其第一个参数即为程序的路径,第二个参数为进程名字,其它参数可置NL。 测试运行操作步骤是:将所有.c文件逐一编译,然后运行测试程序。 源代码: /test #include #include #include #include #include #include #include int main int f ; 10 /process: init if(f = fork) = 0) if(execl
21、(./init,init,NULL) 0) printf(run error!); exit(-1); sleep(1); /使init进程有充足的时间先运行完毕 /processA if(f = fork) = 0) if(execl(./processA,processA,NULL) 0) printf(run error!); exit(-1); /processB if(f = fork) = 0) if(execl(./processB,processB,NULL) 0) printf(run error!); exit(-1); /processC if(f = fork) = 0
22、) if(execl(./processC,processC,NULL) 0) printf(run error!); exit(-1); /process: monitor if(f = fork) = 0) 11 if(execl(./monitor,monitor,NULL) 0) printf(run error!); exit(-1); sleep(10000); return 0; 编译截图: 逐个编译各个.C文件: 在编译成功后,运行test,得到如下结果,可以看出,A、B、C三个进程是在被反复调度着: 此时,查看当前运行的进程,可以看到如下进程树,其中,进程A、B、C正在反复运
23、行中: 12 13 实验编号 3 题目 内核模块 实验在改写内核文件的基础上,学习用增加内核模块的方法增加内核功目的 能。更深层次的理解linux系统内核。 第一步:实现一个简单的“hello world”内核模块,能实现模块的加载和卸载。 实验第二步:采用Makefile编译内核模块 内容 第三步:将该内核模块的初始化函数和清理函数写到两个文件中 第四步:重新采用Makefile编译 报告(1) 程序实现方法和思路 内容(2) 测试及结果 要求 报 告 正 文 (1)编写内核模块函数: 每一个内核模块中必须包含两个函数,即init_module与cleanup_module。其中,init_
24、module为模块初始化函数,其原型为: int init_module(void); 此函数主要完成模块初始化、登记模块API。Clean_module为模块清理函数,其原型为: void clean_module(void); 此函数完成卸载模块时的清理工作、取消登记的模块API。 #include #include int init_module( ) printk(Hello, worldn); return 0; void cleanup_module( ) printk(Goodbye my worldn); (2)编写Makefile文件: 在linux2.6版本的内核下,执行m
25、ake命令,需要一个 Makefile 文件,14 以告诉make命令需要怎么样的去编译和链接程序。Makefile中定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,Makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。 Makefile文件内容如下: obj-m := hello.o KERNELBUILD :=/lib/modules/$(shell uname -r)/build default: make -C $(KERNELBUILD) M=$(shell pwd) modules clean:
26、rm -rf *.o *.ko *.mod.c .*.cmd *.markers *.order *.symvers .tmp_versions 以上两步均完成后,我们可以通过执行一下命令验证内核模块正确与否,在终端下按顺序执行下列命令: make insmod hello.ko rmmod hello 对模块的操作中,有如下几个常用的命令: 1insmod命令 调用insmod程序把需要插入的模块以目标代码的形式插入到内核中。在插入的时候,insmod自动调用init_module函数运行。注意,只有超级用户才能使用这个命令,其命令格式为: # insmod path modulename.
27、ko 2. rmmod命令 调用rmmod程序将已经插入内核的模块从内核中移出,rmmod会自动运行cleanup_module函数,其命令格式为: #rmmod path modulename.ko 3lsmod命令 调用lsmod程序将显示当前系统中正在使用的模块信息。实际上这个程序的功能就是读取/proc文件系统中的文件/proc/modules中的信息,其命令格式为: #lsmod 截图如下: 15 通过命令 lsmod可以查看当前运行着的模块,如下图,第一个模块及为我们编写的hello模块。 (3)编写内核模块,将初始化函数和清理函数写到两个文件中: /hello1.c #inclu
28、de #include int init_module( ) printk(Hello, worldn); return 0; /hello2.c #include #include void cleanup_module( ) printk(Goodbye my worldn); 16 (4)编写Makefile文件: 此时编写的Makefile文件,需要将两个.c文件合并成一个目标文件,内容见下: obj-m := hello.o hello-objs:=hello1.0 hello2.o KERNELBUILD :=/lib/modules/$(shell uname -r)/build
29、 default: make -C $(KERNELBUILD) M=$(shell pwd) modules clean: rm 再次执行上述命令,如下图: -rf *.o *.ko *.mod.c .*.cmd *.markers *.order *.symvers .tmp_versions 由上可见,第二次编写的模块和第一次编写的有相同的执行解雇,由此得出模块编写成功,运行成功。 17 实验编号 4 题目 设备驱动 实验通过在系统内增加一个虚拟驱动,学习对linux操作系统设备的驱动,目的 深入理解linux对设备的管理方式。 实现一个字符设备,支持以下功能: 实验(1)用户可以向设备
30、写入字符串; 内容 (2)用户可以从设备读出写入的字符串; (3)用户通过系统调用ioctl清除设备中写入的字符串。 报告(1) 程序实现方法和思路 内容(2) 测试及结果 要求 报 告 正 文 (1)编写模块程序: 由于Linux操作系统允许设备驱动程序作为可装载内核模块实现,这也就是说,设备的接口实现不仅可以在Linux 操作系统启动时进行注册,而且还可以在Linux 操作系统启动后装载模块时进行注册。 设备驱动程序是一些函数和数据结构的集合,这些函数和数据结构是为实现管理设备的一个简单接口。操作系统内核使用这个接口来请求驱动程序对设备进行I/O操作。甚至,我们可以把设备驱动程序看成是一个
31、抽象数据类型,它为计算机中的每个硬件设备都建立了一个通用函数接口。由于一个设备驱动程序就是一个模块,所以在内核内部用一个file结构来识别设备驱动程序,而且内核使用file_operations结构来访问设备驱动程序中的函数。 设备驱动程序代码的如下几个部分: 驱动程序的注册与注销。 设备的打开与释放。 设备的读写操作。 设备的控制操作。 设备的中断和轮询处理。 #include #include #include #include #include #include #define RWBUF_NAME ChrDev_Cui #define RWBUF_DEV /dev/rwbuf #def
32、ine RWBUF_MAJOR 500 18 #define RWBUF_CLEAR 0x909090 #define RWBUF_GOOD 0x808080 #define RWBUF_COUNT 0x707070 #define RWBUF_SIZE 1024 int init_module void cleanup_module static char rwbufRWBUF_SIZE=13081175; static int rwbuf_len=8; static int_use=0; static int index=0; static int rwbuf_open(struct in
33、ode *inode,struct file *filep); static int rwbuf_close(struct inode *inode,struct file *filep); 用于字符设备的I/O调用主要有:open、release、read、write和ioctl。这些函数的声明如下: static ssize_t rwbuf_read(struct file *filep,char *buf,size_t count,loff_t *ppos); static ssize_t rwbuf_read(struct file *filep,const char *buf,siz
34、e_t count,loff_t *ppos); static int rwbuf_ioctl(struct inode *inode,struct file *filep,unsigned int cmd,unsigned long arg); 字符设备的结构即字符设备的开关表。当字符设备注册到内核后,字符设备的名字和相关操作被添加到device_struct结构类型的chrdevs全局数组中,称chrdevs为字符设备的开关表,具体如下: static struct file_operation rwbuf_fops= open: rwbuf_open,z release: rwbuf_c
35、lose, read: rwbuf_read, write: rwbuf_write, ioctl: rwbuf_ioctl, ; int my_init_module printk(Hello worldn); if(register_chrcev(RWBUF_MAJOR,RWBUF_NAME,&rwbuf_fops) /Linux系统通过调用register_chrdev向系统注册字符型设备驱动程序。 printk(register errorn); 19 return -1; return 0; void cleanup_module unregister_chrdev(RWBUF_MA
36、JOR,RWBUF_NAME); static int rwbuf_open(struct inode *inode,struct file *file) if(in_use=1) return -1; in_use=1; return 0; static int rwbuf_close(struct inode *inode,struct file *file) in_use=0; return 0; static ssize_t rwbuf_read(struct file *filep,char *buf,size_t count,loff_t *ppos) count=rwbuf_le
37、n; copy_to_user(buf,rwbuf,count); return count; static ssize_t rwbuf_write(struct file *filep,char *buf,size_t count,loff_t *ppos) if(count1024) count=1024; copy_from_user(rwbuf,buf,count); rwbuf_len=count; index+; return count; static int rwbuf_ioctl(struct inode *inode,struct file *filep,unsigned
38、20 int cmd,unsigned long arg) switch(cmd) case RWBUF_CLEAR: memset(rwbuf,0,rwbuf_size); rwbuf_len=0; break; case RWBUF_COUNT: return index; break; case RWBUF_GOOD: return rwbuf_len; break; default: return - EINVAL; (2) 内核模块编写完成后,依次执行: make insmod rwbuf mkond /dev/rwbuf c 500 0 maknod 命令用于生成设备文件,/dev
39、/rwbuf表示文件路径,c表示生成的设备文件是字符型设备的,500是主设备号,0是次设备号。 生成设备文件后,即可执行test测试模块编写正确与否: ./test /dev/rwbuf 13081175 截图如下: 验证模块编写无误! 21 实验编号 5 题目 文件系统 实验通过学习在/proc目录下创建文件、文件夹,深入理解linux操作系目的 统的文件系统,学习如何从/proc文件系统中读取、修改内核参数。 在/proc目录下用自己的学号创建一个目录,如/proc/13081201。然后在学号目录下创建两个文件,一个用自己的姓作为文件名,如实验/proc/13081201/zhang,此
40、文件是只读的,用于显示当前进程的PID内容 信息;另一个文件用自己的名字作为文件名,如/proc/13081201/xiaoming,此文件是可读写的。 报告(1) 程序实现方法和思路 内容(2) 测试及结果 要求 报 告 正 文 同样运用内核模块,在/proc目录下创建文件,模块代码或中必须包含模块初始化函数init_module和模块卸载函数cleanup_module。在模块的初始化函数中,执行在/proc目录下以自己学号建立一个子目录,即/proc/13081175,再在此目录下创建两个文件,以自己姓为的名称的只读文件(Cui_file=create_proc_read_entry(Cui,0444,example_dir,proc_read_current,NULL),另一个是以自己名字为文件名的可读可写文件(Yu_file=create_proc_entry(Yu,0666,example)。 只读文件拥有一项操作,即为读文件(proc_read_Cui),可读写文件拥有两个操作,读操作(proc_read_Yu)和写操作(proc_write_Yu),这三个函数需要分别定义。 实验源代码如下: /my_proc.c #include #in