操作系统课程论文
院 系: 计算机学院
班 级: 20##计算机科学技术1班
姓 名: 曹作西
学 号: 2007123456
指导教师: xxx
完成时间: 2010.01
东莞理工学院
摘 要
本文分析面向对象教学操作系统EOS的系统结构和代码构成,通过源代码分析学习该系统的进程有关数据结构,掌握其进程创建过程、线程创建过程和上下文切换方法,理解其进程管理的机理,学习其面向对象实现机制,锻炼大型软件源代码阅读技能,以加深对操作系统原理知识点的理解和掌握,了解系统程序的实现方法,学习EOS面向对象程序设计技术。
一、 课程设计任务和分工
本次课程设计的任务是从进程管理代码的阅读与改进(包括进程创建、进程同步和进程调度四个实验)、设备驱动代码的阅读与改进、内存管理代码的阅读与改进、系统启动代码的阅读与改进、文件系统代码的阅读与改进等五个题目中任选一个,分组完成,每组人数为1—5人。课程设计内容是对代码通过阅读源代码,理解EOS操作系统的结构、设计技术,分析其执行流程,并对其功能进行改进和增强,以达到更好地掌握操作系统原理与技术的目的。
本文分工是阅读EOS进程创建过程,并画出系统数据结构和主要流程图。
二、实现原理和数据结构
1.实验原理
阅读EOS进程创建源代码,通过EOS实验环境的程序调试过程理解其执行流程,在此基础上画出EOS进程创建的源代码。
2.数据结构所在源文件
ki.h:内核支撑模块的内部函数及变量的声明
mi386.h:i386 内存相关。
obp.h: 对象管理模块的内部头文件。
psp.h:进程管理模块的内部头文件。
kdb.h: 内核调试器接口声明。
ke.h:系统支撑模块对外接口的头文件,供内核其它模块使用。
3.数据结构名称和位置
进程对象结构体:PROCESS,psp.h
线程对象结构体:THREAD,ps.h
对象类型结构体:OBJECT_TYPE, obp.h
对象头结构体:OBJECT_HEADER, obp.h
上下文环境结构体:CONTEXT
4. 数据结构
(1)进程结构体
typedef struct _PROCESS {
BOOLEAN System; // 是否系统进程
UCHAR Priority; // 进程的优先级
PMMPAS Pas; // 进程地址空间
PHANDLE_TABLE ObjectTable;进程的的内核对象句柄表
LIST_ENTRY ThreadListHead; // 进程线程链表头
PTHREAD PrimaryThread; // 主线程指针
LIST_ENTRY WaitListHead;
// 等待队列,所有等待进程结束的线程都在此队列等待。
PSTR ImageName; // 二进制映像文件名称
PSTR CmdLine; // 命令行参数
PVOID ImageBase; // 可执行映像的加载基址
PPROCESS_START_ROUTINE ImageEntry;
// 可执行映像的入口地址
HANDLE StdInput;
HANDLE StdOutput;
HANDLE StdError;
ULONG ExitCode; // 进程退出码
} PROCESS;
(2)线程结构体
typedef struct _THREAD {
PPROCESS Process; // 线程所属进程指针
LIST_ENTRY ThreadListEntry; // 进程的线程链表项
UCHAR Priority; // 线程优先级
UCHAR State; // 线程当前状态
ULONG RemainderTicks; // 剩余时间片,用于时间片轮转调度
STATUS WaitStatus; // 阻塞等待的结果状态
KTIMER WaitTimer; // 用于有限等待唤醒的计时器
LIST_ENTRY StateListEntry; // 所在状态队列的链表项
LIST_ENTRY WaitListHead;
// 所有等待线程结束的线程都在此队列等待。
为了结构简单,EOS没有对内核进行隔离保护,所有线程都运行在内核状态,所以目前线程没有用户空间栈//
PVOID KernelStack; // 线程位于内核空间的栈
CONTEXT KernelContext; // 线程执行在内核状态的上下文环境状态
PMMPAS AttachedPas; // 线程在执行内核代码时绑定进程地址空间。
PTHREAD_START_ROUTINE StartAddr; // 线程的入口函数地址
PVOID Parameter; // 传递给入口函数的参数
ULONG LastError; // 线程最近一次的错误码
ULONG ExitCode; // 线程的退出码
} THREAD;
(4)对象头结构体
typedef struct _OBJECT_HEADER {
PSTR Name; // 对象名称字符串指针
ULONG Id; // 对象ID,全局唯一
POBJECT_TYPE Type; // 对象类型指针
ULONG PointerCount; // 对象指针计数器
ULONG HandleCount; // 对象句柄计数器
LIST_ENTRY TypeObjectListEntry; // 所属类型的对象链表项
LIST_ENTRY GlobalObjectListEntry; // 全局的对象链表项
QUAD Body; // 对象体的起始域
} OBJECT_HEADER;
(4)对象类型结构体
typedef struct _OBJECT_TYPE{
PSTR Name; // 类型名称字符串指针
SINGLE_LIST_ENTRY TypeListEntry;
LIST_ENTRY ObjectListHead;
ULONG ObjectCount; // 对象计数器,所有属于此类型的对象的总数
ULONG HandleCount;
} OBJECT_TYPE;
(5)EOS对象结构
三、各函数详细说明及程序流程图
1.进程创建应用程序测试函数(newproc.c)
2. 创建进程函数:CreateProcess("A:\\Hello.exe",
NULL, 0, &StartupInfo, &ProcInfo)
功能:产生PROCESS和THREAD对象
3. 创建进程运行环境:
PspCreateProcessEnvironment
(8, ImageName, CmdLine,
&ProcessObject)
4. 创建系统进程PsCreateSystemProcess()
(1)创建进程环境PspCreateProcessEnvironment
过程:
Þ 创建进程对象ObCreateObject:对象类型PspProcessType
Ý 计算对象头及对象体存储需求及分配存储器
Ý 初始化对象头,type= PspProcessType,将对象头插入对象链表及全局对象链表
Ý 返回对象体指针
Þ 为进程分配句柄表
Þ 初始化进程控制块:优先级:24,exit_code:0
Þ 返回变量值: PspSystemProcess
(2)创建进程的主线程 PspCreateThread
设置:PspSystemProcess->PrimaryThread
参数:进程PspSystemProcess,启动地址: KiSystemProcessRoutine
过程:
Þ 创建线程对象ObCreateObject ,类型PspThreadType ,大小:THREAD大小
Ý 计算对象头及对象体存储需求并分配内存
Ý 初始化对象头,type=PspThreadType, 对象头插入对象链表及全局对象链表
Ý 返回对象体指针
Þ 栈大小8KB
Þ 初始化线程控制块:process= PspSystemProcess, priority, startaddr, state=0, RemainderTicks =6,
Þ 初始化线程上下文:PspInitializeThreadContext
Ý 初始化所有通用寄存器为0,设置段寄存器
Ý 所
Ý 有线程都从线程启动函数PspThreadStartup()开始执行
Ý 初始化内核模式栈;初始化程序状态字,仅仅设置了一位:允许中断
Þ 使线程进入就绪状态。
Þ 执行调度:KeSchedule ()
Þ 线程启动函数PspThreadStartup()
Ý PspCurrentThread->StartAddr(PspCurrentThread->Parameter)
Ý PsExitThread(ExitCode);
四、功能改进和增强
无
五、运行结果分析
多次调用CreateProcess函数创建Hello.exe应用程序的多个进程
六、结论和心得体会
六、参考文献
1. 北京海西慧学科技有限公司. EOS实验指南. 2009.
2. 北京海西慧学科技有限公司. EOS应用指南. 2009.
3. 汤晓丹. 现代操作系统. 电子工业出版社. 2008.
一、 课程设计目的
掌握Linux操作系统的使用方法;
了解Linux系统内核代码结构;
掌握实例操作系统的实现方法。
二、 课程设计要求
1、 掌握Linux操作系统的使用方法,包括键盘命令、系统调用;掌握在
Linux下的编程环境。
? 编一个C程序,其内容为实现文件拷贝的功能;
? 编一个C程序,其内容为分窗口同时显示三个并发进程的运行结果。要求用到Linux下的图形库。
掌握系统调用的实现过程,通过编译内核方法,增加一个新的系统调用。另编写一个应用程序,调用新增加的系统调用。
实现的功能是:文件拷贝;
掌握增加设备驱动程序的方法。通过模块方法,增加一个新的设备驱动程序,其功能可以简单。
实现字符设备的驱动;
了解和掌握/proc文件系统的特点和使用方法
? 了解/proc文件的特点和使用方法
? 监控系统状态,显示系统中若干部件使用情况
? 用图形界面实现系统监控状态。
设计并实现一个模拟的文件系统(选作)
2、 3、 4、 5、
三、 课程设计系统平台
四、 课程设计内容一
1、 编一个C程序,其内容为实现文件拷贝的功能
要实现文件拷贝功能,主要用到的函数是open、write、read。 以前在windows下写C语言打开文件常用的fopen,此时不能用,因为fopen是ANSIC标准中的C语言库函数,在不同的系统中应该调用不同的内核api ;所以应该直接使用linux中的系统函数open。
主要用到的头文件:
Unistd.h \\包含了许多Linux系统服务的函数原型,如:read、write Fcntl.h \\定义了很多宏和open,fcntl函数原型
Stdio.h \\标准输入输出头文件
sys/types.h \\此头文件包含适当时应使用的多个基本派生类型
sys/stat.h \\包含了获取文件属性的一些函数
errno.h \\用于调试错误代码是所需要的一些errno变量
string.h \\包含了处理字符串的一些函数
设计思路:由命令行参数获取2个文件名,根据其文件名和路径分别打开该2个文件,设置一个循环,从源文件复制N个字节到目的文件,直到源文件指针到文件尾,最后关闭2个文件。
在可能出错的地方需要加上相应的报错代码和中断,并输出错误信息,以方便调试或是往后应用在第2小题中可能发生的错误。
理清楚设计思路后,根据需求写出相应的源代码见后页源程序代码scopy.c ;在Linux终端使用编译命令 gcc –o scopy scopy.c将程序编译并生产exe可执行文件。
然后手动创建一个测试文件test.txt ,在终端输入命令
./scopy test.txt target.txt
这样就能将源文件test.txt复制到目标文件target.txt
程序源代码 scopy.c:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#define BUFFER_SIZE 1024 //缓冲区大小
int main(int argc,char **argv)
{
int from_fd,to_fd;
int bytes_read,bytes_write;
char buffer[BUFFER_SIZE]; //设定一个缓冲区
char *ptr;
if(argc!=3) //三个参数
{
fprintf(stderr,"Usage:%s fromfile tofile\n\a",argv[0]); return(-1);
}
/* 打开源文件 */
if((from_fd=open(argv[1],O_RDONLY))==-1)
{
fprintf(stderr,"Open %s Error:%s\n",argv[1],strerror(errno)); return(-1);
}
/* 创建目的文件 */
if((to_fd=open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1) {
fprintf(stderr,"Open %s Error:%s\n",argv[2],strerror(errno)); return(-1);
}
while(bytes_read=read(from_fd,buffer,BUFFER_SIZE))
{
/* 一个致命的错误发生了 */
if((bytes_read==-1)&&(errno!=EINTR)) break; else if(bytes_read>0)
{
ptr=buffer;
while(bytes_write=write(to_fd,ptr,bytes_read)) {
/* 一个致命错误发生了 */
if((bytes_write==-1)&&(errno!=EINTR))break;
/* 写完了所有读的字节 */
else if(bytes_write==bytes_read) break; /* 只写了一部分,继续写 */ else if(bytes_write>0)
{
ptr+=bytes_write;
bytes_read-=bytes_write; }
}
/* 写的时候发生的致命错误 */
if(bytes_write==-1)break;
}
}
close(from_fd);
close(to_fd);
return(1);
}
2、 编一个C程序,其内容为分窗口同时显示三个并发进程的运行结果。要求用到Linux下的图形库。 本次我选用的图形库是GTK+ 首先要在Linux下载GTK+相关库文件并安装。 在终端输入sudo apt-get install gnome-core-devel ,然后根据提示操作,就会安装 libgtk2.0-dev libglib2.0-dev 等开发所需的相关库文件。 编译GTK+代码时需要包含的头文件是gtk/gtk.h,此外,还必须连接若干库;比如编译test.c时用以下命令。 gcc –o test test.c `pkg-config --cflags --libs gtk+-2.0` 在编写代码时需要用到的控件、窗口等视窗物件形态,用类GtkWidget定义其为指针类型。 编写一个GTK+程序的基本步骤如下: ? 初始化Gtk ? 建立控件 ? 登记消息与消息处理函数 ? 执行消息循环函数gtk_main() 之后所设计的3个进程,基本上都是以这样的方式编写代码的,因为之前曾用过OpenGL,所以在这方面掌握的比较快。 初始化主要使用的函数有
gtk_init(&argc,&argv); //启动GTK
gtk_window_new(GTK_WINDOW_TOPLEVEL); //创建窗口 gtk_window_set_title(GTK_WINDOW(window),"标题名"); //设置窗口标题名
gtk_widget_set_usize(window, 200, 200); //设置窗口大小 gtk_widget_show(window); //显示窗口 建立控件的一般流程 /*创建表格准备封装*/ gtk_table_new ( //创建多少列
gint rows, //创建多少栏
gint columns, //用来决定表格如何来定大小
gint homogeneous);
/*这个函数是将表格table,结合到窗口window里*/
gtk_container_add(GTK_CONTAINER(window),table);
gtk_widget_show(table);
// 显示该表格
/*要把物件放进box中,可用以下函数*/
void gtk_table_attach_defaults (
GtkTable*table,
GtkWidget*widget,
gintleft_attach,
gintright_attach,
ginttop_attach,
gintbottom_attach);
//参数("table")是选定某表格 //("child")是想放进去的物件 //以下参数是指定把物件放在哪里, 及用多少个boxes
本次我所设计的3个进程主要使用了下列控件:进度条、按钮、文本框、滚动条,分别用在3个进程里。
进度条函数:
progress_bar=gtk_progress_bar_new(); //创建进度条
按钮函数:
gtk_button_new_with_label(“LABEL”); //创建带有"LABEL"的字在上面的按钮
文本框:
gtk_text_new(NULL,NULL); //创建文本构件
滚动条:
gtk_vscrollbar_new(GTK_TEXT(text)->vadj); //创建滚动条并设置其与文本同步
其他函数:
gint gtk_timeout_add (
guint32 interval,
GtkFunction function,
gpointer data);
//每间隔interval 毫秒呼叫一次指定函数 //被呼叫的函数 //要传给该函数的资料
gtk_signal_connect (
GtkObject *object,
gchar *name,
GtkSignalFunc func,
gpointer func_data); //送出信号的物件 //希望接取的信号名称 //送给该函数的资料
根据上述重要的函数分别编写4个源代码,分别编译后,输入./Stest运行Stest结果如截图所示:
下面附上4个进程的源代码:
1) Stest.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/sem.h>
int semid;
char *finish;
int p1,p2;
int main (void){
if((p1=fork())==0){//创建新进程
execv("./S1",NULL);
}
else{
if((p2=fork())==0){
execv("./S2",NULL);
}
else{
execv("./S3",NULL);
}
}
return 0;
}
2) S1.c
#include <gtk/gtk.h>
#include<string.h>
GtkWidget *window;
GtkWidget *table;
GtkWidget *button;
GtkWidget *progress_bar;
// 更新进度条,这样就能够看到进度条的移动
gint progress_timeout( gpointer data ){
gdouble value;
int v;
char text[20]="0%";
//使用在调整对象中设置的取值范围计算进度条的值
value=gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR
(progress_bar))+0.01;
if (value>1.0)
value=0.0;
v=(int)(value*100);
strcpy(text," ");
sprintf(text,"%d",v);
strcat(text,"% ");
// 设置进度条的新值
gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress_bar),value);
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),text); //这是一个timeout函数,返回 TRUE,这样它就能够继续被调用 //如果想要结束,可以在进度条到100%时,return 0;这样回调函数就会结束
return TRUE;
}
int main(int argc,char *argv[]){
int timer;
gpointer data;
gtk_init(&argc,&argv);//在任何构件生成之前完成
window=gtk_window_new(GTK_WINDOW_TOPLEVEL);//创建窗口
gtk_window_set_title(GTK_WINDOW(window),"Swindow1");//设置窗口标题
gtk_widget_set_usize(window, 200, 200);//设置窗口大小
gtk_container_set_border_width(GTK_CONTAINER(window),5);//设置窗口边框宽度
gtk_widget_show(window);//显示窗口
gtk_signal_connect(GTK_OBJECT(window),"destroy",GTK_SIGNAL_FUNC(gtk_main_quit),NULL);
table=gtk_table_new(5,11,TRUE);//创建表格3行*5列
gtk_widget_show(table);//显示表格
gtk_container_add(GTK_CONTAINER(window),table);//将table1装进窗口 /* 创建进度条 */
progress_bar=gtk_progress_bar_new();
gtk_table_attach_defaults(GTK_TABLE(table),progress_bar,0,11,1,2);//进度条装进表格
gtk_widget_show(progress_bar);
/* 加一个定时器(timer),以更新进度条的值 */
timer=gtk_timeout_add(150,progress_timeout,data);
gtk_main ();
return 0;
}
3) S2.c
#define GTK_ENABLE_BROKEN
#include<gtk/gtk.h>
#include<string.h>
GtkWidget *window;
GtkWidget *table;
GtkWidget *label;
GtkWidget *button;
GtkWidget *button1;
int i=0;
gint hello(void ){
label=gtk_label_new("Hello World!"); //创建标题
gtk_table_attach_defaults(GTK_TABLE(table),label,3,8,0,1);
gtk_widget_show (label);
return TRUE;
}n
int main(int argc,char *argv[]){
GtkWidget *vscrollbar;
int timer;
gpointer data;
gtk_init(&argc,&argv);//在任何构件生成之前完成
window=gtk_window_new(GTK_WINDOW_TOPLEVEL);//创建窗口
gtk_window_set_title(GTK_WINDOW(window),"Swindow2");//设置窗口标题
gtk_widget_set_usize(window, 200, 200);//设置窗口大小
gtk_container_set_border_width(GTK_CONTAINER(window),5);//设置窗口边框宽度
gtk_window_set_position(GTK_WINDOW(window),
GTK_WIN_POS_MOUSE);//设置窗口位置
gtk_widget_show(window);//显示窗口
gtk_signal_connect(GTK_OBJECT(window),"destroy",GTK_SIGNAL_FUNC(gtk_main_quit),NULL);
table=gtk_table_new(11,10,TRUE);//创建表格11行*10列
gtk_container_add(GTK_CONTAINER(window),table);//将table1装进窗口
gtk_widget_show(table);//显示表格
/* 添加一个按钮,用来退出应用程序 */
button = gtk_button_new_with_label ("close");
gtk_table_attach_defaults(GTK_TABLE(table),button,3,8,6,8);//按钮装进表格
gtk_widget_show (button);
gtk_signal_connect(GTK_OBJECT(button),"clicked",GTK_SIGNAL_FUNC(gtk_main_quit),NULL);
gtk_main ();
return 0;
}
4) S3.c
#define GTK_ENABLE_BROKEN
#include<gtk/gtk.h>
#include<string.h>
GtkWidget *window;
GtkWidget *table;
GtkWidget *label;
GtkWidget *text;
int i=0;
gint text_timeout( gpointer data ){
char buf[20];
char temp[10];
sprintf(temp,"%d",i);
strcpy(buf,"text ");
strcat(buf,temp);
strcat(buf,"\n");
//将buf内容插入到文本构件中
gtk_text_insert(GTK_TEXT(text),NULL,NULL,NULL,buf,-1);
i++;
//这是一个timeout函数,返回 TRUE,这样它就能够继续被调用 //如果想要结束,return 0;这样回调函数就会结束
return TRUE;
}
int main(int argc,char *argv[]){
GtkWidget *vscrollbar;
int timer;
gpointer data;
gtk_init(&argc,&argv);//在任何构件生成之前完成
window=gtk_window_new(GTK_WINDOW_TOPLEVEL);//创建窗口
gtk_window_set_title(GTK_WINDOW(window),"Swindow3");//设置窗口标题
gtk_widget_set_usize(window, 200, 400);//设置窗口大小
gtk_container_set_border_width(GTK_CONTAINER(window),5);//设置窗口边框宽度
gtk_window_set_position(GTK_WINDOW(window),
GTK_WIN_POS_CENTER); //设置窗口位置
gtk_widget_show(window);//显示窗口
gtk_signal_connect(GTK_OBJECT(window),"destroy",GTK_SIGNAL_FUNC(gtk_main_quit),NULL);
table=gtk_table_new(11,10,TRUE);//创建表格11行*10列
gtk_container_add(GTK_CONTAINER(window),table);//将table1装进窗口
gtk_widget_show(table);//显示表格
label=gtk_label_new("Text List"); //创建标题
gtk_table_attach_defaults(GTK_TABLE(table),label,0,10,0,1);
gtk_widget_show (label);
text=gtk_text_new(NULL,NULL);//创建文本构件
gtk_widget_show(text);//显示文本构件
//将文本构件装进表格
gtk_table_attach_defaults(GTK_TABLE(table),text,0,9,1,11);
//创建滚动条并设置其与文本同步
vscrollbar=gtk_vscrollbar_new(GTK_TEXT(text)->vadj);
gtk_widget_show (vscrollbar);//显示垂直滚动条
gtk_table_attach_defaults (GTK_TABLE (table), vscrollbar,9,10,1,11);//将滚动条装进表格
timer=gtk_timeout_add(1000,text_timeout,data);//调用回调函数,每隔1秒输出一行文本
gtk_main ();
return 0;
}
五、 课程设计内容二
Linux内核,简单来说就是一套用来控制计算机最底层的硬件设备,如处理器、内存、硬盘等的一种软件,一般称为操作系统,在Linux术语中称为内核。其中包含的模块有存储管理、CPU和进程管理、文件系统、设备管理和驱动、网络通信,以及系统的初始化(引导)、系统调用等。
这次题目就是要更改Linux内核中的”系统调用”模块,在其中添加自定义的函数,实现功能是文件拷贝。
本次我选用的Linux内核版本是
需要做的工作流程基本如下:
1. 下载并解压内核
首先到官方网站/下载内核,不一定要最新的,以广大用户评定较稳定的内核为优先。我这次选用linux-2.6.37.4 其次打开终端,使用下列命令对其解压到目录/usr/src
sudo tar -xjvf linux-2.6.37.4.tar.bz2 -C /usr/src
该目录用来存放内核的源码。
2. 修改内核
首先要对系统调用模块的源码添加一个自定义函数,即对/usr/src/linux-2.6.37.4/kernel/sys.c进行修改,在该源码的最后添加下列函数的源码:
asmlinkage int sys_mycall(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);
}
else
{
printk("Error!");
}
sys_close(source);
sys_close(dest);
set_fs(fs);
return 10;
}
修改完函数之后,接下来要修改系统调用号所对应的函数名,即修改/usr/src/linux-2.6.32.10/arch/x86/include/asm/unistd_32.h
该文件定义了系统调用号,我们只要找一没被使用的系统调用号,用该号给我们自定义函数使用,比如
#define __NR_sys_mycall 337
/*定义系统调用sys_mycall 的系统调用号为337*/
修改完系统调用号后,接下来要修改系统调用表,即/usr/src/linux-2.6.32.10/arch/x86/kernel/syscall_table_32.S,这个文件是用汇编语言编写的,因此要让自定义的系统调用相对于其他系统调用的顺序337个,写上.long sys_mycall /*337*/
基本修改完以上源码后,接下来对其进行默认的净化、设置等。
使用下列代码对其源码进行处理
sudo make mrproper 净化解压后的源代码
sudo make menuconfig 对内核选项进行配置
如果这一步有错误可能是正在使用的系统没有安装必要的库文件,
如ncurses、libncurses*,这时候需要输入如下指令来安装
首先回到系统根目录
sudo apt-get install ncurses
sudo apt-get install libncurses*
依照提示就能安装好必要的库文件了。然后再回到内核源码的目录
下尝试使用sudo make menuconfig对内核选项进行配置。
sudo make dep 建立模块间的依赖信息
sudo make clean 删除配置时留下的一些不用的文件
3. 编译内核
接下来是最费时间的环节,少则2个小时,多则3个小时的编译,
需要再三确保前面步骤是否正确后再进行下一步。
sudo make bzImage 编译内核
这个过程大概是20多分钟
sudo make modules 编译内核模块
这个过程大概要100分钟~150分钟左右,一般如果有错误,会在前
十几分钟就停止编译并报错。
4. 安装内核
比较简单,只需要两条指令
sudo make modules_install 安装内核模块
sudo make install 安装内核
安装完毕后,需要开机时选择使用新的Linux核心,要做下列修改:
1)复制内核到系统启动引导目录
cp /usr/src/linux-2.6.37.4/arch/i386/boot/bzImage /boot/vmlinuz-2.6.37.4-mykernel
2)创建初始RAM磁盘——initrd 在创建之前先安装必要的程序 apt-get install bootcd-mkinitramfs mkinitramfs -o /boot/initrd.img-2.6.37.4 3)更新grub
在/boot/grub/grub.cfg中, 复制一段旧的核心代码,并将里面linux和
initrd中的路径改为新增的,注意不能用update-grub2 在/boot中复制一个旧的config-xxxxxx做为自己的 4)cd /boot cp initrd.img-2.6.37.4 initrd-2.6.37.4.old 以上是备份initrid,下面是修改 depmod –a update-initramfs -k 2.6.37.4 –c cd /tmp gzip -dc /boot/initrd.img-2.6.37.4| cpio -id touch lib/modules/2.6.37.4/modules.dep find ./ | cpio -H newc -o > /boot/initrd.img-2.6.37.4.new gzip /boot/initrd.img-2.6.37.4.new cd /boot mv initrd.img-2.6.37.4.new.gz initrd.img-2.6.37.4 5)重开机测试
5. 测试功能
用C语言编写测试程序testsys.c,源代码如下: #include <linux/unistd.h>
#include <stdio.h>
#include <asm/unistd.h>
int main(int argc,char**argv)
{
int i=syscall(337,argv[1],argv[2]); /*337为系统调用号*/ printf("successfully!\r\n");
printf("%d",i);
return 1;
}
编译gcc –o testsys testsys.c
运行./testsys test.txt target.txt
系统调用337号功能,拷贝文件test.txt到target.txt 运行结果截图如下:
六、 课程设计内容三
本题是为了更深刻了解模块机制,这种机制可以动态的在内核中添加或者删除模块。模块一旦被插入内核,他就和内核其他部分一样了。
Unix系统将设备分为三种类型字符设备、块设备、网络接口;一般对应这三种模块字符模块、块模块、网络模块。
字符设备,是能够像字节流一样被访问的设备,由字符设备驱动程式来实现这种特性。下面是一个的字符设备驱动程式。
源代码sdev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#define DEFAULT_MSG "default dev data\n" /*默认字符设备数据*/
#define DEVICE_NAME "sdev" /*设备名*/
#define MAXBUF 20 /*设备数据缓冲区大小*/
static unsigned char sdev_buf[MAXBUF]; /*设备内存数据缓冲区*/
static int sdev_open (struct inode *inode, struct file *file);
static int sdev_release (struct inode *inode, struct file *file);
static ssize_t sdev_read (struct file *file, char __user *buf,size_t count, loff_t *pos); static ssize_t sdev_write (struct file *file, const char __user *buf,size_t count, loff_t *pos);
static int sdev_open (struct inode *inode, struct file *file){ return 0;}
static int sdev_release (struct inode *inode, struct file *file){ return 0;}
/*从设备读取count个数据到用户数据区buf中*/
static ssize_t sdev_read (struct file *file, char __user *buf,size_t count, loff_t *pos) {
int size = count < MAXBUF ? count : MAXBUF; /*选择读取数量 count or 数据缓冲区*/
if (copy_to_user(buf, sdev_buf, size)) /*把设备数据copy到用户空间buf中,数量为size*/
return -ENOMEM; /*错误:内存不足*/
return size;
}
/*把buf中count个数据写入设备内存空间中*/
static ssize_t sdev_write (struct file *filp, const char __user *buf,size_t count, loff_t *pos)
{
int size = count < MAXBUF ? count : MAXBUF; /*选择写入数量 count or 数据缓冲区*/
memset(sdev_buf, 0, sizeof(sdev_buf)); /*将设备内存清空*/
if (copy_from_user(sdev_buf, buf, size)) /*把buf中的用户数据写到设备内存sdev_buf中,数量为size*/
return -ENOMEM;
return size;
}
/**/
static struct file_operations sdev_fops = {
.read = sdev_read,
.write = sdev_write,
.open = sdev_open,
.release = sdev_release,
};
static struct cdev *sdev_cdev; /*创建设备指针*/
static int __init sdev_init(void)
{
dev_t dev; /*设备号*/
int error;
/*模块初始化*/
error = alloc_chrdev_region(&dev, 0, 2, DEVICE_NAME);
号*/
if (error)/*返回值不为0表示分配失败*/
{
printk("动态分配设备号失败!\n");
return error;
} /*动态分配一个设备
sdev_cdev = cdev_alloc(); /*新分配一个字符设备对象*/
if (sdev_cdev == NULL)
{
printk("动态分配字符设备对象失败!\n");
unregister_chrdev_region(dev, 2); /*注销一个分配的设备号区域*/ return -ENOMEM;
}
sdev_cdev->ops = &sdev_fops; /*设定字符设备操作函数指针*/
sdev_cdev->owner = THIS_MODULE; /*设备的属主*/
error = cdev_add(sdev_cdev, dev, 1); /*将设备添加到内核中去*/
if (error)
{
printk("设备添加失败!\n");
unregister_chrdev_region(dev, 2); /*注销一个分配的设备号区域*/ cdev_del(sdev_cdev); /*删除字符设备对象*/
return error;
}
memset (sdev_buf, 0, sizeof(sdev_buf)); /*清空设备缓冲区数据*/
memcpy(sdev_buf, DEFAULT_MSG, sizeof(DEFAULT_MSG)); /*设定设备缓冲区默认数据*/
printk("设备缓冲区默认数据: default dev data\n");
return 0;
}
static void __exit sdev_exit(void) /*模块卸载*/
{
unregister_chrdev_region(sdev_cdev->dev, 2);
cdev_del(sdev_cdev);
printk("设备删除成功!\n");
}
module_init(sdev_init);
module_exit(sdev_exit);
MODULE_LICENSE("GPL");
除了编写字符设备驱动程序外,还需要编写一个通过makefile文件来描述hello.c源程序的相互关系并自动维护编译工作。
下面根据通用的Makefile做些微修改后的代码
源代码Makefile:
DEBFLAGS = -O2
EXTRA_CFLAGS += $(DEBFLAGS)
# CFLAGS += -I$(LDDINC)
ifneq ($(KERNELRELEASE),)
# call from kernel build system
xbrige-objs := sdev.o
obj-m := sdev.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
# $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD)/../include modules
endif
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
depend .depend dep:
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) -M *.c > .depend
ifeq (.depend,$(wildcard .depend))
include .depend
endif
然后编写一个测试程序
源文件test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAXBUF 20
int main()
{
int testdev;
int i;
char buf[MAXBUF];
testdev = open("/dev/sdev",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file \n");
return -1;
}
read(testdev,buf,MAXBUF);
for (i = 0; i < MAXBUF;i++)
printf("%c",buf[i]);
close(testdev);
}
一切文件准备就绪之后,接下来要编译并安装这个模块,具体操作如下:
1) 首先cd到上述3个文件的目录下
2) 先输入make,生成sdev.ko sdev.o sdev.mod.c 等文件﹔
3) 然后输入insmod hello.ko插入﹔
4) 之后我们可以输入lsmod 查看
5) 输入cat /proc/devices记下号码(251)
6) 输入mknod /dev/sdev c 251 0 ﹐在dev目录下创建sdev文档
7) 输入gcc -o test test.c编译测试程序﹐再输入./test运行
8) 结果将输出sdev中的字符串default dev data,如截图所示
9) 输入echo xxxxxx>/dev/sdev把sdev中的数据变为xxxxxx﹐并可调用./test
观看
结果﹐此处xxxxxx为Hello HUST
10) 输入rmmod sdev﹐删除sdev﹔
11) 可输入lsmod 和cat /proc/devices查看﹐确认是否删掉﹔
12) 最后输入rm /dev/sdev﹐再输入y确定删掉dev目录下的sdev。
七、 课程设计内容四
本题未完成
八、 课程设计总结
这次课程设计使我对Linux有了深刻的了解,了解Linux下的图形编程、系统编译、系统调用、设备模块等。
首先是在Linux下编程,虽然使用了熟悉的C语言来编写程序,但是其中有些函数并不是能直接使用,比如fopen、fwrite等,需要使用更为低端的系统open、write等,此时就需要用到Linux里的头文件,进而需要了解Linux下的文件目录结构。之后使用Linux下的图形库GTK进行窗口编程,这是一个新接触的图形库,但是跟我们以往在图形学里所学的OpenGL相差不大,可以以类比的方式学习。首先要先寻找并下载相关的图形库。自己在网上寻找常用的GTK函数,然后做了三个简单的程序,再运用上学期所学的进程并发方法,对着3个程序进行并发,由于在较为漂亮的ubantu下运行的,所以呈现出来的窗口还比较赏心悦目,激发了对Linux编程的兴趣。
其次是学习系统调用,本次学习只是对系统调用进行了解,所以并没有使用复杂的程序作为被调用的系统。想要自己添加新的系统调用,那就要对系统内核有所了解,了解其内核代码结构。此间了解了Linux系统版本号的含义2.6.xxx其中6是双数表示一个稳定的版本,假如是
2.5.xxx那就表示是一个测试的版本。本次作为学习选用了一个稳定的版本linux-2.6.37.4。虽然课程设计ppt里已讲述文件系统的目录结构,但凭借这些讯息就想要编译内核是远远不够的。因此而花了大量的时间上网搜寻相关资料来了解如何编译内核。在一步步的对内核进行查找、修改和编译的过程中,大致对内核的结构有所了解,发现内核源码的精炼,以及编译程序make的强大编译能力,能将几时MB的内核源码,编译成几GB的核心,当然这需要相当多的时间作为代价去编译,因此在编译之前只能小心翼翼的检查每一个步骤及其含义,尤其是修改syscall_table_32.S这个文件时,一不小心就会修改错误,导致编译错误,之后发现其为一个汇编语言所编写的文件,需要相对于文件行数来进行编写,就像枚举一个数组,需要按照顺序编写。最后编译完成,安装内核,修改启动引导,最后测试系统调用号。其结果只是一个非常简单的文件拷贝,但其中需要学习的过程非常之辛苦,要花费大量时间去理解和测试。
然后是添加一个设备驱动程序,在做完先前一个题目后,发现如果要添加一个系统调用,又要重新修改并编译一次内核,那所需要花费的时间实在是太多太多了,非常不方便,于是有了新的机制,模块机制。假设所需要的程序都已经编写好模块化了,那么从安装模块到使用该设备驱动程序就是几分钟的事,非常方便,如果不需要,随时也可以卸载驱动程序。刚开始看到这道题时,感觉会比较简单,但其实也不然。难点在于编写这些模块化的代码是相当复杂且麻烦的,我的感觉就是用复杂并规范的程序代码换取内核上的编译时间。编写一个驱动设备程序代码,其规划化要求很高,刚开始看到的时候非常抽象,很难一眼就看出来某些代码的含义是什么,后来就算一一解析,也无法完全掌握其含义,也许是涉及到太基层的关系。因此最后在一知半解下使用了通用的makefile来对自己的设备驱动源码编译。编译完后就是对模块的安装并操作,如同前面所说,这个过程是比较快的,通过几条指令就能将一个驱动安装好并使用。
最后通过这次课程设计我学到了很多关于Linux系统的知识,虽然都是比较基层的系统原理学习,但这跟以前常用的Windows系统很有区别,因此大致了解操作系统和操作系统之间的区别是在什么地方。除此之外,Linux系统是比较开放的,什么都是开源,因此在对学习系统原理上非常有帮助。另外我还了解到了Linux相比Windows还有很多优点,比如服务器的架设、灵活性、多平台等
九、 参考文献
操作系统原理 庞丽萍 华中科技大学出版社
Linux设备驱动(第二版,中文) 魏永明 中国电力出版社
《操作系统原理》实验报告院(部):管理工程学院专业:信息管理与信息系统实验项目:实验一二三五班级:信管102姓名:学号:目录引言.…
西安郵電大學操作系统设计报告题目进程线程互斥锁院系名称计算机学院班级1104学生姓名赵大伟学号8位04113124指导教师舒新峰设…
操作系统课程论文院系班级姓名学号指导教师完成时间东莞理工学院摘要本文分析面向对象教学操作系统EOS的系统结构和代码构成通过源代码分…
操作系统课程设计实验报告姓名学号班级地点20xx年月日任务说明共完成四个任务任务一IO系统调用开销比较任务二实现一个简单的shel…
操作系统课程设计总结报告学期20xx20xx学年第2学期学院软件学院学号姓名20xx年7月3日本学期开设了操作系统课程主要学习了计…
《操作系统原理》实验报告院(部):管理工程学院专业:信息管理与信息系统实验项目:实验一二三五班级:信管102姓名:学号:目录引言.…
操作系统课程设计报告专业学号姓名提交日期操作系统课程设计报告设计目的1本实验的目的是通过一个简单多用户文件系统的设计加深理解文件系…
上海电力学院计算机操作系统原理课程设计报告题目名称编写程序模拟虚拟存储器管理姓名杜志豪学号20xx1798班级20xx053班同组…