资源描述
课 程 设 计 报 告
课程名称:操作系统原理
院 系 : 计算机科学和技术
专业班级 : CS140 ____ __
学 号 : U14_____
姓 名 : ______ ___
指导老师 : _______ __
完成时间 : 3月11日_
目 录
1 试验目标 2
2 试验环境 2
3 试验内容 2
3.1 试验一 2
3.2 试验二 2
3.3 试验三 3
3.4 试验四 3
3.5 试验五(选做) 3
4 设计和实现 3
4.1 试验一 3
4.2 试验二 6
4.3 试验三 9
4.4 试验四 11
5 心得体会 14
1 试验目标
·掌握Linux操作系统使用方法;
·了解Linux系统内核代码结构;
·掌握实例操作系统实现方法;
2 试验环境
此次课程设计采取操作系统环境是windows10、Ubuntu双系统,Ubuntu系统版本号为16.04,内核版本号为linux 4.4.4;前两个试验在目前Ubuntu环境下完成,后两个试验在win10下虚拟机VirtualBoxUbuntu 15.10(内核为linux4.2.0-42) 中完成。
3 试验内容
3.1 试验一
要求熟悉和了解Linux下编程环境。
(1)编写一个C程序,用fread、fwrite等库函数实现文件拷贝功效。
(2)编写一个C程序,使用基于文本终端图形编程库(curses)或图形界面(QT/GTK),分窗口显示三个并发进程运行(一个窗口实时显示目前时间,一个窗口实时监测CPU利用率,一个窗口做1到100累加求和,刷新周期分别为1秒,2秒和3秒)。
3.2 试验二
要求掌握添加系统调用方法,采取编译内核方法,添加一个新系统调用,实现文件拷贝功效,另外编写一个应用程序,测试新增加系统调用。
3.3 试验三
掌握增加设备驱动程序方法。采取模块方法,添加一个新字符设备驱动程序,实现打开/关闭,读/写等基础操作。另外编写一个应用程序,测试新添加驱动程序。
3.4 试验四
要求了解和分析/proc文件。
(1)了解/proc文件特点和使用方法;
(2)监控系统状态,显示系统部件使用状态;
(3)用图形界面实现系统监控状态,包含CPU和内存利用率、全部进程信息等(可自己补充、添加其它功效);
3.5 试验五(选做)
要求了解和掌握文件系统设计方法(选做)。
设计、实现一个模拟文件系统。包含文件/目录创建/删除,目录显示等基础功效(可自行扩充文件读/写、用户登录、权限控制、读写保护等其它功效)。
4 设计和实现
4.1 试验一
4.1.1 试验要求
要求熟悉和了解Linux下编程环境。
4.1.2 试验设计及调试
(1)编写一个C程序,其内容为实现文件拷贝功效。
这个试验思绪是申明两个文件指针*fp_read和*fp_write,前者用来打开要读文件,后者打开要写文件,再创建一个1000个字节大小缓冲区buff[],然后调用fread将内容从文件1读到buff里,再调用fwrite把内容从buff写到文件2中。这个试验关键要掌握fread和fwrite使用方法,注意它们参数和返回值。代码见附件中源码。试验结果以下图4.1:
图4.1 运行之前图
运行./lab1_1 text1.txt text2.txt后结果以下图4.2:
图4.2 运行以后图
由上图可见text1.txt成功拷贝至text2.txt
(2)编写一个C程序,此次试验使用是图形界面GTK,分窗口显示三个并发进程运行(一个窗口实时显示目前时间,一个窗口实时监测CPU利用率,一个窗口做1到100累加求和,刷新周期分别为1秒,2秒和3秒)。
这个试验要用到gtk,首先要配置gtk,在终端中输入:
sudo apt-get install libgtk2.0-dev
包含到3个进程并发,所以要调用函数fork来创建3个进程。我思绪是在这3个进程中分别创建一个线程,去完成对应功效:显示目前时间,监测CPU利用率,做累加求和。分别经过函数void havetime()、void cpu_usage()、void add()实现,在main函数里,初步画出3个进程对应界面。具体是调用gtk_window_new()函数创建一个窗口、gtk_window_set_title()设置窗口标题、gtk_window_set_position设置窗口在屏幕位置、gtk_label_new()创建一个标签用来显示文本、gtk_container_add()把标签添加到窗口中、gtk_widget_show_all()来展示需要展示控件。比如创建第一个线程:
g_thread_create((GThreadFunc)havetime, NULL, FALSE, NULL);
经过在线程havetime()中实时更新标签label内容,然后在main()中创建窗口中展示来完成所要求功效。进程2和3所要求功效也是经过这种方法实现。注意用到gtk编译命令和以往不一样,为:
gcc -o lab1_2 lab1_2.c `pkg-config --cflags --libs gtk+-2.0`
代码见附件中源码,试验结果以下图4.3:
图4.3 运行结果图
小插曲:在调用sprintf(s,"CPU利用率为%f%% ",usage)想把利用率“%”拷进缓冲区s后打印出来时,一个百分号是不能够打印出来,要写两个%,如想要打印两个%,则要写4个%,以这类推。
4.2 试验二
4.2.1 试验要求
要求掌握添加系统调用方法,采取编译内核方法,添加一个新系统调用,实现文件拷贝功效,另外编写一个应用程序,测试新增加系统调用。
4.2.2 试验设计及调试
(1) 下载一个内核
下载linux-4.4.4.tar.gz
在 /usr/src/ 目录下解压(用超级用户权限),
(2) 编写新系统调用程序
用户空间所使用open、read、write、close函数此时对应内核函数为 sys_open、 sys_read、 sys_write、 sys_close。
首先经过sys_open()打开源文件和目标文件,分别返回文件描述符source和dest,然后把目前用户地址范围保留在fs,再把目前内存访问地址范围设置为内核内存地址访问范围,再经过sys_read()把源文件内容写到buf,再用sys_write()把buf内容写到dest,接着用sys_close()来关闭文件,最终再把内存访问地址范围设置为用户。保留fs是避免使用缓冲区超出了用户空间地址范围而报错。
把自己写这个系统调用程序添加至/usr/src/linux-4.4.4/kernel目录下sys.c最终。
(3) 添加系统调用号
在 /usr/src/linux-4.4.4/arch/x86/entry/syscalls目录下修改 syscall_64.tbl 文件,添加一个自己调用程序系统调用号,我之前用到了325好,所以添加326号,以下:
326 common mysyscall sys_mysyscall
(4) 添加系统调用程序申明
在/usr/src/linux-4.4.4/include/linux 目录下syscalls.h最终加上自己添加系统调用程序申明以下:
asmlinkage int sys_mysyscall(char* sourceFile,char* destFile);
(5) 编译、安装内核
在 /usr/src/linux-4.4.4 目录下对内核选项进行配置:
sudo make menuconfig
图4.4 内核配置图
选择save后退出
接下来就是漫长编译内核了(4个线程跑会快部分):
sudo make -j 4
大约1个小时左右编译完成,再安装内核:
sudo make modules_install //安装内核模块
sudo make install //安装内核
安装完成后重启,在Ubuntu高级选项中进入新内核。
(6) 编写系统调用测试程序
#include <sys/syscall.h>
#include <unistd.h>
int main(){
syscall(326,"text1.txt","text2.txt");
return 0;
}
试验结果以下图4.5:
图4.5 运行之前图
./lab2 后以下图:
图4.6 运行以后图
可见系统调用成功。
4.3 试验三
4.3.1 试验要求
掌握增加设备驱动程序方法。采取模块方法,添加一个新字符设备驱动程序,实现打开/关闭,读/写等基础操作。另外编写一个应用程序,测试新添加驱动程序。
4.3.2 试验设计及调试
(1)添加设备驱动原理:linux设备通常分为:字符设备、块设备和网络设备。驱动程序运行在内核空间,应用程序通常经过文件系统接口函数访问/dev目录下设备文件来访问驱动程序。编写设备驱动程序关键工作就是编写file_operations子函数,这次试验关键完成就是file-operations数据结构中.open\.release\.read\.write4个模块,file_operations结构每个域全部对应一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write等操作时,系统调用经过设备文件主设备号找到对应设备驱动程序,然后读取该数据结构对应函数指针,接着把控制权交给该函数。
(2) 编写Makefile文件
Makefile文件用于编译设备驱动程序,其内容以下:
iifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
obj-m := mydev.o
endif
(3) 编写设备功效函数
编写设备驱动程序关键工作就是编写子功效函数。
open()函数用来打开一个设备,在该函数中能够对设备进行初始化。假如这个函数被赋值NULL,那么设备打开永远成功,并不会对设备产生影响。
release()函数用来释放open()函数中申请资源,并在文件引用计数为0时,被系统调用。其对应应用程序close()方法,但并不是每一次调用close()方法,全部会触发release()函数,只有在打开全部设备文件全部释放后,该函数才会被调用。
read()函数用来从设备中获取数据,成功时返回读取字节数,失败时返回一个负错误码。
write()函数用来写数据到设备中。成功时该函数返回写入字节数。
具体实现见附件中源码。
(4) 设备驱动程序安装
① make,调用Makefile编译设备驱动程序
图4.7 make图
编译成功,生成mydev.ko文件
② sudo insmod mydev.ko 挂载模块,再查看设备主设备号:
cat /proc/devices
图4.8 主设备号图
可见系统为mydev生成主设备号为248
③ sudo /dev/mydev c 248 0
mydev是设备文件名字,“c”是指创建是字符设备文件,248是主设备号,0是从设备号。
图4.9 生成设备文件图
可见生成了正确设备文件mydev
(5) 测试驱动程序
测试程序实现是先从设备中读出里面初始字符串U14813,再把一个字符串写进去,然后再读出来,测试程序详见附件源码,测试试验结果以下图:
先运行命令实施程序:sudo ./test
图4.10 结果图
可见打开、读、写均正确。
最终实施 sudo rmmod mydev 来删除模块
sudo rm /dev/mydev 来删除设备文件
4.4 试验四
要求了解和分析/proc文件。
4.4.1 试验要求
了解/proc文件特点和使用方法;监控系统状态,显示系统部件使用状态;用图形界面实现系统监控状态,包含CPU和内存利用率、全部进程信息等(可自己补充、添加其它功效)。
4.4.2 试验设计及调试
这个试验关键是对/proc文件了解和对gtk熟练利用。我用gtk里笔记本构件画了3个页面,分别为page one、page two、page three。
page one:显示CPU利用率
计算和显示CPU直接用到试验一第二个部分显示CPU利用率部分,不一样是这儿用到了笔记本控件,要把“page one”作为一个button加到notebook上,还要把算出来CPU利用率作为一个标签内容加到notebook上,即:
GtkWidget *button4 = gtk_label_new("Page one");
label1 = gtk_label_new("begin");//全局标签
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), label1, button4);//笔记本容器、子控件、标题名
然后创建一个线程去计算CPU利用率,然后实时更新显示出来。
page two:显示全部进程信息
用gtk来画这个界面逻辑是:最大逻辑控件是笔记本notebook,把一个hbox添加到notebook里,再把显示全部进程信息表clist添加到一个滚动窗口scrolled_window里,再把这个scrolled_window添加到hbox左边部分;hbox右边部分包含一个vbox,这个vbox上面是一个frame来显示进程大致情况,下面是一个button用来刷新进程。以下图4.11:
notebook
hbox
vbox
scrolled_window
frame
clist
button
图4.11 构件分布结构图
clist中全部进程信息是经过void get_proc_info(GtkWidget *clist,int *p,int *q,int *r,int *s)函数读出来,具体就是遍历/proc文件夹下全部数字文件夹,这些数字文件夹stat中存放就是全部进程部分信息,包含:PID、名称、状态、优先级、占用内存,直接遍历读出来后加到clist中显示出来。一样,frame中总进程数、运行进程数、睡眠进程数、僵尸进程数也全部是依据get_proc_info()遍历时统计出来。刷新按钮要实现功效其实就是再次读出全部进程信息并显示出来,经过void refresh(GtkWidget *clist)函数实现。
GtkWidget *button2 = gtk_button_new_with_label("刷新");
g_signal_connect_swapped (G_OBJECT (button2), "clicked",G_CALLBACK (refresh), clist);
当button2上发生click事件时就调用refresh完成刷新。
page three:显示部分基础CPU信息
这页就是简单创建两个frame1和frame2来显示CPU部分信息和操作系统部分信息。
frame1中:CPU名称经过char* get_cpu_name(char *buf1)函数取得,CPU类型经过char* get_cpu_type(char *buf2)函数取得,CPU主频经过char* get_cpu_f(char *buf3)函数取得,这3个信息全部是从/proc/cpuinfo中读出来,其中包含到部分简单字符串判定等操作,在此不加赘述;
frame2中:操作系统名称是经过char *get_os_type(char *buf1)函数取得,操作系统版本是经过char *get_os_version(char *buf2)函数取得,它们全部返回一个缓冲区指针,用以在主函数main()中显示。
3个页面具体代码见附件中源码。
编译:gcc -o lab4 lab4.c `pkg-config --cflags --libs gtk+-2.0`
实施:./lab4
试验结果以下图4.12、4.13、4.14:
图4.12 第一个页面图
图4.13 第二个页面图
图4.14 第三个页面图
5 心得体会
此次课程设计,第一个题目第一问很简单,因为之前在试验中写过文件拷贝程序,所以很快就顺利实现了,要注意fopen、fread、fwrite多个函数使用方法,第二问刚开始不知怎么做,关键是因为之前没接触过gtk这种画界面环境,在网上查阅相关资料后发觉套路全部是一样,个人认为把握住gtk中控件概念,实现简单界面全部还是比较简单,除了gtk,在CPU利用率计算上还卡了一段时间,关键是没搞清到底应该怎么计算,查了大量资料后找到一个比较简单计算方法去实现,结果也很正确;第二题和第三题是同一类型,全部是需要对内核、系统调用、设备驱动文件有很好了解才能很快做出来,自己还掌握得不是很好,所以也花了不少时间去查资料、实现要求功效,但现在回过头来看收获很大,对课上所学部分内容有了愈加深刻了解,对linux系统调用和设备文件有了更深认识;第四题确实是花时间最多,因为不仅包含到复杂gtk利用,还包含对/proc文件了解,这两方面全部是查了大量资料后才一点点完成,时间花了,确实也有了成效,看到自己画界面和读出进程、CPU信息,成就感瞬间爆棚,但这还远远不够,相信以后学习工作中还会碰到愈加困难类似问题,这次课设只是打下了个小小基础,任重道远!总而言之,这次课设受益匪浅。
6 附录(源码)
试验一:
第一题:
#include <stdio.h>
#define buffer_size 1000
int main(int argc,char *argv[])
{
if(argc != 3) //输入3个参数
{
printf("input error!\n");
return 0;
}
char buff[buffer_size] = {0}; //缓冲区初始化为'\0'
int nread = 0;
FILE *fp_read = NULL;
FILE *fp_write = NULL;
if((fp_read = fopen(argv[1],"rb")) == NULL)
{
printf("can't open %s",argv[1]);
return 1;
}
if((fp_write = fopen(argv[2],"wb")) == NULL)
{
printf("can't open %s",argv[2]);
return 1;
}
while((nread = fread(buff,sizeof(char),buffer_size,fp_read))>0) //实际读到字符数nread
fwrite(buff,sizeof(char),nread,fp_write);
fclose(fp_read);
fclose(fp_write);
return 0;
}
第二题:
#include <gtk/gtk.h>
#include <time.h>
#include <unistd.h>
GtkWidget*window;
GtkWidget*label;
void havetime(){
int i;
for(;;i++){
time_t timer = time(NULL);
char s[1000] = {0};
sprintf(s,"local time is %s",ctime(&timer));//格式化字符串写入s
/*sleep for a second*/
sleep(1);
gdk_threads_enter();
gtk_label_set_text(GTK_LABEL(label),s);
gdk_threads_leave();
}
}
void cpu_usage(){
FILE *fp;
char cpu[5];
char buff[1000] = {0};
char s[1000] = {0};
long int user,nice,sys,idle,iowait,irq,softirq;
long int s1,s2,idle1,idle2;
float usage;
while(1){
fp = fopen("/proc/stat","r");
if(fp == NULL){
perror("fopen");
exit(0);
}
fgets(buff,sizeof(buff),fp); //从文件结构体指针中读取数据
sscanf(buff,"%s%d%d%d%d%d%d%d",cpu,&user,&nice,&sys,&idle,&iowait,&irq,&softirq);
s1 = user+nice+sys+idle+iowait+irq+softirq;
idle1 = idle;
rewind(fp);
sleep(1);
//memset(buff,0,sizeof(buff));
//cpu[0] = '\0';
user = nice = sys = idle = iowait = irq = softirq = 0;
fgets(buff,sizeof(buff),fp);
sscanf(buff,"%s%d%d%d%d%d%d%d",cpu,&user,&nice,&sys,&idle,&iowait,&irq,&softirq);
s2 = user+nice+sys+idle+iowait+irq+softirq;
idle2 = idle;
usage = (float)(s2 - s1 - (idle2-idle1))/(s2 - s1)*100;
sprintf(s,"CPU利用率为%f%% ",usage);
gdk_threads_enter();
gtk_label_set_text(GTK_LABEL(label),s);
gdk_threads_leave();
fclose(fp);
sleep(1);
}
}
void add(){
int j = 1;
int sum = 0;
int sum1 = 0;
for(;j<101;j++){
sum1 = sum+j;
char s[1000] = {0};
sprintf(s,"%d + %d = %d",sum,j,sum1);
sleep(3);
gdk_threads_enter();
gtk_label_set_text(GTK_LABEL(label),s);
gdk_threads_leave();
sum = sum1;
}
}
int main(int argc,char *argv[])
{
int pid_1,pid_2,pid_3;
if((pid_1 = fork()) == 0){
gtk_init(&argc,&argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window),"child 1 progress");
gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_NONE);
gtk_container_set_border_width(GTK_CONTAINER(window),100);
label = gtk_label_new("now let's begin!");
gtk_container_add(GTK_CONTAINER(window),label);
gtk_widget_show_all(window);
g_signal_connect(window,"destroy",G_CALLBACK(gtk_main_quit),NULL);
/*线程初始化*/
if(!g_thread_supported()) g_thread_init(NULL);
gdk_threads_init();
/*创建线程*/
g_thread_create((GThreadFunc)havetime, NULL, FALSE, NULL);
}
else if((pid_2 = fork()) == 0){
gtk_init(&argc,&argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window),"child 2 progress");
gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_NONE);
gtk_container_set_border_width(GTK_CONTAINER(window),100);
label = gtk_label_new("now let's begin!");
gtk_container_add(GTK_CONTAINER(window),label);
gtk_widget_show_all(window);
g_signal_connect(window,"destroy",G_CALLBACK(gtk_main_quit),NULL);
/*线程初始化*/
if(!g_thread_supported()) g_thread_init(NULL);
gdk_threads_init();
/*创建线程*/
g_thread_create((GThreadFunc)cpu_usage, NULL, FALSE, NULL);
}
else if((pid_3 = fork()) == 0){
gtk_init(&argc,&argv);
/*窗口初始化*/
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window),"child 3 progress");
gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_NONE);
gtk_container_set_border_width(GTK_CONTAINER(window),100);
/*标签*/
label = gtk_label_new("now let's begin!");
gtk_container_add(GTK_CONTAINER(window),label);
gtk_widget_show_all(window);
g_signal_connect(window,"destroy",G_CALLBACK(gtk_main_quit),NULL);
/*线程初始化*/
if(!g_thread_supported()) g_thread_init(NULL);
gdk_threads_init();
/*创建线程*/
g_thread_create((GThreadFunc)add, NULL, FALSE, NULL);
}
gdk_threads_enter();
gtk_main ();
gdk_threads_leave();
return 0;
}
试验二:
sys.c:
asmlinkage int sys_mysyscall(char* sourceFile,char* destFile){
int source=sys_open(sourceFile,O_RDONLY,0);
int dest=sys_open(destFile,O_WRONLY|O_CREAT|O_TRUNC,0600);
char buf[4096];
mm_segment_t fs;
fs = get_fs();
set_fs(get_ds()); //设置为内核内存访问地址范围
int i;
if(source>0 && dest>0)
{
do
{
i=sys_read(source,buf,4096);
sys_write(dest,buf,i);
}while(i);
}
sys_close(source);
sys_close(dest);
set_fs(fs);
return 10;
}
test.c:
#include <sys/syscall.h>
#include <unistd.h>
int main(){
syscall(326,"text1.txt","text2.txt");
return 0;
}
试验三:
mydev.c:
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/fs.h"
#include "linux/init.h"
#include "linux/types.h"
#include "linux/errno.h"
#include "linux/uaccess.h"
#include "linux/kdev_t.h"
#define BUFFER_SIZE 1024
static int my_open(struct inode *inode, struct file *file);
static int my_release(struct inode *inode, struct file *file);
static ssize_t my_read(struct file *file, char __user *user, size_t t, loff_t *f);
static ssize_t my_write(struct file *file, const char __user *user, size_t t, loff_t *f);
static char message[BUFFER_SIZE] = "U14813";
static int device_num = 0;//
static int counter = 0;//
static char* devname = "mydev";
struct file_operations pstruct = {
.read = my_read,
.write = my_write,
.open = my_open,
.release = my_release
};
int init_module(){
int ret;
ret = register_chrdev(0,devname,&pstruct);
if(ret < 0){
printk("regist failure\n");
return -1;
}
else {
printk("the device has been registered!\n");
device_num = ret;
printk("<1>the virtual device's major number %d.\n", device_num);
printk("<1>Or you can see it by using\n");
printk("<1>------more /proc/devices-------\n");
printk("<1>To talk to the driver,create a dev file with\n");
printk("<1>------'mknod /dev/myDevice c %d 0'-------\n", device_num);
printk("<1>Use \"rmmode\" to remove the module\n");
return 0;
}
}
void cleanup_module(){
unregister_chrdev(device_num,devname);
printk("unregister it success\n");
}
static int my_open(struct inode
展开阅读全文