基本指令练习化学实验基本方法报告

微机员测试题库_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
评价文档:
18页¥10.0059页免费3页免费6页¥5.006页¥5.004页¥5.003页免费13页1下载券6页免费2页免费
喜欢此文档的还喜欢6页2下载券8页1下载券63页1下载券8页免费21页免费
微机员测试题库|微​机​员​测​试​题​库
把文档贴到Blog、BBS或个人站等:
普通尺寸(450*500pix)
较大尺寸(630*500pix)
你可能喜欢扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
PLC编程软件使用及基本指令编程练习
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer-4.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口操作系统实验
计算机操作系统实践教程(韩立毛:南京大学出版社,2011.10)
Linux上机基础实验
进程的创建实验
进程的控制实验
进程的互斥实验
信号通信实验
消息传递实验
分区与页式存储管理实验
Shell与系统调用实验
【以下作废】
实验1 操作系统初识
一、实验目的
通过Internet资源,从感性上认识操作系统的功能和应用,了解市场上常见的操作系统,为进一步的学习操作系统打下基础。
2. 熟悉Linux实验环境,掌握一些有关基础知识和实验技能。
3. 掌握远程登录命令telnet的使用;熟悉使用Linux字符界面的常用命令。
4. 分析系统目录结构,获取联机帮助信息。
5. 学习并掌握Linux编辑器vi和编译工具gcc的使用方法。
6. 掌握在Linux操作系统环境上编辑、编译、调试、运行一个C语言程序的全过程。
二、相关知识准备
1. 登录与修改口令
通常,一台机器上会安装多个操作系统,因此,在开机时会让用户选择进入哪个操作系统。当你选择进入Linux后,Linux初始化过程中会显示大量初始化信息,要求学习者逐渐读懂这些初始化信息。
Linux在相应初始化完成后,会在屏幕上显示“login:”此时输入用户名(即帐号)并键入回车,则系统显示”passwd:”,然后输入口令并键入回车。此时,系统验证所键入的用户名和口令,若正确则成功进入系统。若用户希望修改口令,则可以在成功进入系统后,在命令提示符($或#)后输入“passwd”并键入回车,则系统显示”new
password:”,当用户键入新口令并键入回车后,系统再显示“retry new
password:”,此时需要再次键入刚才输入的新口令并键入回车,则系统接收并记住新口令。如果用户在登录时忘记了密码,则只能找系统管理员解决(取消用户密码:passwd
-d 用户名)。
2. 退出、注销与关机
当用户不再使用Linux时,在离开前,通常应键入“logout”命令或Ctrl+D来退出帐号。在Linux下,涉及到关机或重启的命令有:
(1)halt命令。这是最常用的关机方式。
(2)reboot命令。用户只是想退出操作系统,并不想关机,还想再进入其他操作系统。
(3)shutdown命令。在多用户系统中,系统管理员在关机前,通知各用户即将关机,以便给各用户留下一定的时间作保存和退出工作。
3. 建立用户账号,设置相应密码
在超级用户(root)命令提示符#下,教师根据需要建立适当数量的账号(成组创建:./add_u B
B08),以便学生上机登录使用。建立账号的命令:
#adduser &用户名&(如用户名:B080101)
#passwd &用户名&(初始为空)
4. 远程登录命令telnet的使用
telnet命令的使用形式如下:
telnet [主机名][主机的IP地址]& 如:telnet
192.168.0.254
从安装了Windows操作系统的机器登录到Linux服务器,telnet会话过程的具体操作如下:
(1)单击“开始”,选择“程序”,选择“MS-DOS”方式(或“附件”-“命令提示符”或“开始”,选择“运行”);
(2)在提示符C:\&下输入telnet [主机的IP地址];或“运行”中键入IP地址。
(3)如果连接成功,则会出现登录界面,通过用户账号和口令登录。
5. Linux的基本使用命令
(1)使用passwd命令修改用户密码。
(2)使用who命令查看当前登录在系统中的用户列表。
(3)熟练使用帮助信息,使用man命令获得ls命令的使用手册。
(4)熟悉cat、ls、cd、pwd、rm、mv、cp、mkdir、rmdir、more等命令的使用。
(5)查看并记录所在机器Linux操作系统目录结构。
(6)Linux常用命令,如表1-1所示。
表1-1 Linux常用命令
创建新用户
#adduser↙
$cp 源文件 目标文件↙
$rm 文件名
创建新目录
$mkdir 目录名↙
$rmdir 目录名↙
显示当前目录
$pwd↙
显示进程状态
$ps↙
显示当前目录下内容
$ls -l↙
显示文件内容
$cat 文件名↙
转换当前目录
$cd 路径名↙
$mv 源文件 目标文件
$ls &l|more↙
改变文件权限
$chmod 777 文件名↙
$clear↙
6. vi编辑器的基本操作
(1) 进入vi编辑器:$vi文件名↙
(2) vi编辑器常用命令
表1-2 vi编辑器常用命令
输入的字符
说&&&&&&&&&
进入命令模式
在光标前输入文字
在光标后输入文字
删除光标后输入的文字
删除当前行
删除后内容自动进入剪贴板
向前查找字符字符串
然后输入n,则继续向前查
向后查找字符字符串
将光标所在单词拷入剪贴板
将光标所在行拷入剪贴板
将剪贴板内容贴在光标后
不保存退出……..0
:q!表示强制退出
:w!表示强制保存
将第3行至第7行拷到第9行
将第3行至第7行移到第9行
取消前次命令
:g/abc/s//123/g
将全部的abc替换成123
7. 编译工具gcc的使用
Linux的编译器是gcc。gcc软件包支持C、C++。gcc的可执行文件在/usr/bin/gcc下,/lib和/usr/lib目录下是库文件。/usr/include目录下是头文件。
gcc编译常用格式为:$gcc源文件名↙
例如:$gcc aaa.c↙,将生成默认可执行文件a.out
或者$gcc -o 目标文件名 源文件名↙
例如:$gcc &o aaa& aaa.c或$cc aaa.c &o aaa.out
8. 程序的运行
$./可执行文件名↙(例如:$./aaa.out↙)
三、实验内容
1.熟悉开机后登录Linux系统和退出系统的过程。
2.熟悉Linux字符界面,练习并掌握常用的Linux操作命令。
3.学习使用Linux的在线求助系统,如man和help命令等。
4.掌握一种Linux的编辑器,特别是字符界面的vi工具的使用。
5.使用vi编辑一个打印“Hello,I am a C
program”字串的C语言程序,然后编译并运行它。熟悉gcc、gdb等编译器、调试器的使用。
int main( )
int i=getuid( );
printf("Hello world! This is my uid: %d\\n",i);
6.使用搜索引擎、、www.wikipedia.org回答下列问题。
(1)什么是操作系统?你买回一台新计算机后,要装的第一个软件是什么?为什么必须在计算机上安装操作系统?
(2)目前市场上常见的操作系统有哪些类型?有哪些应用?
(3)请尽可能多地罗列出目前市场上已有的操作系统产品,分别给出这些操作系统所取得的成就,采用的新技术,以及这些操作系统的特点、特色和不足,指明它们的开发公司和开发年代。
(4)推动操作系统发展的主要动因有哪些?列举出保证操作系统每次革命性发展的支撑技术。
(5)Linux与Windows系统的主要区别是什么?
四、实验报告
1. 实验目的与实验内容。
2. 实验主要步骤。
3. 实验结果。
实验2 进程控制
一、实验目的
1. 了解和熟悉Linux进程控制常用的系统调用(open、 creat、 close 、 read、 write、
lseek、fork、wait、sleep、exit、exec)。
2. 掌握系统调用的简单编程。
3. 进一步掌握C语言程序的开发方法,阅读、调试C程序并编写简单的进程创建程序。
4. 通过有关进程控制的应用实例,深刻理解进程的管理过程。
二、相关知识准备
1. 进程状态命令
表2-1 Linux中的进程状态命令
ps输出中的标题
显示所有正在执行的进程
进程标识号
列出当前正在运行的进程的基本信息
开始这个进程的终端
列出所有用户的基本信息
进程的累计执行时间,以分和秒表示
正在执行的命令名
2. 进程控制命令
(1)kill &进程标识符&,向进程发送终止信号,撤消进程。
(2)nice命令用于改变进程的优先级,使用格式为:$ nice [+][-]n[PID]。
(3)创建后台进程:在命令后输入后台命令符&,如$ sleep 50 &
,表示要创建一个睡眠时间为50秒的进程。
3. 进程控制相关的系统调用
(1)fork()。创建一个子进程,用它创建的子进程是fork调用者进程(即父进程)的复制品,即进程映象。除了进程标识符以及与进程特性有关的一些参数外,其它与父进程相同,与父进程共享文本段和打开的文件,并都受进程调度程序的调度。
如果创建进程失败,则fork()返回值为-1;如果创建进程成功,则在父进程中返回值是子进程号,子进程中返回的值是0。m=fork()。
(2)wait()。父进程处于阻塞(或等待)状态,等待子进程执行完成终止后继续工作。其返回值为子进程号。n=wait()。
(3)exit()。子进程自我终止,释放所占资源,通知父进程可以删除自己。此时它的状态变成P_state=SZOMB。
(4)getpid()。获得进程的进程号,为正整数。p=getpid()。
Linux中与进程控制相关的几个主要系统调用如表2-2所示。
表2-2 Linux中与进程控制相关的几个主要系统调用
返回值的解释
创建一个子进程
对父进程:返回子进程号
对子进程:返回0
错误:返回-1
execve(char *file, char **argv, char **envp)
用指定程序覆盖当前程序代码
pid_t wait(int *statloc)
等待进程终止
正确:子进程的ID
获得进程号
void exit(int status)
进程正常结束
三、实验内容
1. 在Windows下,使用性能监视器来观察进程运行情况和CPU工作情况。
2. 在Linux下,用top命令显示进程动态执行时的系统变化情况。
3. 在Linux下,熟悉进程状态命令(ps)的使用;用pstree观察系统进程层次、分析进程状态。
4. 在Linux下,熟悉进程的控制命令(kill、nice、创建后台进程)的使用。
5. 在Linux下,分析程序的功能与运行结果,熟悉Linux进程控制常用的系统调用。
(1)分析下列程序的功能与程序的运行结果。
{&& int p1,p2;
while((p1=fork())==
-1);&&&&&&&
if (p1==0)&
putchar('b');&&&&&&&
{ &&while((p2=fork( ))= =
if(p2==0)&
putchar('c');&&
putchar('a');&&&&
(2)分析使用wait()和exit()控制进程的程序功能与运行结果。
if(pid=fork())
printf("it is parent process\n");}
{ printf("it is child process\n");
printf("it is end\n");
(3)写出下列程序的功能,并分析程序的运行结果。
{&& int p1,p2,i;
while((p1=fork())==
-1);&&&&&&&&&
if (p1==0)
& for(i=0;i&10;i++)
printf("daughter %d\n",i);
&&&&&&&&&&&&&&&
while((p2=fork())== -1);&&
if(p2= =0)
&&&&&&&&&&&&&&&&&
for(i=0;i&10;i++)
&&&&&&&&&&&&&&&&&&&&&&
printf("son %d\n",i);
&&&&&&&&&&&&&&&&&
for(i=0;i&10;i++)
&&&&&&&&&&&&&printf("parent&
(4)分析并完善下列程序。程序的功能是:输入两个整数并求和输出,然后创建一个子进程,当进程调度程序调度到父进程或子进程时将输出不同的信息。
{ int i,j,k,
scanf("%d%d",&j,&k);
printf("sum=%d \n",sum);
while(&&&&&&&&&&
{ printf("i=%d\n",i); }
if(&&&&&&&&&&
printf("it is parent process!\n");
printf("it is child process!\n");
分析并完善下列程序。程序的功能是:如果父进程要通过建立子进程在同一显示器上分别循环显示“Parents”和“Children”,循环次数由n决定。
{ int pid,n; n=1;
if((&&&&&&&&&&
While(n&10)
{ printf("%d",n++);
&&&printf("Parents");
While(n&10)
&&&&&&&&&&&&&&&&&
&&&&printf("Children");
四、实验报告
1. 实验目的与实验内容。
2. 实验主要步骤。
3. 实验程序的功能与结果分析。
4. 完善后的程序。
一、实验目的
1. 练习使用wall、write、mesg命令来实现用户之间的信息交流。
2. 练习使用系统调用pipe()实现进程的管道通信。
3. 使用进程的“软中断”实现同一用户的进程之间通信。
4. 掌握进程通信的基本方法。
二、相关知识准备
1. 有关的信息发送命令
(1)wall命令:功能是对全部已登录的用户发送信息,用户可以先把要发送的信息写好存入一个文件中,然后输入:# wall
这样就能对所有的用户发送信息了。符号“&”表示输入重定向。例如:
# wall "Thank you! "
Broadcast message from root (tty1) Fri Nov 26 14:15:07 1999…
Thank you!
执行以上命令后,用户的屏幕上显示出“Thank
you!”信息后,并不出现系统提示符$(#),再次按回车键后,屏幕出现系统提示符。
(2)write命令:功能是向系统中某一个用户发送信息。该命令的一般格式为:
write 用户帐号 [终端名称]
例如: $ write Guest hello
此时系统进入发送信息状态,用户可以输入要发送的信息,输入完毕,希望退出发送状态时,按组合键&
Ctrl+c&即可。
(3)mesg命令:设定是否允许其他用户用write命令给自己发送信息。如果允许别人给自己发送信息,输入命令:# mesg
y,否则,输入:# mesg n。
对于超级用户,系统的默认值为n;而对于一般用户系统的默认值为y。如果mesg后不带任何参数,则显示当前的状态是y还是n。
2. 进程的“软中断”通信
它可用于同一用户的进程之间通信。其方式是:一个进程通过系统调用kill(pid,sig)
向同一用户的其它进程pid发送一个软中断信号:另一进程通过系统调用signal(sig,func)捕捉到信号sig后,执行予先约定的动作func,从而实现这两个进程间的通信。
(1)发送信号kill(pid,sig),本进程将指定信号sig发送给指定进程pid,其中参数为pid进程号,pid与sig均为整数值。
(2)接收信号signal(sig,func),本进程接收到其它进程发送给它的信号后,完成指定的功能func,其中参数func一般是函数。
3. 进程管道的通信
建立进程间的管道,格式为:
int fd[2];
其中,fd[1]是写端,向管道中写入;fd[0]是读端,从管道中读出。
本质上将其当作文件处理。进程间可通过管道,用write与read来传递数据,但write与read不可以同时进行,在管道中只能有4096字节的数据被缓冲。
三、实验内容
1. 分析程序的功能,并写出程序的运行结果。
int func();
{ int i,j;
&&&&signal(17,func);
& if(i=fork())
printf("Parent: Signal 17 will be send to Child! \n");
& kill(i,17);
&printf("Parent: finished! \n");
{ sleep(10);
printf("Child: A signal from my Parent is received! \n")
exit(0); &&}
{ printf("It is signal 17 processing function! \n";)
2. 分析程序的功能,并写出程序的运行结果。
{& int p1,p2,i;
while((p1=fork())==
-1);&&&&&&
if (p1= =0)
{& lockf(1,1,0);
for(i=0;i&10;i++)
printf("daughter %d\n",i);
lockf(1,0,0);&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&
while((p2=fork())==-1);&
if (p2= =0){
lockf(1,1,0);&&&&&&&
for(i=0;i&10;i++)
printf("son %d\n",i);
lockf(1,0,0);&&&&&&&&&&&
&&&&&&&&&&&&&&else{
&&&&&&&&&&&&&&&&&&&&&&&
lockf(1,1,0);&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&
for(i=0;i&10;i++)
&&&&&&&&&&&&&&&&&&&&&&&&&&&
printf(" parent %d\n",i);
&&&&&&&&&&&&&&&&&&&&&&&
lockf(1,0,0);&&&&&&&&
3.分析并完善下列程序。程序的功能是:建立一个pipe,同时父进程产生一个子进程,子进程向pipe中写入一个字符串,父进程从中读出该字符串,并每隔3秒种输出显示一次。每隔3秒输出一行*********
Good-night!,用Ctrl+C停止,回到用户目录下。
{ int x,fd{2};
& char S[30];
& pipe(fd);
& for (;;)
& { x=fork();
(&&&&&&&&&&&
{ sprintf(S, "Good-night!\n");
&&&write(fd[1],S,20);
&&&&&&&&&&&&&&&
&&&exit(0);
{ wait(0);
&&&read(fd[0],S,20);
&&&printf("*********
%s\n",S); }
四、实验报告
1. 实验目的与实验内容。
2. 实验主要步骤。
3. 实验程序的功能与结果分析。
4. 完善后的程序。
实验4 &进程通信(二)
一、实验目的
了解和熟悉共享存储机制。
二、实验内容
编制一长度为1k的共享存储区发送和接收的程序。
三、实验知识准备
1、共享存储区机制的概念
共享存储区(Share&
Memory)是UNIX系统中通信速度最高的一种通信机制。该机制可使若干进程共享主存中的某一个区域,且使该区域出现(映射)在多个进程的虚地址空间中。另一方面,一个进程的虚地址空间中又可连接多个共享存储区,每个共享存储区都有自己的名字。当进程间欲利用共享存储区进行通信时,必须先在主存中建立一共享存储区,然后将它附接到自己的虚地址空间上。此后,进程对该区的访问操作,与对其虚地址空间的其它部分的操作完全相同。进程之间便可通过对共享存储区中数据的读、写来进行直接通信。图示列出二个进程通过共享一个共享存储区来进行通信的例子。其中,进程A将建立的共享存储区附接到自己的A区域,进程B将它附接到自己的B区域。
应当指出,共享存储区机制只为进程提供了用于实现通信的共享存储区和对共享存储区进行操作的手段,然而并未提供对该区进行互斥访问及进程同步的措施。因而当用户需要使用该机制时,必须自己设置同步和互斥措施才能保证实现正确的通信。
2、涉及的系统调用
(1)shmget( )
创建、获得一个共享存储区。
系统调用格式:
shmid=shmget(key,size,flag)
该函数使用头文件如下:
int& shmget(key,size,flag);
int& size,
其中,key是共享存储区的名字;size是其大小(以字节计);flag是用户设置的标志,如IPC_CREAT。IPC_CREAT表示若系统中尚无指名的共享存储区,则由核心建立一个共享存储区;若系统中已有共享存储区,便忽略IPC_CREAT。
操作允许权&&&&&&&&&&&&&&&&&
用户可读&&&&&&&&&&&&&
&&&&&&&00400
用户可写&&&&&&&&&&&&&&&&&&&&
小组可读&&&&&&&&&&&&&&&&&&&&
小组可写&&&&&&&&&&&&&&&&&&&&
其它可读&&&&&&&&&&&&&&&&&&&&
其它可写&&&&&&&&&&&&&&&&&&&&
控制命令&&&&&&&&&&&&&&&&&&&
IPC_CREAT&&&&&&&&&&&&&&&
IPC_EXCL&&&&&&&&&&&&&&&&
例:shmid=shmget(key,size,(IPC_CREAT|0400))
创建一个关键字为key,长度为size的共享存储区
(2)shmat( )
共享存储区的附接。从逻辑上将一个共享存储区附接到进程的虚拟地址空间上。
系统调用格式:
&&&&&&&&&&&&&
virtaddr=shmat(shmid,addr,flag)
该函数使用头文件如下:
参数定义:
char& *shmat(shmid,addr,flag);
int& shmid,
其中,shmid是共享存储区的标识符;addr是用户给定的,将共享存储区附接到进程的虚地址空间;flag规定共享存储区的读、写权限,以及系统是否应对用户规定的地址做舍入操作。其值为SHM_RDONLY时,表示只能读;其值为0时,表示可读、可写;其值为SHM_RND(取整)时,表示操作系统在必要时舍去这个地址。该系统调用的返回值是共享存储区所附接到的进程虚地址viraddr。
(3)shmdt( )
把一个共享存储区从指定进程的虚地址空间断开。
系统调用格式:
&&&&&&&&&&&&
shmdt(addr)
该函数使用头文件如下:
参数定义:
int& shmdt(addr);
其中,addr是要断开连接的虚地址,亦即以前由连接的系统调用shmat(
)所返回的虚地址。调用成功时,返回0值,调用不成功,返回-1。
(4)shmctl( )
共享存储区的控制,对其状态信息进行读取和修改。
系统调用格式:
&&&&&&&&&&&&&&
shmctl(shmid,cmd,buf)
该函数使用头文件如下:
参数定义:
&&&&&&&&&&&&
int& shmctl(shmid,cmd,buf);
&&&&&&&&&&&&
int& shmid,
&&&&&&&&&&&&
struct& shmid_ds& *
其中,buf是用户缓冲区地址,cmd是操作命令。命令可分为多种类型:
① 用于查询有关共享存储区的情况。如其长度、当前连接的进程数、共享区的创建者标识符等;
② 用于设置或改变共享存储区的属性。如共享存储区的许可权、当前连接的进程计数等;
③ 对共享存储区的加锁和解锁命令;
④ 删除共享存储区标识符等。
上述的查询是将shmid所指示的数据结构中的有关成员,放入所指示的缓冲区中;而设置是用由buf所指示的缓冲区内容来设置由shmid所指示的数据结构中的相应成员。
三、实验内容
1、为了便于操作和观察结果,用一个程序作为“引子“,先后fork()两个子进程,server和client,进行通信。
2、server端建立一个key为75的共享区,并将第一个字节置为-1,作为数据空的标志。等待其他进程发来的消息。当该字节的值发生变化时,表示收到了信息,进行处理。然后再次把它的值设为-1,如果遇到的值为0,则视为为结束信号,取消该队列,并退出server。server每接收到一次数据后显示“(server)received”。
3、client端建立一个key为75的共享区,当共享取得第一个字节为-1时,server端空闲,可发送请求。client随即填入9到0。期间等待
server 端的再次空闲。进行完这些操作后,client退出。client每发送一次数据后显示“(client)sent”。
4、父进程在server和client均退出后结束。
5、出现上述应答延迟的现象是程序设计的问题。当client端发送了数据后,并没有任何措施通知server端数据已经发出,需要由client的查询才能感知。此时,client端并没有放弃系统的控制权,仍然占用CPU的时间片。只有当系统进行调度时,切换到了server进程,再进行应答。这个问题,也同样存在于server端到client的应答过程中。
#define& SHMKEY& 75
shmid,i;&& int&
void& client( )
shmid=shmget(SHMKEY,);&&&&&
addr=shmat(shmid,0,0);&&&&&&&&&&
for (i=9;i&=0;i--)
& {& while (*addr!=-1);
printf("(client) sent\n");
void& server()
shmid=shmget(SHMKEY,|IPC_CREAT);
addr=shmat(shmid,0,0);&&&&&&&
while (*addr==-1);
printf("(server) received\n");
}while (*addr);
shmctl(shmid,IPC_RMID,0);&&&&
& &while ((i=fork())==-1);
&& if (!i) server( );
system(“ipcs& -m”);
&& while ((i=fork())==-1);
&& if (!i) client( );
&& wait(0);
&& wait(0);
6、运行结果分析
和预想的完全一样。但在运行过程中,发现每当client发送一次数据后,server要等待大约0.1秒才有响应。同样,之后client又需要等待大约0.1秒才发送下一个数据。
7、比较两种消息通信机制中数据传输的时间
由于两种机制实现的机理和用处都不一样,难以直接进行时间上的比较。如果比较其性能,应更加全面的分析。
(1)消息队列的建立比共享区的设立消耗的资源少。前者只是一个软件上设定的问题,后者需要对硬件的操作,实现内存的映像,当然控制起来比前者复杂。如果每次都重新进行队列或共享的建立,共享区的设立没有什么优势。
(2)当消息队列和共享区建立好后,共享区的数据传输,受到了系统硬件的支持,不耗费多余的资源;而消息传递,由软件进行控制和实现,需要消耗一定的cpu的资源。从这个意义上讲,共享区更适合频繁和大量的数据传输。
(3)消息的传递,自身就带有同步的控制。当等到消息的时候,进程进入睡眠状态,不再消耗cpu资源。而共享队列如果不借助其他机制进行同步,接收数据的一方必须进行不断的查询,白白浪费了大量的cpu资源。可见,消息方式的使用更加灵活。
四、实验报告
1. 实验目的与实验内容。
2. 实验主要步骤。
3. 实验程序的功能与结果分析。
4. 完善后的程序。
实验5& 存储管理
一、实验目的
1. 掌握内存管理的常用命令和系统调用;
2. 在Windows下,使用性能监视器来观察内存(管理)工作情况并作出解释;
3. 掌握动态存储分配相关函数malloc、free等。
二、相关知识准备
内存管理所用到的文件include/linux/mm.h,主要包括两个数据结构:mem_map、free_area。
2. 相关函数
(1)内存动态分配函数
void& *malloc(size_t&
该函数分配指定大小size个字节的内存空间,成功时返回分配内存的指针(即所分配内存的地址)。该内存区域没有清空。
(2)void free(void * addr);
该函数释放由malloc()分配的内存,addr是要释放内存空间的起始地址,并且addr必须是被以前malloc(
)调用返回的。
3. 需要动态存储分配的原因
无论是系统软件还是应用程序,实际的程序中经常需要设计和处理动态数据结构。操作系统软件本身,也有很多动态数据结构,如进程表,操作系统的依次运行中,进程的个数也是一直在变化着的。所有这些动态数据结构,由于其长度的变化,因此存在其存储空间的分配方案难以确定。如果静态分配(即在编程时确定长度),那么由于无法准确预测和预留所需长度,因此存在这样的问题:预留空间大时,可能存在浪费;预留空间小时,可能不够用。因此,通常对这种动态数据结构的分配采用动态分配方案,就是在程序运行过程中动态分配这些动态数据结构所需的存储空间,并可以动态伸缩。
4. 如何进行动态分配?
由于这是一个普遍的需求,因此由操作系统来解决,如Linux下的malloc系统调用。
三、实验内容
1. 分析并完善下列程序的功能。程序的功能是:申请内存、使用内存以及释放一块内存。
int main(void)
if((&&&&&&&&&&
)malloc(10))==NULL)
{ printf("not enough memory to allocate buffer\\n");
exit(1); }
strcpy(str,"hello");
printf("string is %s\\n",str);
&&&&&&&&&&&&&&&
分析并完善下列程序的功能。程序的功能是:在打开文件后,通过fstat()获得文件长度,然后通过malloc()系统调用申请响应大小的内存空间,通过read()将文件内容完全读入该内存空间,并显示出来。
fd=open("/home/B0803/li.c",0);
fstat(&&&&&&&&&&&
len=ps.st_
tp=malloc(len);
read(&&&&&&&&&&&&&&
printf("%s\\n",tp);
close(fd);}
3.设计一个虚拟存储区和内存工作区,并使用最佳淘汰算法(OPT)、先进先出的算法(FIFO)和最近最久未使用算法(LRU)计算访问命中率。命中率=1-页面失效次数/页地址流长度。
#define TRUE 1
#define FALSE 0
#define INVALID -1
#define NULL& 0
total_instruction&
32&&&&&&&&&&&&&&
clear_period&
50&&&&&&&&&&
struct&&&&&&&&&&&&&&&&&&&&&
pn,pfn,counter,
pl[total_vp];&&&&&&&&&&&&&&
pfc_struct{&&&&&&&&&&&&&&&&&
&&& struct
pfc_struct *
typedef struct pfc_struct pfc_
pfc[total_vp],*freepf_head,*busypf_head,*busypf_
int diseffect,&
a[total_instruction];
int page[total_instruction],&
offset[total_instruction];
int& initialize(int);
int& FIFO(int);
int& LRU(int);
int& LFU(int);
int& NUR(int);
int& OPT(int);
int main( )
{ int s,i,j;
& srand(10*getpid());
s=(float)319*rand(
)//2+1;& //
{& if(s&0||s&319)
{ printf("When i==%d,Error,s==%d\n",i,s);
exit(0); }
a[i]=s;&&&&&&&&&&&&&&&&&&&&&&&&&&&
a[i+1]=a[i]+1;&&&&&&&&&&&&&&&&&&&&
a[i+2]=(float)a[i]*rand( )//2;
a[i+3]=a[i+2]+1;&&&&&&&&&&&&&&&&&&
s=(float)(318-a[i+2])*rand( )//2+a[i+2]+2;
if((a[i+2]&318)||(s&319))
printf("a[%d+2],a number which is :%d and s==%d\n",i,a[i+2],s);
for (i=0;i
page[i]=a[i]/10;
offset[i]=a[i];
for(i=4;i&=32;i++)&&
printf("---- page frames---\n",i);
&& return 0;
initialize(total_pf)&&&&&&&&&&&&&
total_&&&&&&&&&&&&&&&&&&&&&&&&&
diseffect=0;
pl[i].pn=i;
pl[i].pfn=INVALID;&&&&&&&
pl[i].counter=0;
pl[i].time=-1;&&&&&&&&
pfc[i].next=&pfc[i+1];
pfc[i].pfn=i;
pfc[total_pf-1].next=NULL;
pfc[total_pf-1].pfn=total_pf-1;
freepf_head=&pfc[0];&&&&&&&&
FIFO(total_pf)&&&&&&&&&&&&&
total_&&&&&&&&&&&&&&&&&&&
pfc_type *p;
initialize(total_pf);&&&&&&&&
busypf_head=busypf_tail=NULL;
if(pl[page[i]].pfn==INVALID)&&
diseffect+=1;&&&&&&&&&&&&&&&&&
if(freepf_head==NULL)&&&&&&&&
p=busypf_head-&
pl[busypf_head-&pn].pfn=INVALID;
freepf_head=busypf_&
freepf_head-&next=NULL;
busypf_head=p;
p=freepf_head-&&&&&&&&&
freepf_head-&next=NULL;
freepf_head-&pn=page[i];
&&&pl[page[i]].pfn=freepf_head-&
if(busypf_tail==NULL)
busypf_head=busypf_tail=freepf_
busypf_tail-&next=freepf_&
busypf_tail=freepf_
freepf_head=p;
printf("FIFO:%6.4f\n",1-(float)diseffect/320);
(total_pf)&&&&&&
int total_
min,minj,i,j,present_
&initialize(total_pf);
&present_time=0;
if(pl[page[i]].pfn==INVALID)&&&&&&&
diseffect++;
if(freepf_head==NULL)&&&&&&&&&&&&&
min=32767;
&&&&&&&&&&
if(min&pl[j].time&&pl[j].pfn!=INVALID)
&&&&&&&&&&&&&&&&&&&
min=pl[j].
freepf_head=&pfc[pl[minj].pfn];&&
//腾出一个单元
pl[minj].pfn=INVALID;
pl[minj].time=-1;
freepf_head-&next=NULL;
pl[page[i]].pfn=freepf_head-&&&
//有空闲页面,改为有效
pl[page[i]].time=present_
freepf_head=freepf_head-&&&&&&
//减少一个free 页面
pl[page[i]].time=present_&&&&&&&
//命中则增加该单元的访问次数
present_time++;
printf("LRU:%6.4f\n",1-(float)diseffect/320);
OPT(total_pf)&&&&&&
int total_
{int i,j, max,maxpage,d,dist[total_vp];
pfc_type *t;
initialize(total_pf);
{ //printf("In OPT for
1,i=%d\n",i);&
//i=86;i=176;206;250;220,221;192,193,194;258;274,275,276,277,278;
if(pl[page[i]].pfn==INVALID)&&&&&
diseffect++;
if(freepf_head==NULL)&&&&&&&&
{for(j=0;j
&&&&&&&&&&&&&&
if(pl[j].pfn!=INVALID) dist[j]=32767;&
else dist[j]=0;
for(j=i+1;j
&&&&&&&&&&&
if(pl[page[j]].pfn!=INVALID)
&&&&&&&&&&&&
dist[page[j]]=d;
&&&&&&&&&&&&
&&&&&&&&&&&&
&&&&&&&&&&&
&&&&max=dist[j];
maxpage=j;
&&&&&&&&&&
freepf_head=&pfc[pl[maxpage].pfn];
&&&&&&&&&&
freepf_head-&next=NULL;
&&&&&&&&&&
pl[maxpage].pfn=INVALID;
pl[page[i]].pfn=freepf_head-&
freepf_head=freepf_head-&
printf("OPT:%6.4f\n",1-(float)diseffect/320);
程序实现:首先用srand( )和rand(
)函数定义和产生指令序列,然后将指令序列变换成相应的页地址流,并针对不同的算法计算出相应的命中率。
(1)通过随机数产生一个指令序列,共320条指令。指令的地址按下述原则生成:
A:50%的指令是顺序执行的
B:25%的指令是均匀分布在前地址部分
C:25%的指令是均匀分布在后地址部分
具体的实施方法是:
A:在[0,319]的指令地址之间随机选取一起点m
B:顺序执行一条指令,即执行地址为m+1的指令
C:在前地址[0,m+1]中随机选取一条指令并执行,该指令的地址为m’
D:顺序执行一条指令,其地址为m’+1
E:在后地址[m’+2,319]中随机选取一条指令并执行
F:重复步骤A-E,直到320次指令
(2)将指令序列变换为页地址流
设:页面大小为1K;
用户内存容量4页到32页;用户虚存容量为32K。
在用户虚存中,按每K存放10条指令排列虚存地址,即320条指令在虚存中的存放方式为:
第 0 条-第 9 条指令为第0页(对应虚存地址为[0,9])
第10条-第19条指令为第1页(对应虚存地址为[10,19])
………………………………
第310条-第319条指令为第31页(对应虚存地址为[310,319])
按以上方式,用户指令可组成32页。
四、实验报告
1. 实验目的与实验内容。
2. 实验主要步骤。
3. 实验程序的功能与结果分析。
4. 完善后的程序。
实验6 文件管理
一、实验目的
1.学习和掌握文件控制的基本原理和常用的系统调用;
2. 观察文件系统工作情况。
3. 了解/proc等有关系统文件。
二、实验相关知识准备
1.目录/proc下与系统相关的文件和目录
(1) /proc/$pid/fd:这是一个目录,该进程($PID号码进程)每个打开的文件在该目录下有一个对应的文件。
例如:#ls /proc/851/fd
这表示,851号进程目前正在使用(已经打开的)文件有4个,它们的描述符分别是0、1、2、255。其中,0、1、2
依次分别是进程的标准输入、标准输出和标准错误输出设备。
(2)/proc/filesystems:该文件记录了可用的文件系统类型。
(3)/proc/mounts:该记录了当前被安装的文件系统信息
例如:#cat /proc/mount
(4)/proc/$pid/maps:该文件记录了进程的映射内存区信息。
例如:#cat&&/proc/851/maps
2.Linux常用文件系统调用
(1)open系统调用
格式: #include
int open(char *path,int flags,mode_t mode);
其中:参数path 是指向所要打开的文件的路径名指针。
参数falgs 规定如何打开该文件它必须包含以下三个值之一
O_RDONLY&& 只读打开
O_WRONLY&& 只写打开
O_RDWR&&&&
参数mode 规定对该文件的访问权限,定义在中
(2)read系统调用
格式: #include
int read(int fd,void *buf,size_t nbytes)
该系统调用从文件描述符fd所代表的文件中读取nbytes
个字节,buf指定的缓冲区内。所读取的内容从当前的读/写指针所指示的位置开始,这个位置由相应的打开文件描述中的偏移值(off_set)给出,调用成功后文件读写指针增加实际读取的字节数。
使用read 系统调用时,应注意设置的数据缓冲区充分大,能够存放所要求的数据字节,因为内核只复制数据,不进行检查。
返回: -1:&& 错误
0 :文件偏移值是在文件结束处
整数:从该文件复制到规定的缓冲区中的字节数。通常这个字节数与所请求的字节数相同。除非请求的字节数超过剩余的字节数,这时将返回一个小于请求的字节数的数字。
(3)write系统调用
格式:#include
int write(int fd,void *buf,size_t nbytes)
该调用从buf所指的缓冲区中将nbytes
个字节写到描述符fd所指的文件中。&&&&
(4)close系统调用
格式: #include
int close(int fd)
每打开一个文件,系统就给文件分配一个文件描述符,同时为打开文件描述符的引用计数加1。Linux文件系统最多可以分配255个文件描述符。当调用close()时,打开文件描述符的引用计数值减1,最后一次对close()的调用将使应用计数值为零。
虽然当一个进程结束时,任何打开的文件将自动关闭,明显地关闭任何打开的文件是良好的程序设计习惯。
三、实验内容
1. 查看/proc下与文件系统相关的文件和目录。
2. 通过运行例程,体会几个主要文件系统调用的使用方法(如:open、read、write、stat等)。
3. 观察文件系统工作情况:stat()、fstat()等。
4. /proc等有关系统文件。
5. 分析并完善下列程序,并对程序的功能加语句注释。程序的功能是:
利用用户自定义的缓冲区,使对文件的单字节读/写操作在该缓冲区中进行,只有当用户缓冲区空或满时,才调用read/write从(或向)文件读、写数据。
#define SIZ&&105
void rd();
void wr();
char buffer [SIZ]="\\0";
int nread=0;
int main(int argc,char **argv)
if(argc&2)
{ fprintf(stderr,"illegal operation");
exit(EXIT_FAILURE); }
(!strcmp(&&&&&&&&&&&&&&
,argv[1]))
printf("[read mode]\\n"); }
{ if(!strcmp("write",argv[1]))
&&&&&&&&&&&&&&&
printf("[write mode]\\n"); }
{ if((fd=open("text",O_RDONLY))==-1)
{ fprintf(stderr,"file open eror\\n");
exit(EXIT_FAILURE); }
while((nread=read(fd,buffer,SIZ))!=0)
{ write(1,buffer,nread);}
close(fd);
void wr ()
{ while(nread
{ buffer[nread++]=getc(stdin);
if(nread&SIZ)
printf("buffer overrun"); }
if((write(1,buffer,nread))!=nread)
write(2,"a write error has occurred\\n",27);
if((fd=open("text",O_WRONLY))==-1)
{ fprintf(stderr,"file open error\\n");
exit(EXIT_FAILURE); }
write(fd,buffer,SIZ);
close(fd);
程序1:HashFile.c
#define COLLISIONFACTOR 0.5
struct HashFileHeader
//file signature
//record length
total_rec_& //
current_rec_&&&&&&
//current record num,if current_record_num
struct CFTag
{&&//collision
//free tag,0 is free,1 is occupied& };
int hashfile_creat(const char *filename,mode_t mode,int
reclen,int recnum);
//int hashfile_open(const char *filename,int flags);
int hashfile_open(const char *filename,int flags, mode_t
int hashfile_close(int fd);
int hashfile_read(int fd,int keyoffset,int keylen,void
int hashfile_write(int fd,int keyoffset,int keylen,void
int hashfile_delrec(int fd,int keyoffset,int keylen,void
int hashfile_findrec(int fd,int keyoffset,int keylen,void
int hashfile_saverec(int fd,int keyoffset,int keylen,void
int hash(int keyoffset,int keylen,void *buf,int recnum);
int checkHashFileFull(int fd);
int readHashFileHeader(int fd,struct HashFileHeader *hfh );
程序2:jtRecord.c
#define RECORDLEN 32
struct jtRecord
other[RECORDLEN-sizeof(int)]; };
#define COLLISIONFACTOR 0.5
struct HashFileHeader
//record length
total_rec_//
current_rec_//current record num,
if current_record_num
struct CFTag
{&&//collision
//free tag,0 is free,1 is occupied};
int hashfile_creat(const char *filename,mode_t mode,int
reclen,int recnum);
//int hashfile_open(const char *filename,int flags);
int hashfile_open(const char *filename,int flags, mode_t
int hashfile_close(int fd);
int hashfile_read(int fd,int keyoffset,int keylen,void
int hashfile_write(int fd,int keyoffset,int keylen,void
int hashfile_delrec(int fd,int keyoffset,int keylen,void
int hashfile_findrec(int fd,int keyoffset,int keylen,void
int hashfile_saverec(int fd,int keyoffset,int keylen,void
int hash(int keyoffset,int keylen,void *buf,int recnum);
int checkHashFileFull(int fd);
int readHashFileHeader(int fd,struct HashFileHeader *hfh );
四、实验报告
1. 实验目的与实验内容。
2. 实验主要步骤。
3. 实验程序的功能与结果分析。
4. 完善后的程序。
实验7 用户接口
一、实验目的
1.理解面向操作命令的接口shell。
2.学会简单的shell编程。
3.理解操作系统调用的运行机制。
4.掌握创建系统调用的方法。
三、实验知识准备
1.控制台命令接口
(1)Bash的由来与大致原理
当登录 Linux或者打开一个 xterm时,当前默认的 Shell就是 Bash。从产生来看, Bash是 GNU
Project的 Shell。GNU Project是自由软件基金会的一部分,它对 Linux下的许多编程工具负责。
Bash(Bourne Again SHell)是自由软件基金会发布的 Bourne Shell 的兼容程序。它包含了其它优秀
shell 的许多良好特性,功能非常全面。很多 Linux版本都提供 Bash。
Bash在处理自己的脚本时,先找到需要处理的命令名称,进而在当前用户的缺省命令目录当中找到对应的命令,这些缺省目录一般是
/usr/ /bin/; /sbin。在执行这些命令时,先使用进程创建系统调用 fork(),再使用
exec()来执行这些命令。
(2)建立 Bash脚本
① 编辑文件
你可以用最熟悉的编辑器来编辑这个文本文件,比如文件名为script,在shell下键入:
$ vi script
#! /bin/bash
echo Hello world !
然后保存,退出。
② 测试脚本
使用指令:$ source script
③ 更改脚本属性
使用指令:$ chmod a+x script
将脚本程序设置为可执行。
④ 执行脚本
使用指令:$ ./script
(3)关键字参考
echo在终端上显示
bash特殊变量1~9 保存当前进程或脚本的前9个参数
ls列举文件
ws统计数量
function 定义函数
2.系统调用
系统调用是一种用户程序进入系统空间的办法。操作系统是用户与计算机之间的接口,用户通过操作系统的帮助,可以快速、有效、安全可靠地使用计算机系统中的各种资源来解决自己的问题。为了使用户方便地使用操作系统,OS向用户提供了“用户与操作系统的接口”。这种接口支持用户与操作系统之间进行交互,这些接口被分为命令接口和程序接口两种。前者直接提供给用户在键盘终端上使用;后者则提供给用户(主要是程序员)编程时使用。要学习系统调用,首先要从程序接口入手。
(1)程序接口
程序接口是操作系统专为用户程序设置的,也是用户程序取得OS服务的唯一途径。程序接口通常由系统调用组成。在每个操作系统中,通常有几十上百条的系统调用,它们的作用各不相同:有的用于进程控制、有的用于存储管理、有的用于文件管理等等。在
Windows下进行过WIN32编程的人员应该对Windows提供的API函数有一定的印象,这些API函数就是Windows操作系统提供给程序员的系统调用接口。而Linux作为一个操作系统,也有它自己的系统调用。
(2)系统调用
通常,在OS的核心中都设置了一组用于实现各种系统功能的子程序,并将它们提供给程序员调用。程序员在需要OS提供某种服务的时候,便可以调用一条系统调用命令去实现他所希望的功能,这就是系统调用。各个不同的操作系统有各自的系统调用,正如前文所讲的Windows
API,便是Windows的系统调用,Linux的系统调用与之不同的是Linux由于内
核代码完全公开,所以我们可以细致地分析出其系统调用的机制。
① 系统调用和普通过程的区别
运行于不同的系统状态:如前文所述,用户程序可以通过系统调用进入系统空间,而普通过程则只能在用户空间中运行。
通过软中断切换:由于用户程序使用系统调用后要进入系统空间,所以需要调用一个软中断;而普通过程在被调用时没有这个过程。
② 系统调用的类型
系统调用的作用与它所在的操作系统有着密切的联系,根据操作系统的性质不同,它们所提供的系统调用会有一定的差异。不过对于普通操作系统而言,应该具有下面几种类型的系统调用:进程控制类型、文件操纵类型、进程通信类型、信息维护类型、
③ 系统调用的实现机制
由于操作系统的不同,其系统调用的实现方式可能不同,然而实现机制应该是大致相同的,其实现机制一般包含下面几个步骤:
设置系统调用号和参数:在系统当中,往往会设置多条系统调用命令,并赋予每条系统调用命令唯一的一个系统调用号。设置系统调用所需的参数有直接方式和参数表方式两种。
处理系统调用:操作系统中有个系统调用入口表。表中的每个表目都对应一条系统调用命令,它包含有该系统调用自带参数的数目、系统调用命令处理程序的入口地址等等。操作系统内核便是根据所输入的系统调用号在该表中查找到相应的系统调用,进而转入它的入口地址去执行系统调用程序。
(3)相关函数
① fork( )
创建一个新进程。格式:int fork()
其中返回 int 取值意义如下:
0:创建子进程,从子进程返回的 ID值
大于 0:从父进程返回的子进程 ID值
-1:创建失败
② foo( )
这是已经添加到新构成的操作系统中的一个系统调用,原值返回输入的整型数。格式:
int foo(int iNumber),返回的值就是输入的参数。
(4)添加系统调用
① 添加源代码
第一个任务是编写添加到内核中的源程序,即添加到内核文件中的一个函数。该函数的名称应该是在新的系统调用名称之前加上
sys_标志。假设新加的系统调用为
foo(),在/usr/src/linux/kernel/sys.c文件中添加源代码,如下所示:
asmlinkage int sys_foo(int x)
printf("%d\n",x);
注意:在该目录中的“ /usr/src/linux”是
Linux各个版本的统称,它因系统内核的版本不同而名字不同。例如:当前操作系统是 Linux7.1其内核是
Linux-2.4.2,所以在:“usr/src”目录下有两个文件:Linux-2.4和 Linux-2.4.2,其中
linux-2.4是 Linux-2.4.2的连接文件,你可以进入任何一个目录,它对内核的修改都是一样的。
② 连接新的系统调用
添加新的系统调用之后,下一个任务是让
Linux内核的其余部分知道该程序的存在。为了从已有的内核程序中增加新函数的链接,需要进行下面的操作。
进入目录/usr/src/linux/include/asm-i386/,打开文件
unistd.h。这个文件包含了系统调用清单,用来给每个系统调用分配一个唯一的号码。系统调用号的定义格式如下:
# define __NR _name NNN
其中,name以系统调用名称代替,而
NNN是该系统调用对应的号码。应该将新的系统调用名称放到清单的最后,并给它分配已经用到的系统调用号后面的一个号码。比如:
# define __NR _foo 222
此处的系统调用号便是222。Linux内核自身用的系统调用号已经用到221了。而如果读者还要自行增加系统调用,就必须从223开始。
另外一个需要进行的操作是进入目录/usr/src/linux/arch/i386/kernel/,打开文件entry.S。
该文件中有类似下面的清单:
ENTRY(sys_call_table)
.long SYSMBOL_NAME(sys_ni_syscall)
.long SYSMBOL_NAME(sys_exit)
.long SYSMBOL_NAME(sys_fork)
那么就在该表的最后加上:
.long SYSMBOL_NAME(sys_foo)
③ 重新编译内核
为了使新的系统调用生效,需要重建Linux的内核。首先必须以root身份登录。进入目录/usr/src/linux,重建内核:
[root@linuxserver root]# make menuconfig //配置新内核
[root@linuxserver root]# make dep //创建新内核
[root@linuxserver root]# make modules_install //加入模块
[root@linuxserver root]# make clean //清除多余创建的文件
[root@linuxserver root]# make bzImage //生成可执行内核引导文件
④ 使用新编译的内核
cp &a /usr/src/linux-2.4.2/arch/i386/boot/bzImage /boot
⑤ 重新配置/etc/lilo.conf 文件
使用vi编辑器编辑/etc/lilo.conf文件:
vi /etc/lilo.conf
在其中加入如下几行:
image=/boot/bzImage #启动内核的位置,即自己新配置的内核所在目录
label=xhlinux #给内核起一个名字,配置完成,重新启动的时候,会显示这个名字
#用户可以选择该项,重启后,系统将进入你新配置的内核进行引导
read_only #定义新的内核为只读
root=/dev/hda5 #定义硬盘的启动位置是/dev/hda5,在该设计中没有变
&#仿照以前内核引导的位置,不用修改,用以前的就可以
⑥ 完成以上配置后,重新启动系统进入自己的新系统
三、实验内容
1. 查看 Bash版本。
在 shell提示符下输入:$echo $BASH_VERSION
2. 编写 Bash脚本,统计/my目录下 c语言文件的个数。
Bash脚本,可以有多种方式实现这个功能,而使用函数是其中的一个选择。在使用函数之前,必须先定义函数。首先进入自己的工作目录,用
vi编写名为 count的文件。
cd /home/student #在 home/student目录下编程
下面是脚本程序:
#! /bin/bash
function count
echo &n "Number of matches for $1:" #接受程序的第一个参数
ls $1|wc &l #对子程序的第一个参数所在的目录进行操作
将 count文件复制到当前目录下,然后在当前目录目录下建立文件夹 my
vi1.c #在 my目录下建立几个 c文件,以便用来程序测试
chmod +x count
count ./my/*.c
3. 分析下列程序的功能,并分析程序运行的可能结果。
int main()
iUid=fork();
if(iUid==0)
{ &printf("this is parent.\n");
sleep(1); &}
if(iUid&0)
{ &printf("this is child.\n");
sleep(1); &}
if(iUid&0)
printf("can not using system call.\n");
4. 分析下列程序的功能,并写出程序的运行结果。
_syscall1(char*,foo,int,ret)
{ int I,J;
Printf("This is the result of new kernel\n");
Printf("%d",j);
编译程序:
gcc &o &I /usr/src/linux-2.4.2/include test.c
注意:由于需要引入内核头文件unistd.h,不能简单地用普通的编译命令,而应该这样设置参数。
运行测试程序:
解释:当函数没有定义在内核中的时候,如果运行以上程序,系统将显示一个未定义的值
-1;而在内核中定义了以后,运行该程序,系统将显示100,说明我们的内核添加系统调用已经成功!
5. 完善创建系统调用mycall()的程序,实现功能:打印字串到屏幕上。
① 编写系统调用相应函数
在/usr/src/linux/kernel/sys.c文件中添加如下代码:
asmlinkage void mycall(char *str)
printk("%s\n",str);
② 添加系统调用号
打开文件/usr/src/linux/include/asm-i386/unistd.h,添加如下一行:
# include _NR_mycall 223 //因为_NR_foo是222,所以这个只能用223了
③ 改动系统调用表
打开文件:
/usr/src/linux/arch/i386/kernel/entry.s,在“.long
SYMBOL_NAME(sys_foo)”下面添加一行:
.long SYMBOL_NAME(sys_mycall)
④ 重新编译内核
对重新编译内核和使用新内核引导熟悉的读者,可以跳过第四步和第五步。以root身份登录。进入目录/usr/src/linux,重建内核:
[root@linuxserver root]# make menuconfig //配置新内核
[root@linuxserver root]# make dep //创建新内核
[root@linuxserver root]# make modules_install //加入模块
[root@linuxserver root]# make clean //清除多余创建的文件
[root@linuxserver root]# make bzImage //生成可执行内核引导文件
编译完毕之后,新的内核引导文件在目录/usr/src/linux/arch/i386/boot/中,叫
bzImage。把它复制到/boot/下面:
[root@linuxserver root]# cp /usr/src/linux/arch/i386/boot/bzImage
⑤ 用新的内核引导
这需要修改文件/etc/lilo.conf,先打开该文件,参见前面进行修改。
修改完成后,存盘退出,运行命令:
[root@linuxserver root]# /sbin/lilo
⑥ 重新启动系统
6.编程调用自己创建的系统调用
系统调用mycall()是读者自己创建的,读者自己对该系统调用的参数表应该非常熟悉。然而使用这个系统调用还需要注意:系统调用转换宏。这个转换宏是将系统调用命令转换为
对应参数的INT80中断请求,一般的格式是syscalN(),这个N可以为0~6,对应于系统调用参数的个数,本处N=1。下面是一个简单的源代码:test1.c
_syscall1(char*,mycall,int,ret)
int main()
char string[50];
str="this string will be displayed.";
mycall(str);
四、实验报告
1. 实验目的与实验内容。
2. 实验主要步骤。
3. 实验程序的功能与结果分析。
实验8 综合实验
一、实验目的
1.体会用户空间和系统空间。
2.理解操作系统“宏内核”组织方式。
3.学习模块的加载与卸载操作。
4. 通过学习内核模块的编写和运行,了解模块是 Linux
OS一种特有的机制,可根据用户的实际情况需要,在不需要对内核进行重新编译的情况下,模块能在内核中被动态地加载和卸载。编写一个模块,将它作为
Linux OS内核空间的扩展来执行,并通过 insmod命令来手工加载,通过命令 rmmod来手工卸载。
二、实验知识准备
操作系统的“内核”是指操作系统中负责处理器、存储器、设备和文件管理的部分,它们一般常驻系统空间,有微内核与宏内核之分。所谓微内核,就是指操作系统中只负责实现操作系统的最基本的服务,比如内存管理、中断管理等等,而其它的诸如文件系统、网络协议栈、驱动模块等等就在外部的用户空间中执行。当前很多实时系统和嵌入式系统便是采用了微内核。而宏内核则是将文件系统、网络协议栈、驱动模块等等这些部分都放在内核空间中运行,比如
UNIX就是采用了宏内核。
Linux采取了一种比较折中的办法,除了具有内核需要实现的最基本的服务外,其它部分均采用模块的方式,用户可以根据自己的需要动态的加载模块,也可以由系统自动加载。同时,采用模块方式也可以将应用程序调入到内核空间当中,实现一些在普通用户级无法实现的功能。可以说进行模块编程的同时,就是进行内核编程,因为它的运行环境是内核环境,它的程序运行函数库都是在内核空间定义的,而不是使用用户的函数库,而本实验便是指导读者进行模块编程。
Linux模块是一些可以作为独立程序来编译的函数和数据类型的集合。在加载这些模块时,将它的代码链接到内核中。Linux模块有两种加载方式:静态加载(内核启动时加载)和动态加载(在内核运行过程中加载)。若在模块加载之前就调用了动态模块的一个函数,则此调用将失败;若模块已被加载,则内核就可以使用系统调用,并将其传递到模块中的相应函数。模块通常用来实现设备驱动程序(这要求模块的
API和设备驱动程序的 API相一致)。模块能够实现所期望的任何功能。
1.模块的组织结构
模块一旦被加载进系统,就在内核地址空间中的管态下执行。它就像任何标准的内核代码一样成为内核的一部分,并拥有和其它内核代码相同的权限和职责。若模块知道内核数据结构的地址,则它可以读写内核数据结构。但
Linux是一个整体式的内核结构,整个内核是一个单独的且非常大的程序,从而存在一个普遍的问题:在一个文件中实现的函数可能需要在其它文件中定义的数据。在传统的程序中,这个问题是通过链接编辑器在生成可执行对象文件时,使用链接编辑器可以解析的外部(全局)变量来解决的。又因为模块的设计和实现与内核无关,所以模块不能靠静态链接通过变量名引用内核数据结构。恰好相反,Linux内核采用了另外一种机制:实现数据结构的文件可以导出结构的符号名(可以从文件
/proc/ksyms或文件
/…/kernel/ksyms.c中以文本方式读取这个公开符号表),这样在运行时就可以使用这个结构了。不过在编写模块的过程中,编写(修改)导出变量时要格外注意,因为修改变量会导致修改内核的状态,其结果可能并不是内核设计者所期望的。在确信自己了解修改内核变量的后果之前,应该对这些变量只进行读操作。
模块作为一种抽象数据类型,它具有一个可以通过静态内核中断的接口。最小的模块结构必须包括两个函数:init_module()和
cleanup_module(),它们在系统加载模块和卸载模块时被调用。也可以编写一个只包括这两个函数的模块,这样该模块中唯一会被调用的函数就是模块被加载时所调用的函数
init_module()和模块被卸载时所调用的函数 cleanup_module()。并且用函数
init_module()来启动模块加载期间的操作,用函数 cleanup_module()来停止这些操作。
由于模块可以实现相当复杂的功能,故可以在模块中加入很多新函数以实现所期望的功能。不过加入模块的每个新函数都必须在该模块加载到内核中时进行注册。若该模块是静态加载的,则该模块的所有函数都是在内核启动时进行注册;若该模块是动态加载的,则这些新函数必须在加载这个模块时动态注册。当然,如果该模块被动态卸载了,则该模块的函数都必须从系统中注销。通过这种方式,当这个模块不在系统中时,就不能调用该模块的函数。其中注册工作通常是在函数
init_module()中完成的,而注销工作则是在函数cleanup_module()中完成。
由上述定义的模块应有如下格式:
说明是个内核功能
声明是一个模块
&&&&&&&&&&&&&&&&&&&&&&&//
其它 header信息
int init_module( )
加载时,初始化模块的编码
期望该模块所能实现的一些功能函数,如 open()、release()、write()、
// read()、ioctl()等函数
void cleanup_module( )
…… &&&//
卸载时,注销模块的编码
2.模块的编译
一旦设计并编写好模块,必须将其编译成一个适合内核加载的对象文件。由于编写模块是用 C语言来完成的,故采用
gcc编译器来进行编译。如果需要通知编译程序把这个模块作为内核代码而不是普通的用户代码来编译,则就需向 gcc编译器传递参数“ -D_
_KERNEL_ _”;若需要通知编译程序这个文件是一个模块而不是一个普通文件,那么就要向 gcc编译器传递参数“
-DMODULE”;若需要对模块程序进行优化编译、链接,则就需使用“-O2”参数;如果还需要对加载后的模块进行调试,那么就应该使用“
-g”参数;同时需要使用“-Wall”参数来向加载程序传递 all,并使用“
-c”开关通知编译程序在编译完这个模块文件后不调用链接程序。
一般编译模块文件的命令格式如下:
#gcc -O2 -g -Wall -DMODULE -D_ _KERNEL_ _ -c filename.c
//filename.c为自己编写的模块程序源代码文件
① -O2 //表示编译产生尽可能小和尽可能快的代码
② &Wall //提示编译信息
③ -DMODULE //确定其类型
④ -D__KERNEL__ //提示是对内核的编译
⑤ /usr/src/linux-2.4/include 其中的选项是你机器的内核的版本
执行命令后就会得到文件 filename.o,该文件就是一个可加载的目标代码文件。
3.模块的加载
内核模块的加载方式有两种。一种是使用 insmod命令手工加载模块;另一种是请求加载 demand
loading(在需要时加载模块),即当有必要加载某个模块时,如果用户安装了内核的核心中并不存在的文件系统时,核心将请求内核守护进程kerneld准备加载适当的模块。该内核守护进程是一个带有超级用户权限的普通用户进程。在这个实验中我们主要采用insmod命令手工加载模块。
系统启动时,kerneld开始执行,并为内核打开一个 IPC通道,内核通过向
kerneld发送消息请求执行各种任务。kerneld的主要功能是加载和卸载内核模块,
kerneld自身并不执行这些任务,它是通过某些程序(如 insmod)来完成。
Kerneld只是内核的代理,只为内核进行调度。
insmod程序必须找到请求加载的内核模块(该请求加载的模块一般被保存在
/lib/modules/kernel-version中)。这些模块与系统中其它程序一样是已链接的目标文件,但不同的是它们被链接成可重定位映象(即映象没有被链接在特定的地址上运行,其文件格式是
a.out或 ELF)。即,模块在用户空间(使用适当的标志)进行编译,结果产生一个可执行格式的文件。在用
insmod命令加载一个模块时,将会发生如下事件:
① 新模块(通过内核函数 create_module())加入到内核地址空间。
② insmod执行一个特权级系统调用
get_kernel_syms()函数以找到内核的输出符号(一个符号表示为符号名和符号值,如地址值)。
③ create_module()为这个模块分配内存空间,并将新模块添加在内核模块链表的尾部,然后将新模块标记为
UNINITIALIZED(模块未初始化)。
④ 通过 init_module()系统调用加载模块。(该模块定义的符号在此时被导出,供其它后来可能加载的模块使用)
⑤ insmod为新加载的模块调用 init_module()函数,然后将新模块标志为 RUNNING(模块正在运行)。
在执行完 insmod命令后,就可在 /proc/modules文件中看到加载的新模块了。(为证实其正确性,可在执行
insmod命令之前先查看/proc/modules文件,执行之后再查看比较。)
4.模块的卸载
当一个模块不需要使用时,可以使用
rmmod命令卸载该模块。由于无需链接,因此它的任务比加载模块要简单得多。但如果请求加载模块时,当其使用计数为
0,kerneld将自动从系统中卸载该模块。卸载时通过调用模块的
cleanup_module()释放分配给该模块的内核资源,并将其标志为
DELETED(模块被卸载);同时断开内核模块链表中的链接,修改它所依赖的其它模块的引用,重新分配模块所占的内核内存。
5.模块程序中管理模块的几个文件操作
在内核是用一个 file结构来识别模块,而且内核使用
file_operatuions结构来访问模块程序中的函数。file_operatuions结构是一个定义在
中的函数指针表。管理模块的文件操作,通常也称为“方法”,它们都为 struct file_operations提供函数指针。在
struct file_operations中的操作一般按如下顺序出现,除非特别说明,一般它们返回
0值时表示访问成功;发生错误时会返回一个负的错误值(目前共有 13个操作):
int (*lseek) ()、int (*read)()、int (*write)()、int
(*readdir)()、int (*select)()、int (*ioctl)()、 int (*mmap)()、 int
(*open)()、 void (*release)()、 int (*fsync)()、 int (*fasync)()、 int
(*check_media_change)()、int (*revalidate)()
(1)int (*read)(struct inode *,struct file *,char *, int)
该方法用来从模块中读取数据。当其为 NULL指针时将引起 read系统调用返回
-EINVAL(“非法参数”)。函数返回一个非负值是表示成功地读取了多少字节。
(2)int (*write)(struct inode *,struct file *,const char *,
该方法用来向模块发送数据。当其为NULL指针时将导致write系统调用返回-EINVAL。如果函数返回一个非负值,则表示成功地写入了多少字节。
(3)int (*open)(struct inode *, struct file *)
该方法是用来打开模块,它是作为第一个操作在模块节点上进行的。即便这样,该方法还是可以设置为 NULL指针。如果为
NULL指针,则表示该模块的打开操作永远成功,但系统不会通知你的模块程序。
(4)void (*release)(struct inode *,struct file *)
该方法是用来关闭模块的操作。当节点需要被关闭时就调用这个操作。与 open类似,release也可以为 NULL指针。
当在你的模块中需要上面这些方法时,若没有相应的方法,则在 struct file_operations中的相应地方将其令为
NULL指针。这样我们的需要会大致象下面这样:
struct file_operations modulename_fops ={
modulename_lseek
modulename_read,
modulename_write,
&&&&&&&&//
modulename_readdir
&&&&&&&&//
modulename_select
&&&&&&&&//
modulename_ioctl
&&&&&&&&//
modulename_mmap
modulename_open,
modulename_release,
NULL, &&&//
modulename_fsync
NULL, &&&//
modulename_fasync
NULL, &&&//
modulename_check_media_change
// modulename_revalidate
三、实验内容
1.建立一个简单的内核模块
(1)必要的 header文件
除了前面讲到的头文件#include 和#include 之外,如果你的内核打开了版本检查,那么还必须增加头文件#include
,否则就会出错。
(2)init_module()函数
由于实验的要求不高,因此可以在该函数里只完成一个打印功能,如 printk(“Hello! This is a testing
module!\n”);等。为便于检查模块是否加载成功,我们可以给一个返回值,如 return 0;若返回一个非 0值,则表示
init_module()失败,从而不能加载模块。
(3)cleanup_module()函数
只需要使用一条打印语句就可以取消 init_module()函数所做的打印功能操作,如
printk(“Sorry! The testing module is unloaded now!\n”);等。
(4)模块的编写
此处把该模块文件取名为 testmodule.c
#include &&// 在内核模块中共享
#include &&// 一个模块
//处理 CONFIG_MODVERSIONS
#if CONFIG_MODVERSIONS == 1
#define MODVERSIONS
int init_module()
&&&&&&&//初始化模块
{ &printk("Hello! This is a testing module!
return 0; }
void cleanup_module()
init_module()函数所做的打印功能操作
{& printk("Sorry! The testing module is
unloading now! \n"); }
(5)模块的编译、加载和卸载
模块的编译、加载和卸载的过程如下:
[root@linux /]# gcc &O2 &Wall &DMODULE &D_KERNEL_ -c
testmodule.c
[root@linux /]# ls &s //在当前目录下查看生成的目标文件 testmodule.o
现在,模块 testmodule已经编译好了。用下面命令将它加载到系统中:
[root@linux /]# insmod &f testmodule.o
如果加载成功,则在 /proc/modules文件中就可看到模块
testmodule,并可看到它的主设备号。同时在终端显示:
Hello! This is a testing module!
如果要卸载,就用如下命令:
[root@linux /]# rmmod testmodule
如果卸载成功,则在 /proc/devices文件中就可看到模块 testmodule已经不存在了。同时在终端显示:
Sorry! The testing module is unloading now!
2.模块加载前后的比较
(1)在用户模式下体验越级调用产生的后果
cr3存放着当前进程的“页表目录结构”的地址,这个值是在进程被唤醒的时候放入的。对它的读操作必须在内核空间中进行,否则将出现错误,本实验就是向读者展示这个错误。编写如下程序:
&&//用户空间的标准输入输出头文件
void GetCr3()
_asm_ _volatile_("movl %%cr3,%0": "=r" (a));
printf("the value in cr3 is: %d",a);
&&&//用户空间的标准输出函数
int main()
对以上文件进行编译和链接,以观察结果。
注意:有些底层的操作,需要在C语言中嵌入汇编语言。在Linux 中使用的是AT&T格式的汇编。
(2)采用该模块程序实现上一实验中没有实现的功能
对寄存器cr3的访问,必须在内核空间中完成,在用户程序中被禁止。而采用加载模块程序的方式就可以做到这一点。下面是程序源代码:文件名GetCr3.c。此外最好先建立一个目录。
int init_module()
_asm_ _volatile_("movl %%cr3,%0":"=r" (iValue));
printf("cr3:%d",iValue);
void cleanup_module(void)
printk("uninstall getcr3! \n");
编写 Makefile文件如下:
DFLAGS=-D_KERNEL_-DMODULE
CFLAGS=-O2 &g &Wall &Wstrict-prototypes &pipe
&l/user/include/linux/
GetCr3.o: GetCr3.c
gcc &c GetCr3.c $(DFLAGS) $(CFLAGS) &o GetCr3.o
make就可以得到相应的目标文件了。然后在GetCr3.o所在的目录下键入加载命令:
[root@linuxserver root]# /sbin/insmod GetCr3.o
可以看到输出结果:
&&&//这个数值有可能不一样
而卸载时运行:
[root@linuxserver root]# /sbin/rmmod GetCr3
就可以看到模块程序退出时的输出:
Uninstall GetCr3!
3.向模块中添加新函数
向testmodule模块中添加新函数 open()、release()、write()和 read()。
(1)函数 open( )
int open(struct inode *inode,struct file *filp)
{ &MOD_INC_USE_COUNT;
&&//增加该模块的用户数目
printk(“This module is in open!\n”);
return 0;
(2)函数 release( )
void release(struct inode *inode,struct file *filp)
{ &MOD_DEC_USE_COUNT;
&&&//该模块的用户数目减
printk("This module is in release!\n");
return 0;
#ifdef DEBUG
printk("release(%p,%p)\n",inode,filp);
(3)函数 read()
int read(struct inode *inode,struct file *filp,char *buf,int
{ &int leave;
if(verify_area(VERIFY_WRITE,buf,count) == DEFAULT)
return DEFAULT;
for(leave=count;leave&0;leave --)
{ &_put_user(1,buf,1);
return count;
int write(struct inode *inode,struct file *filp,const char
*buf,int count)
return count;
4.模块的测试
在该模块程序编译加载后,再在/dev目录下创建模块设备文件moduledev,使用命令:
#mknod /dev/moduledev c major minor
其中“ c”表示
moduledev是字符设备,“major”是moduledev的主设备号。(该字符设备驱动程序编译加载后,可在/proc/modules文件中获得主设备号,或者使用命令:
[root@linux /]#cat /proc/modules | awk ”\\$2==\” moduledev\”{
print\\$1}”获得主设备号)
使用如下程序可对加载模块进行测试
{ &int i,testmoduledev;
char buf[10];
testmoduledev=open("/dev/moduledev",O_RDWR);
&& if(testmoduledev == -1)
printf("Can’t open the file! \n");
read(testmoduledev,buf,10);
&& for(i=0;i&10;i++)
&& printf(“%d\n”,buf[i]);
close(testmoduledev);
return 0;
四、实验报告
1. 实验目的与实验内容。
2. 实验主要步骤。
3. 实验程序的功能与结果分析。
已投稿到:

我要回帖

更多关于 化学实验基本方法 的文章

 

随机推荐