2014 操作系统课程设计 Operating System Practicum
内容简介 设计目的 设计内容 实施方法及要求 时间安排 辅导
设 计 目 的 掌握Linux操作系统的使用方法; 了解Linux系统内核代码结构; 掌握实例操作系统的实现方法。
内容简介 设计目的 设计内容 实施方法及要求 时间安排 辅导
设 计 内 容(1) 要求:熟悉和理解Linux编程环境 内容 编写一个C程序,使用Linux下的图形库,分窗口显示三个并发进程的运行。
设 计 内 容(2) 要求:掌握添加系统调用的方法 内容 采用编译内核的方法,添加一个新的系统调用。 编写一个应用程序,测试新添加的系统调用。
设 计 内 容(3) 要求:掌握添加设备驱动程序的方法 内容: 采用模块方法,添加一个新的设备驱动程序。 要求添加字符设备的驱动。 编写一个应用程序,测试添加的驱动程序。
设 计 内 容(4) 要求:理解和分析/proc文件 内容 了解/proc文件的特点和使用方法。 监控系统状态,显示系统中若干部件的使用情况。 用图形界面显示系统监控状态。
设 计 内 容(5) 要求:理解和掌握文件系统的设计方法 内容 设计、实现一个模拟的文件系统。
内容简介 设计目的 设计内容 实施方法及要求 时间安排 辅导
实施方法及要求 每位同学都必须独立完成课程设计内容。 上机考核 演示完成的系统,并回答老师的问题。 提交 文本的课程设计报告,内容包括课程设计内容分析、程序清单(附注释)、调试记录(碰到的问题和解决方案)以及课程设计心得。 程序(电子版)。
实施方法及要求(续) 支持借鉴和学习已有的优秀知识! 反对全盘拷贝,不求甚解! 吸收和消化他人经验,做自己的课程设计! 自学能力的培养:学会上网查资料、解决问题!
内容简介 设计目的 设计内容 实施方法及要求 时间安排 辅导
时间安排 答疑 石柯 13517283980 keshi@mail.hust.edu.cn 每周四下午检查进展 刘水兵 13554079235 527620694@qq.com 刘承全 15072497636 450880690@qq.com 每周四下午检查进展
内容简介 设计目的 设计内容 实施方法及要求 时间安排 辅导
课程设计辅导 Linux系统的相关知识 进程并发 添加系统调用 添加设备驱动程序 /proc文件分析
Linux系统的相关知识 Linux版本 Linux通过简单的编号来区别内核的稳定版和开发版。每个版本用三个数字描述,由圆点分隔。前两个数表示版本号,第三个数表示发布号,如2.4.20。(2.6版本和2.4版本在具体的操作细节上有很大差异) 如果第二个数为偶数,则表示稳定的内核;否则,表示开发中的内核。 稳定版的发布主要用来纠正用户所报告的错误,但实现内核的主要算法和数据结构基本不变。然而,开发版本间可能存在很大的差异。
Linux系统的常用目录 文件目录结构——树型结构 常用目录有: /dev: dev是device的缩写。这个目录包含Linux的所有设备文件,如/dev/hda代表第一个物理IDE硬盘。 /etc: 这个目录用来存放系统管理所需要的配置文件和子目录。 /lib: 这个目录里存放着系统最基本的动态链接共享库,几乎所有的应用程序都需要用到这些共享库。
Linux系统的常用目录(续) /usr: 这是最庞大的目录,我们要用到的应用程序和文件几乎都存放在这个目录下。其中包含以下子目录: /usr/include: Linux下开发和编译应用程序所需的头文件; /usr/lib: 常用的动态链接共享库和静态档案库; /usr/local: 这是提供给一般用户的目录,在这里安装软件最适合; /usr/man: 帮助文档的存放目录; /usr/src: Linux的源代码目录。
Linux系统的核心源码 Linux核心源代码位于/usr/src/linux下,包括: arch: 包括所有和CPU类型相关的核心代码。它的每一个子目录都代表一种支持的CPU类型,例如i386就是关于Intel CPU及与之相兼容的体系结构的子目录,PC机一般都基于此目录; drivers: 放置系统所有的设备驱动程序;每种驱动程序又各占用一个子目录,如/block下为块设备驱动程序; include: 包括编译核心所需要的大部分头文件。与平台无关的头文件在include/linux子目录下,与Intel CPU相关的头文件在include/asm-i386子目录下;
Linux系统的核心源码(续) Linux核心源代码中的内容还包括: init: 包含核心的初始化代码(注:不是系统的引导代码),包含两个文件 main.c和version.c,这是研究核心如何工作的一个非常好的起点; mm: 包括所有独立于CPU体系结构的内存管理代码,如页式存储管理中内存的分配和释放等,而和体系结构相关的内存管理代码则位于arch/*/mm/下; kernel: 主要的核心代码,此目录下的文件实现了大多数Linux系统的内核函数,其中最重要的文件当属进程调度sched.c,同样,和体系结构相关的代码在arch/*/kernel中。
课程设计辅导 Linux系统的相关知识 进程并发 添加系统调用 添加设备驱动程序 /proc文件分析
进程并发 pid=fork():创建子进程。 返回值:0 从子进程返回, > 0 从父进程返回 返回值:0 从子进程返回, > 0 从父进程返回 exit进程自我终止,进入僵死状态,仍然保留task_struct结构。 wait( ) 等待进程终止(由父进程调用) exec( ) 执行一个可执行程序(文件)
进程并发 窗口、进度条和按钮的创建 程序流程 如何同时显示三个并发进程 编译命令
进程并发
进程并发 GTK(GIMP Toolkit) 控件、消息处理器和回调函数 利用控件可以实现一些图形的显示,比如显示窗口等等
进程并发 main(int argc, char* argv[]) { son = fork(); if (son == 0) { 创建窗口; exit(0); } else if (son < 0) { printf("儿子进程创建失败。\n"); } if (son > 0) { waitpid(son, &status, 0); } return 0; } void destroy(GtkWidget *widget, gpointer data) { gtk_main_quit(); } int g_window(int argc, char *argv[], char s[]) { …;
课程设计辅导 Linux系统的相关知识 进程并发 添加系统调用 添加设备驱动程序 /proc文件分析
添加系统调用 Linux系统调用机制 系统调用与普通函数调用的区别 Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。 用户可以通过系统调用命令在自己的应用程序中调用它们。 系统调用与普通函数调用的区别 系统调用 核心态 操作系统核心提供 普通的函数调用 用户态 函数库或用户自己提供
Linux系统调用机制 添加系统调用(续) int 0x80 使用寄存器中适当的值跳转到内核中事先定义好的代码中执行:跳转到系统调用的总入口system_call,检查系统调用号,再查找系统调用表sys_call_table,调用内核函数,最后返回。 系统调用是靠一些宏、一张系统调用表、一个系统调用入口来完成的。
添加系统调用(续) 与系统调用相关的内核代码文件(2.6内核): /usr/src/linux/include/asm-i386/unistd.h 系统调用清单(为每个系统调用分配唯一号码) /usr/src/linux/arch/i386/kernel/entry.S 包含系统调用和异常的底层处理程序、信号量的识别程序 ret_from_sys_call:调用和中断的返回点 对sys_call_table[ ](系统功能调用表)进行初始化 /usr/src/linux/kernel/sys.c 系统调用实现代码 /usr/src/linux/arch/i386/kernel/traps.c 定义许多出错处理程序
添加系统调用(续) 步骤_1 添加源代码 编写添加到内核中的源程序,函数名以sys_开头。 步骤_1 添加源代码 编写添加到内核中的源程序,函数名以sys_开头。 如:mycall(int num),在/usr/src/linux/kernel/sys.c文件中添加如下代码: asmlinkage int sys_mycall(int number) { return number; //该系统调用仅返回一个整型值 }
添加系统调用(续) 步骤_2 连接新的系统调用 使内核的其余部分知道该系统调用的存在。为此,需编辑两个文件: 步骤_2 连接新的系统调用 使内核的其余部分知道该系统调用的存在。为此,需编辑两个文件: /usr/src/linux/include/asm-i386/unistd.h——系统调用清单(为每个系统调用分配唯一号码) #define _NR_name nnn 这里,name:系统调用名;nnn:系统调用对应的号码,不能与内核自身的系统调用号相同。 /usr/src/linux/arch/i386/kernel/entry.S——对sys_call_table[ ]进行初始化(增加新的内核函数的指针) .long SYMBOL_NAME(sys_mycall)
添加系统调用(续) 步骤_3 重建Linux内核 配置内核的方式 对每个配置选项,有三种选择: 以root身份进入/usr/src/linux目录,重建内核 #make config //基于文本的传统配置界面 #make dep //检验内核源代码文件的依赖性和完整性 #make clean //清除以前编译的目标文件 #make bzImage //编译内核,也可采用make zImage 编译生成的内核文件为 /usr/src/linux/arch/i386/boot/bzImage 配置内核的方式 make config 基于文本的传统配置界面 make menuconfig 基于文本的菜单形式配置界面,字符终端下使用 make xconfig 基于图形窗口模式的配置界面,Xwindow下使用 对每个配置选项,有三种选择: “Y”——将该功能编译进内核 “N”——不将该功能编译进内核 “M”——将该功能编译成可动态载入的内核模块
补充说明 配置内核的方式 对每个配置选项,有三种选择: make config 基于文本的传统配置界面 make menuconfig 基于文本的菜单形式配置界面,字符终端下使用 make xconfig 基于图形窗口模式的配置界面,Xwindow下使用 make oldconfig 在原来的配置上作小的修改 对每个配置选项,有三种选择: “Y”——将该功能编译进内核 “N”——不将该功能编译进内核 “M”——将该功能编译成可动态载入的内核模块
若采用grub,修改/etc/grub.conf,添加新的引导内核 添加系统调用(续) 步骤_4 重启内核 将/usr/src/linux/arch/i386/boot/bzImage拷贝到/boot/bzImage 配置启动文件 若采用lilo,修改/etc/lilo.conf,添加新的引导内核 image=/boot/bzImage // 上面编译生成的内核映象 label=Linuxtest // 给该系统取个名字 root=/dev/hda5 // 根目录所在的分区,可用命令df查看 read-only 若采用grub,修改/etc/grub.conf,添加新的引导内核 title Linuxtest root(hd0,4) kernel /boot/bzImage ro root=/dev/hda5 initrd /boot/initrd-2.4.18-3.img 重启后,出现Linuxtest选项,选择它进入新的内核
添加系统调用(续) 步骤_5 使用新的系统调用 应用程序app.c中调用新添加的系统调用mycall: 编译gcc –o app app.c 步骤_5 使用新的系统调用 应用程序app.c中调用新添加的系统调用mycall: 宏指令说明:_syscalln(parameters):n表示系统调用所需参数,parameters为参数。如上面的宏中,第一个参数int表示返回值类型,第二个参数mycall为函数名。第三个和第四个分别用来指定参数的类型和名称。 编译gcc –o app app.c int main(int argv, char *argc[]) { int a = mycall(100); printf(“%d\n”,a); return 0; } #include<stdio.h> #include</usr/src/linux-2.4/ include/asm/unistd.h> #include<errno.h> _syscall1(int, mycall, int, ret) _syscall1(int mycall, int ret),讲这个函数时一定要强调,syscall后面紧紧跟了一个1,这个1时系统调用所需要的参数。
添加系统调用(续) 函数调用: 相同点:功能相同 不同点:作为内核代码,不能直接调用系统调用命令,应直接使用系统调用的实际函数 printf —— printk open() —— sys_open() close() —— sys_close() read() —— sys_read() write() —— sys_write()
添加系统调用(续)-3.2内核 修改(添加)源代码 第一个任务是编写加到内核中的源程序,即将要加到一个内核文件中去的一个函数,该函数的名称应该是新的系统调用名称前面加上sys_标志。假设新加的系统调用为mycall(int number),在/usr/src/linux-3.2.4/kernel/sys.c文件中添加源代码,如下所示: asmlinkage int sys_mycall(int number) { return number; }
添加系统调用(续)-3.2内核 连接新的系统调用 /usr/src/linux-3.2.4/arch/x86/include/asm/unistd_32.h 。 该文件中包含了系统调用清单,用来给每个系统调用分配一个唯一的号码。文件中每一行的格式如下: #define __NR_name NNN #define __NR_mycall 349 /*这是我们自己添加的系统调用*/ #define NR_syscalls 350 /usr/src/linux-3.2.4/arch/x86/kernel/syscall_table_32.s 对sys_call_table[]数组进行初始化。该数组包含指向内核中每个系统调用的指针.long sys_name .long sys_mycall 必须注意添加的行的位置,否则容易造成内核编译的失败
添加系统调用(续)-3.2内核 开始对新的内核进行编译(取决于所用的系统,以ubuntu为例) cd /usr/src/linux-3.2.4/ sudo make mrproper sudo make menuconfig (第一次这步可能执行出错,需要安装什么包如下命令安装:sudo apt-get install libncurses5-dev,安装好后,再make menuconfig sudo 此步由make bzImage 和 make modules两步组成,两步操作都要等很长时间,还不如等一步,之后就是让机子慢慢的编译内核去了) sudo make modules_install sudo make install sudo update-grub
添加系统调用(续)-3.2内核 对新加的系统调用进行测试 #include<stdio.h> #include</usr/src/linux-3.2.4/arch/x86/include/asm//unistd_32.h> #include<errno.h> #include<sys/syscall.h> int main(int argc,char **argv) { int b=syscall(349,200); /*第一个参数系统调用号,第二个参数,给的任意数值参数*/ printf("%d\n",b); return 0; }
课程设计辅导 Linux系统的相关知识 进程并发 添加系统调用 添加设备驱动程序 /proc文件分析
添加设备驱动程序 内核模块 LKM Loadable Kernel Modules Linux核心是一种monolithic类型的内核,即单一的大核心。 linux内核是一个整体结构,因此向内核添加任何东西.或者删除某些功能,都十分困难。为了解决这个问题,引入了模块机制,从而可以动态的在内核中添加或者删除模块。一旦被插入内核,他就和内核其他部分一样。
模块的实现机制 添加设备驱动程序(续) 模块初始化 int init_module( ){ }; 模块卸载 int cleanup_module( ){ }; 操作 unsigned long sys_create_module (char *name, unsigned long size); //重新分配内存 int sys_delete_module (char *name); //卸载 int sys_query_module (const char *name, int which, void *buf, size_t bufsize, size_t *ret); //查询 头文件:/usr/scr/linux/include/linux/module.h
添加设备驱动程序(续) 模块的实现机制 模块加入:insmod modulename.o 查看模块:lsmod 完成:加载目标文件 调用create_module重新分配内存 内核符号用get_kernel_syms解析未解析的引用 调用init_module初始化LKM->执行init_module(void)函数 查看模块:lsmod 结果:Module Page Used by modulename 1(内存信息) 0(使用次数) 删除模块:rmmod modulename
添加设备驱动程序(续) 模块编程实例 hello.c源码 编译 gcc –DMODULE –D_KERNEL_ -I /usr/src/linux_2.4.20-8/include -c hello.c printk("hello world !\n'); printk("I have runing in a kernel mod! \n"); return 0; } void cleanup_module() /* 模块卸载 */ { printk(" I will shut down myself in kernel mod!\n)"; } #include "linux/kernerl.h" #include "linux/module.h" /*处理版本问题CONFIG_MODVERSIONS */ #if CONFIG_MODVERSIONS==1 #define MODVERSIONS #include "linux/version.h" #end if int init_module() /* 模块初始化*/ {
添加设备驱动程序(续) Linux支持的设备类型 字符设备—— c 块设备—— b 网络设备 存取时没有缓存;对字符设备发出读写请求时,实际的I/O就发生了。如:鼠标、键盘等。 块设备—— b 利用一块系统内存区域作缓冲区,当用户进程对设备请求能满足用户要求时,返回请求数据,否则,调用请求函数进行实际的I/O操作。如:硬盘、软盘、CD-ROM等。 网络设备
添加设备驱动程序(续) 设备驱动程序 一组常驻内存的具有特权的共享库,是低级硬件处理例程。 设备等同文件处理,每个设备文件有两个设备号 主设备号:标识驱动程序 从设备号:表示使用同一个设备驱动程序的不同硬件设备。 设备驱动程序工作的基本原理 用户进程利用系统调用对设备进行诸如read/write操作,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。
添加设备驱动程序(续) 设备驱动程序的功能 对设备初始化和释放; 把数据从内核传送到硬件和从硬件读取数据; 读取应用程序传输给设备文件的数据和回送应用程序请求的数据; 检测和处理设备出现的错误。
添加设备驱动程序(续) Linux系统采用一组固定的入口点来实现驱动设备的功能。 open入口点: 打开设备准备I/O操作。open子程序必须对将要进行的I/O操作做好必要的准备工作,如清除缓冲区等。 close入口点: 关闭一个设备。当最后一次使用设备终结后,调用close子程序。 read入口点: 从设备上读数据。 write入口点: 往设备上写数据。 ioctl入口点: 执行读、写之外的操作。 select入口点: 检查设备,看数据是否可读或设备是否可用于写数据。 如果设备驱动程序没有提供上述入口点中的某一个,系统会用缺省的子程序来代替。对于不同的系统,也还有一些其它的入口点。
添加设备驱动程序(续) 入口点采用如下数据结构实现: 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 *, 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); } #include</usr/src/linux/include/linux/fs.h> struct file_operations { struct module *owner; loff_t (*llseek) (struct file *,loff_t ,int); 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 register_chrdev (unsigned int major, const char *name, struct file_operations *fops); //向系统的字符设备表登记一个字符设备 //major:希望获得的设备号,为0时系统选择一个没有被占用的设备号返回。 //name:设备名 //fops:登记驱动程序实际执行操作的函数的指针 //登记成功,返回设备的主设备号,否则,返回一个负值 int register_blkdev (unsigned int major, const char *name, struct file_operations *fops); //向系统的块设备表登记一个块设备
添加设备驱动程序(续) 设备卸载 int unregister_chrdev (unsigned int major, const char *name); //卸载字符设备 //major:要卸载设备的主设备号 //name:设备名 int unregister_blkdev (unsigned int major, const char *name); //卸载块设备
典型驱动程序的结构 // 在注销模块中卸载设备 void cleanup_module(void) { unregister_chrdev(test_major, "test"); } // 驱动子程序 static int open(struct inode *inode, struct file *file ) { …… } static void release(struct inode *inode, { …… } …… //将系统调用和驱动程序关联起来 struct file_operations *test_fops= .open; .release; }; // 在初始化模块中注册设备 int init_module(void) result = register_chrdev(0, "test", &test_fops); 字符设备提供的主要入口有:open ()、release ()、read ()、write ()、ioctl ()、llseek()、poll()等。
添加设备驱动程序(续) 添加设备驱动程序的方法 mknod /dev/test c 254 0 编写设备驱动程序mydev.c 在模块的初始化init_module()中调用设备注册函数; 在模块的卸载cleanup_module()中调用设备的卸载函数。 设备驱动模块的编译 gcc -O2 -DMODULE -D__KERNEL__ -I/usr/src/linux-2.4.20-8/include -c mydev.c 加载设备驱动模块: insmod –f mydev.o 若加载成功,在文件/proc/devices中能看到新增加的设备,包括设备名mydev和主设备号。 生成设备文件 mknod /dev/test c 254 0 //其中,test为设备文件名,254为主设备号,0为从设备号,c表示字符设备
添加设备驱动程序(续) 编写应用程序,测试驱动程序 编译 gcc hello.c –o hello if ( testdev == -1 ) { printf("Cann't open file \n"); exit(0); } read(testdev,buf,10); for (i = 0; i < 10;i++) printf("%d\n",buf[i]); close(testdev); #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() int testdev; int i; char buf[10]; testdev = open("/dev/test",O_RDWR);
添加设备驱动程序(续)
添加设备驱动程序(续) .o的文件是object文件,.ko是kernel object,与.o的区别在于其多了一些sections, 比如.modinfo。 在2.4内核中,生成的就是.o文件,2.6 kernel对内核模块的管理做了一些扩展,从此生成的就是.ko文件。
课程设计辅导 Linux系统的相关知识 进程并发 添加系统调用 添加设备驱动程序 /proc文件分析
/proc文件分析 /proc PROC文件系统是进程文件系统和内核文件系统的组成的复合体,是将内核数据对象化为文件形式进行存取的一种内存文件系统, 是监控内核的一种用户接口. 它拥有一些特殊的文件(纯文本),从中可以获取系统状态信息。 系统信息 与进程无关,随系统配置的不同而不同。 命令procinfo可以显示这些文件的大量信息。 进程信息 系统中正在运行的每一个用户级进程的信息。
/proc文件分析 系统信息 进程信息 /proc/cmd/line: 内核启动的命令行 /proc/cpuinfo: CPU信息 /proc/stat: CPU的使用情况、磁盘、页面、交换、所有的中断、最后一次的启动时间等。 /proc/meminfo: 内存状态的有关信息。 进程信息 /proc/$pid/stat /proc/$pid/status /proc/$pid/statm ……etc
/proc文件分析 监控系统功能 /proc文件系统的详细信息通过以下命令获取: 使用 GTK+ Linux下的c语言开发。 具体包括: 主机名、系统启动时间、系统运行时间、版本号、所有进程信息、CPU类型、CPU的使用率、内存使用率…… ----参照WINDOWS的任务管理器,实现其中的部分功能。 /proc文件系统的详细信息通过以下命令获取: man proc
/proc文件分析
/proc文件分析
模拟文件系统 实现方式 以一个独立运行的程序形式显现,运行程序后以命令的形式实现文件操作接口 以库的形式提供,可供用户编写程序调用 问题 文件系统:目录、文件 磁盘空间的管理:直接管理原始磁盘空间,用文件模拟 文件结构 文件操作接口 创建、删除、打开、关闭、读、写
模拟文件系统 磁盘空间管理 超级块 空闲空间管理:链表、成组连接 文件组织结构 目录结构 根目录
模拟文件系统 文件物理结构 链接、索引 文件操作接口
模拟文件系统
模拟文件系统
模拟文件系统
模拟文件系统
模拟文件系统
模拟文件系统
课程设计辅导——参考资料 Linux的“man”帮助 书籍 网站 《边干边学——Linux内核指导》李善平,陈文智等编著.浙江大学出版社. 《Linux Device Driver 2》 《Linux内核编程》 网站 http://www.linuxforum.net/ http://www.linuxsir.org/ http://linuxdevices.com/