操作系统实验一

本文介绍了一系列Linux系统编程实验,包括Shell编程、内核模块编写、系统调用添加、Shell进程管理编程、存储管理观察、进程间通信观察及文件系统管理等内容,通过具体实验步骤和源代码展示了关键技术和实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实验1.1、1.2 Linux Ubuntu的安装、创建新的虚拟机VMWare

实验1.3 Shell编程
1.实验目的与内容
通过本实验,了解Linux系统的shell机制,掌握简单的shell编程技巧。
编制简单的Shell程序,该程序在用户登录时自动执行,显示某些提示信息,如“Welcome to Linux”, 并在命令提示符中包含当前时间、当前目录和当前用户名等基本信息。
2.程序源代码清单
#include<stdio.h>
#include<sys/wait.h>
int main(){
printf(“Hello Linux\n”);
int pid;
int state;
int pfd[2];
pipe(pfd);
if (fork()==0){
printf(“In the grep progress\n”);
dup2(pfd[0],0);
close(pfd[0]);
close(pfd[1]);
execlp(“grep”,“grep”,“sh”,0);
perror(“exelp grep error”);
}
esle if(fork()==0){
printf(“In the ps progress\n”);
dup2(pfd[1],1);
close(pfd[0]);
close(pfd[1]);
execlp(“ps”,“ps”,"-ef",0);
perror(“execlp ps -ef”);
}
close(pfd[1]);
close(pfd[0]);
wait(&state);
wait(&state);
}
实验2.3 内核模块
实验步骤:
(1).编写内核模块
文件中主要包含init_clock(),exit_clock(),read_clock()三个函数。其 中init_clock(),exit_clock()负责将模块从系统中加载或卸载,以及增加或删除模块在/proc中的入口。read_clock()负责产生/proc/clock被读时的动作。
(2).编译内核模块Makefile文件

Makefile under 2.6.25

ifneq ($(KERNELRELEASE),)
#kbuild syntax. dependency relationshsip of files and target modules are listed here.
obj-m := proc_clock.o
else
PWD := $(shell pwd)
KVER ?= ( s h e l l u n a m e − r ) K D I R : = / l i b / m o d u l e s / (shell uname -r) KDIR := /lib/modules/ (shellunamer)KDIR:=/lib/modules/(KVER)/build
all:
$(MAKE) -C ( K D I R ) M = (KDIR) M= (KDIR)M=(PWD) modules
clean:
rm -rf ..cmd .o .mod.c .ko .tmp_versions .symvers .order
endif
编译完成之后生成proc_clock.ko模块文件。
(3).内核模块源代码clock.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>
#define MODULE
#define MODULE_VERSION “1.0”
#define MODULE_NAME “clock”
struct proc_dir_entry
my_clock;
int read_clock(char
page, char
start, off_t off, int count, int
eof,
void
data) {
int len;
struct timeval xtime;
do_gettimeofday(&xtime);
len = sprintf(page, “%d %d\n”, xtime.tv_sec, xtime.tv_usec);
printk(“clock: read_func()\n”);
return len;
}
struct proc_dir_entry *clock_proc_file;
int init_clock(void)
{
clock_proc_file =create_proc_read_entry(“clock”,0,NULL,read_clock,NULL);
return 0;
}
void exit_clock(void)
{
remove_proc_entry(“clock”,clock_proc_file);
}
module_init(init_clock)
module_exit(exit_clock)
MODULE_LICENSE(“GPL”);
(4).编译内核模块

make

(5).加载内核模块
在系统root用户下运行用户态模块命令装载内核模块

insmod proc_clock.ko

(6).测试
在终端中输入以下命令:
# cat /proc/clock
(7).卸载内核模块
在系统root用户下运行用户态模块命令卸载内核模块
#rmmod proc_clock.ko

实验2.4 系统调用
实验步骤:
(1). 添加新调用的源代码
在./linux-2.6.33.7/arch/x86/kernel/sys_i386_32.c中添加相应的调用代码
asmlinkage int sys_xwlcall(struct timeval *tv)
{
struct timeval ktv;
do_gettimeofday(&ktv);
copy_to_user(tv,&ktv,sizeof(ktv));
printk(KERN_ALERT"PID %ld called sys_xwlcall()./n",(long)current->pid);
return 0;
}
(2). 连接系统调用
a、修改./linux-2.6.33.7/arch/x86/include/asm/unistd_32.h,
在系统调用列表后面相应位置添加一行,这样在用户空间做系统调用时就不需要知道系统调用号了,如果在用户空间指明了调用号,就可以省略这一步,实际上我就没写:
#define __NR_xwlcall338
新增加的调用号位338
b、修改./linux-2.6.33.7/arch/x86/kernel/syscall_table_32.S
在ENTRY(sys_call_table)清单最后添加一行,这步至关重要,338就是这里来的:
.long sys_xwlcall
(3). 重建新的Linux内核
先安装好编译内核必要的软件包:

sudo apt-get install build-essential kernel-package libncurses5-dev

复制当前内核的配置文件

cp /boot/config-uname -r ./.config

保存配置文件

sudo make menuconfig

使用debian的的内核编译方法,要简单很多

sudo make-kpkg-initrd–initrd–append-to-version=xwlcall

kernel_image kernel-headers

运行以下deb包,安装内核镜像和模块:
linux-image-2.6.33.7xwlcall_2.6.33.7xwlcall-10.00.Custom_i386.deb

运行以下deb包,安装内核头文件:
linux-headers-2.6.33.7xwlcall_2.6.33.7xwlcall-10.00.Custom_i386.deb

运行以下命令,使内核启动时能调用模块,比如硬件驱动:

sudo update-initramfs -c -k 2.6.33.7xwlcall

此次编译的内核采用ubuntu默认配置文件,通用性非常好,可以拷贝到大部分x86机器上安装。安装后系统自动会修改grub启动选单。
4. 重建引导信息
a、安装deb包就自动重建引导信息了,无须另行处理。
b、如果仍然不放心,可以运行

update-grub

  1. 重新引导从新的内核进入

  2. 修改系统调用表

  3. 测试
    实验3.3 Shell编程实验(进程管理实验)
    1、实验目的
    通过编写shell程序,了解子进程的创建和父进程与子进程间的协同,获得多进程程序的编程经验。
    2、实验内容1
    设计一个简单的shell解释程序,能实现基本的bsh功能。
    3、实验原理
    将每一条命令分子段压入argv栈。然后再子进程中调用execvp()来实现该命令的功能。
    4、代码(源代码清单)
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #define BUFFERSIZE 256
    //最简单的shell,只是简单的执行命令调用,没有任何的其他功能
    int main()
    {
    char buf[BUFFERSIZE],*cmd,*argv[100];
    char inchar;
    int n,sv,buflength;
    int result;
    buflength = 0;
    for(;😉 {
    printf("=> “);
    //处理过长的命令;
    inchar = getchar();//读取命令
    while (inchar != ‘\n’ && buflength < BUFFERSIZE ){
    buf[buflength++] = inchar;
    inchar = getchar();
    }
    if (buflength > BUFFERSIZE){
    printf(“Command too long,please enter again!\n”);
    buflength = 0;
    continue;
    }
    else
    buf[buflength] = ‘\0’;
    //解析命令行,分成一个个的标记
    //char *strtok(char *s,char *delim)
    //分解字符串为一组字符串。s为要分解的字符串,delim为分隔符字符串。
    cmd=strtok(buf,” \t\n");
    if(cmd) {
    if(strcmp(cmd,“exit”)==0) exit(0);
    n=0;
    argv[n++]=cmd;
    while(argv[n++]=strtok(NULL," \t\n"));
    if(fork()==0) {
    execvp(cmd,argv);
    fprintf(stderr,“sxh:%s:command not found.\n”,buf);//如果子进程顺利执行,这段话是不会执行的
    exit(1);
    }
    wait(&sv);
    buflength = 0;
    }
    }
    }
    实验内容2
    编写一个带有重定向和管道功能的Shell
    1.设计思路
    通过fork()创建子进程,用execvp()更改子进程代码,用wait()等待子进程结束。这三个系统调用可以很好地创建多进程。另一方面,编写的Shell要实现管道功能,需要用pipe()创建管道使子进程进行通信。
    2.源代码清单
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    #define BUFFERSIZE256
    //具有输入输出重定向的功能 和管道功能
    int
    main()
    {
    char buf[256],*buf2,*cmd,*cmd2,*argv[64],*argv2[64],*infile,*outfile;
    char inchar;
    int n,sv,buflength,fd[2];

for(;😉 {
buflength = 0;
printf("=> “);
inchar = getchar();
while (inchar != ‘\n’ && buflength < BUFFERSIZE ){
buf[buflength++] = inchar;
inchar = getchar();
}
if (buflength > BUFFERSIZE){
fprintf(stderr,“Command too long,please enter again!\n”);
buflength = 0;
continue;
}
else
buf[buflength] = ‘\0’;
//检查是否具有管道操作符
//strstr()在字符串中查找指定字符串的第一次出现,buf2指向管道符号前端的命令
buf2=strstr(buf,”|");
if(buf2)
buf2++=’\0’;
else {
//否则查看是否具有重定向的操作符
infile=strstr(buf,"<");
outfile=strstr(buf,">");
if(infile) {
infile=’\0’;
infile=strtok(infile+1," \t\n");
}
if(outfile) {
outfile=’\0’;
outfile=strtok(outfile+1," \t\n");
}
}
//解析命令行,分成一个个的标记
cmd=strtok(buf," \t\n");
//执行管道命令
if(buf2){
if(strcmp(cmd,“exit”)==0) exit(0);
if(!cmd) {
fprintf(stderr,“Command token error.\n”);
exit(1);
}
n=0;
//管道后端的命令
argv[n++]=cmd;
while(argv[n++]=strtok(NULL," \t\n"));
//管道前端的命令
cmd2=strtok(buf2," \t\n");
if(!cmd2) {
fprintf(stderr,“Command token error.\n”);
exit(1);
}
n=0;
argv2[n++]=cmd2;
while(argv2[n++]=strtok(NULL," \t\n"));
pipe(fd);
if(fork()==0) {
dup2(fd[0],0); //dup2 复制文件句柄,将fd[0]复制到描述符0。
close(fd[0]); close(fd[1]);
execvp(cmd2,argv2);
fprintf(stderr,"
bad command\n"); exit(1);
} else if(fork()==0) {
dup2(fd[1],1);
close(fd[0]);close(fd[1]);
execvp(cmd,argv);
fprintf(stderr,"
* bad command\n"); exit(1);
}
close(fd[0]);
close(fd[1]);
wait(&sv);
wait(&sv);
buflength = 0;
}
//如果没有管道命令,如果有重定向就执行重定向操作,如果没有重定向就当作普通shell命令执行
else{
if(cmd) {
if(strcmp(cmd,“exit”)==0) exit(0);
n=0;
argv[n++]=cmd;
while(argv[n++]=strtok(NULL," \t\n"));
if(fork()==0) {
int fd0=-1,fd1=-1;
if(infile) fd0=open(infile,O_RDONLY);
if(outfile) fd1=open(outfile,O_CREAT|O_WRONLY,0666);
if(fd0!=-1) dup2(fd0,0);//dup2 复制文件句柄,将fd0复制到描述符0。
if(fd1!=-1) dup2(fd1,1);//dup2 复制文件句柄,将fd1复制到描述符1。
close(fd0);
close(fd1);
execvp(cmd,argv);
fprintf(stderr,"** Bad command\n");
exit(1);
}
wait(&sv);
buflength = 0;
}
}
}//for
}

实验4.1 观察实验(存储管理实验)
1.实验步骤
(1)、安装GDB
(2)、编写观测程序
(3)、按照指令手册进行观察操作
2.观测程序源代码
#include<stdio.h>
#include<stdlib.h>
char str[50] = “Hello Linux.”;
int main()
{
int num = 10;
while(num–){
printf("%s\n",str);
}
}
//gcc -g -o testing testing.c
3.实验结果及分析
(1).Gdb程序观察一个程序文件的内容和结构
结果截图:

(2).GDB观察程序内存映象的内容和结构

(3).在Linux下,用free 和vmstat命令观察内存使用情况

(4).在Linux下,查看/proc与内存管理相关的文件,并解释显示结果

实验5.1 观察实验(进程通信)
在Linux下,用ipcs()命令观察进程通信情况,了解Linux基本通信机制
实验结果(截图):

实验6.3 IO系统编程实验
1、实验目的
编写一个daemon进程,该进程定时执行 ps命令,然后将该命令的输出写至文件F1尾部。通过此实验,掌握Linux I/O系统相关内容。
2、实验内容
编写一个daemon进程,该进程定时执行 ps命令,然后将该命令的输出写至文件F1尾部。
3、实验原理
在这个程序中,首先fork一个子程序,然后,关闭父进程,这样,新生成的子进程被交给init进程接管,并在后台执行。
新生成的子进程里,使用system系统调用,将ps的输出重定向,输入到f1.txt里面。
4、实验步骤
编写daemon.c
代码如下:
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char* argv[])
{
int i,p;
p = fork();
if(p > 0){
exit(0);
}
else if(p == 0){
for(i = 0; i < 100; i++){
sleep(100);
system(“ps > f1.txt”);
}
}
else{
perror(“Create new process!”);
}
return 1;
}
}
编译程序

gcc -o daemon daemon.c

执行程序

./daemon

实验7.1 代码分析(文件系统管理实验)
1.实验目的
了解与文件管理有关的Linux内核模块的代码结构。
2.实验结果(源代码分析)
A. 创建文件模块分析
5780 /*creat system call */
5781 Creat()
5782 {
5783 resister *ip;
5784 extern uchar;
5785
5786 ip = namei(&uchar,1);
5787 if(ip == NULL){
5788 if(u.u_error)
5789 return;
5790 ip = maknode(u.u_arg[1]&07777&(~ISVTX));
5791 if (ip == NULL)
5792 return;
5793 open1(ip,FWRITE,2);
5794 }else
5795 open1(ip,FWRITE,1);
5796 }

第 5786:“namei”( 7 5 1 8 )将一路径名变换成一个“inode”指针。“uchar”是一个过程的
名字,它从用户程序数据区一个字符一个字符地取得文件路径名。
5787:一个空“inode”指针表示出了一个错,或者并没有具有给定路径名的文件存在。
5788:对于出错的各种条件,请见 UMP 的 CREAT(II)。
5790:“maknode”( 7455)调用“ialloc”创建一内存“ inode”,然后对其赋初值,并使
其进入适当的目录。注意,显式地清除了“粘住”位( ISVTX)。
B. 删除文件 rm 模块分析

3510 unlink()
3511 {
3512 resister *ip,*pp;
3513 extern uchar;
3514
3515 pp = namei(&uchar,2);
3516 if (pp ==NULL)
3517 return;
3518 prele(pp);
3519 ip = iset(pp ->dev,u.u_dent.u_ino);
3520 if (ip == NULL)
3521 panic (*unlink – iset *);
3522 if ((ip ->i_mode%IFMT) == IFDIR && !suser())
3523 goto out;
3524 u.u_offset[1] = - DIRSIZ+2;
3525 u.ubase = &u.u_dent;
3526 u.ucount = DIRSIZE +2;
3527 u.u_dent.u_ino = 0;
3528 writei(pp);
3529 ip ->i_nlink–;
3530 ip->i_flag =! IUPD;
3531
3532 out:
3533 iput(pp);
3534 iput(ip);
3535 }

新文件作为永久文件自动进入文件目录。关闭文件不会自动地造成文件被删除。当内存“ inode”项中的“ i _ nlink”字段值为 0 并且相应文件未被打开时,将删除该文件。在创建文件时,该字段由“ maknode”赋初值为 1。系统调用“ link”( 5941 )可将其值加1,系统调用“unlink”( 3529 )则可将其值减 1。创建临时“工作文件”的程序应当在其终止前执行“ unlink”系统调用将这些文件删除。
注意,“unlink”系统调用本身并没有删除文件。当引用计数( i _ count )被减为 0 时(7350、7362),才删除该文件。
为了减少在程序或系统崩溃时遗留下来的临时文件所带来的问题,程序员应当遵守下列约定:
(1) 在打开临时文件后立即对其执行“ unlink”操作。
(2) 应在“tmp”目录下创建临时文件。在文件名中包括进程标识数就可构成一惟一文件名
C. 读写模块分析
5711 Read( )
5712{
5713 rdwr(FREAD);
5714 }

5720 Write( )
5721 {
5722 rdwr(FWRITE);
5723 }

5731 rdwr(mode)
5732{
5733 resister *fp,m;
5734
5735 m = mode;
5736 fp = setf(u.u_arg[R0]);
5737 if (fp ==NILL)
5738 return;
5739 if ((fp ->f_flag&m ==0) {
5740 u.u_error = EBADF;
5741 return;
5742 }
5743 u.u_base = u.u_arg[0];
5744 u.u_count = u.u_arg[1];
5745 u.u_segflg = 0;
5746 if(fp ->f_flag&FPIPE) {
5747 if(m == FREAD)
5748 readp(fp);else
5749 writep(fp);
5750 }else{
5751 u.u_offset[1] = fp ->f_offset[1];
5752 u.u_offset[0] = fp ->f_offset[0];
5753 if (m == FREAD)
5754 readi(fp ->f_inode);else
5755 writei(fp ->f_inode);
5756 dpadd(fp ->f_offset,u.u_arg[1] – u.u_count;
5757 }
5758 u.u_ar0[R0] = u.u_arg[1] – u.u_count;
5759 }

“read”系统调用的基本工作过程为:
……read ( f , b , n ) ;/用户程序
{发生陷入}
2693 trap{#3 系统调用}
5711 read() ;
5713 rdwr(FREAD);

用户进程执行系统调用激活运行在核心态的“ trap”。“trap”识别#3系统调用,然后通过“trap l”调用例程“read”,它又调用“rdwr”。
“rdwr”包含了很多“read”和“write”操作共用的代码。它调用“ getf”( 6 6 1 9 )将用户进程提供的文件标识变换成“file”数组中一项的地址。
注意,该系统调用的第 1 个参数是以不同于另外 2 个参数的方式传送的。
将“u.u_segflg”设置为0,这表示此操作的目的地址在用户地址空间中。在以一个 inode指针参数调用“ read i”后,将要求传送的字符数减去剩余未传输字符数(在 u.u _ count 中),加至文件位移量中。
6221 readi

6239 lbn = lshift (u.u_offset,-9);
6248 on = u.u_offset[i] & 0777;
6241 n = min (512 – on,u.u_count);
6250 dn = ip->i_dev;
6258 bp = bread (dn,bn);
6260 iomove (bp,on,n,B_READ);
6261 brelse (bp);
“read i”将文件位移量分解成两部分:一个逻辑块号“lbn”,以及一个块内索引“on”。将要传输的字符数是下而两个值中的较小者:“u.u _ count”和块内尚余字符数(在这种情况下以后还必须读其他块,此处没有进一步对此说明),还应考虑尚余留在文件中的字符数(对这种情况也未进一步说明)。
“dn”是存储在“ inode”中的设备编号,“bn”是在该设备(磁盘)上的实际块号,这是由“bmap”(6415)用“lbn”计算得到的。
对“ bread ”的调用找到所要求的磁盘块,若需要,则将其从磁盘复制到内存中。“iomove”(6364)将适当数量的字符传送至目的区,然后执行计数操作。
“read”和“ write”执行的操作有很多相似之处,两者共享很多代码。
系统调用“ read”(5711)和“write”( 5720),然后立即调用“rdwr”,它执行下列操作:
5736:将用户程序文件标识变换成指向相应文件表项的指针。
5739:检查所要求的操作(读或写)是否与文件打开时的读/写方式符合。
5743:用各参数在“u”中设置几个标准单元。
5746:从此开始对“管道”文件进行特殊处理。
5755:按读、写要求分别调用“ read i”或“write i”。
5756:更新文件位移量,使其增加实际传送的字符数,同时也将实际传送的字符数返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值