Linux 操作系统内核

更新时间:2024-03-02 10:27:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

Linux 操作系统内核 基本实验指导 Contents z 实验大纲 z 系统安装实验 z Linux内核实验 z 进程管理实验 z 存储管理实验 z 进程通信实验 z I/O 设备管理实验 z 文件系统管理实验 1.实验大纲 1.1 实验目的

在学习《操作系统》课程内容同时,以开放式源代码操作系统 Linux 为实验

平台,同步完成 Linux 操作系统内核的代码分析和修改等 7 组基本课程实验。通 过实验,熟悉 Linux 系统使用方法,掌握 Linux 内核系统结构,了解 Linux 进程 管理、存储管理、设备管理、文件系统等资源管理功能的实现机理和典型算法。 初步掌握运用内核开发环境对内核进行修改完善的能力。

通过本课程实验,使得学生熟悉 Linux 操作系统相关技术,并进一步巩固

课堂所学有关操作系统人机界面和资源管理得相关知识;并通过 Linux 源代码分 析和简单编程,培养学生对实际操作系统的基本系统分析能力。 1.2 实验内容

Linux 基本实验由以下 7 组实验组成。 1.2.1 第 1 组 系统安装实验 实验 1.1 Linux 系统安装

从 CD-ROM 安装 Red Hat Linux 操作系统,如 Red Hat Linux7.2,建立后续 各个实验的运行环境。 实验 1.2 虚拟机安装

在配备 Windows 操作系统 Host 机上,安装虚拟机软件 Virtual PC for

Windows 或 VMware For Windows,进行 BIOS 设定, 对硬盘进行分区和格式化, 安装 Linux 操作系统,以便在一台机器上模拟出多种操作系统运行环境。 实验 1.3 Shell 编程

编制简单的 Shell 程序,该程序在用户登录时自动执行,显示某些提示信息,

如“ Welcome to Linux”, 并在命令提示符中包含当前时间、当前目录和当前用户 名等基本信息。

1.2.2 第 2 组 Linux 内核实验 实验 2.1 观察 Linux 行为

学习 linux 内核、进程、存储和其他资源的一些重要特性。通过使用/proc

文件系统接口, 编写一个程序检查反映机器平衡负载、进程资源利用率方面的各 种内核值, 学会使用/proc 文件系统这种内核状态检查机制。 实验 2.2 内核定时器

学习掌握内核定时器的实现原理和方法,建立一种用户空间机制来测量多 线程程序的执行时间。 实验 2.3 内核模块

模块是 Linux 系统的的一种特有机制,可用于动态扩展操作系统内核功能。

编写实现某些特定功能的模块,将其作为内核的一部分在管态下运行。例如, 通 过内核模块编程在/porc 文件系统中实现系统时钟的读操作接口。 实验 2.4 系统调用

向现有 Linux 内核加入一个新的系统调用从而在内核空间中实现对用户空

间的读写。例如,设计并实现一个新的内核函数 mycall( ),此函数通过一个引用 参数的调用返回当前系统时间,功能上基本与 gettimeofday( )相同。 1.2.3 第 3 组 进程管理 实验 3.1 进程行为观察

1. 在 Linux 下,分别用 snice、 skill、 top 等命令和/proc 中的有关目录、文件观 察系统中进程运行情况和 CPU 工作情况。

2. 在 Linux下,用 ptrace()、gdb跟踪一个进程的运行情况,用 strace工具跟踪 fork() 过程,用 ltrace 工具跟踪 execl() 过程。观察并分析跟踪信息。 实验 3.2 代码分析

阅读分析 Linux 中的进程建立模块、进程撤销模块、进程调度模块、系统 调用总入口模块,了解进程的创建、执行、等待、退出等过程。 实验 3.3 Shell 编程

1. 以超级用户身份编程,计算某一时段中所有程序平均运行时间。

2. 通过编写 shell 程序,了解子进程的创建和父进程与子进程间的协同,获得 多进程程序的编程经验。 1.2.4 第 4 组 存储管理 实验 4.1 观察实验

1. 在 Linux 下,使用 gdb 程序观察一个程序文件的内容和结构。启动该程序 执行,再用 GDB 观察其内存映象的内容和结构。

2. 在 Linux 下,用 free 和 vmstat 命令观察内存使用情况。

3. 在 Linux 下,查看/proc 与内存管理相关的文件,并解释显示结果。 4. 在 Linux 下,用 malloc()函数实现 cat 或 copy 命令。 实验 4.2 代码分析

阅读 Linux/Minix 中以下模块的调用主线,并写出分析报告 z exec 系统调用的内部实现模块调用主线 z malloc 函数的内部实现模块调用主线 z 缺页中断处理程序

实验 4.3 虚拟存储器管理

学习 Linux 虚拟存储实现机制;编写代码,测试虚拟存储系统的缺页错误 (缺页中断)发生频率。 1.2.5 第 5 组 进程通信 实验 5.1 观察实验

在 Linux 下,用 ipcs()命令观察进程通信情况。 实验 5.2 代码分析

阅读 Linux/Minix 中以下模块的调用主线,并写出分析报告。( 1) kill 系 统调用内部实现模块调用主线。( 2) pipe 系统调用内部实现模块调用主线。 实验 5.3 进程同步实验

在学习 linux 内核的同步机制基础上,深入分析各种同步机制的实现方案,设计和编 写一套同步原语。

1.2.6 第 6 组 I/O 设备管理 实验 6.1. 观察实验

1. stat 命令查看机器上硬盘特别文件的 I 节点内容。

2.在 Linux 下,查看/proc 与内存管理相关的文件,解释显示结果。 实验 6.2 代码分析

阅读 Linux/Minix 中以下模块的调用主线( 1) print 函数内部实现模块调用 主线。( 2) scan 函数内部实现模块调用主线。写出分析报告。 实验 6.3 编程实验

编写一个 daemon 进程,该进程定时执行 ps 命令,然后将该命令的输出写 至文件 F1 尾部。

实验 6.4 设备驱动程序

了解 Linux 的设备驱动程序的组织结构和设备管理机制,编写简单的字符设 备和块设备驱动程序。 1.2.7 第 7 组 文件系统管理 实验 7.1 代码分析

阅读 Linux/Minix 中有关文件模块的调用主线,并写出分析报告,包括 z 文件建立模块,即系统调用 create() z 文件删除模块,即系统调用 rm() z 读/写模块,即 read/write 实验 7.2 编程实验 1

在 Linux 环境下,编写 Shell 程序,计算磁盘上所有目录下平均文件个数、 所有目录平均深度、所有文件名平均长度 实验 7.3 编程实验 2

在 Linux 环境下,编写一个利用 Linux 系统调用删除文件的程序,加深对 文件系统和文件操作的理解。 1.3 实验要求

z 学生以小组为单位,每组人数不超过 4 人。

z 以上 7 组实验中,每组至少完成 1 个实验,完成的实验总数不少于 8 个,其 中编程实验不少于 5。

z 实验完成后提交课程实验报告文档,并验收程序代码和上机演示。 z 实验报告中应附有实验程序运行截图 z 课程实验报告要求

对于编程实验,报告应包括:题目,实验目的、实验内容、实验设计原理、 实验步骤、实验结果及分析和人员任务分配等。 2. 系统安装实验

2.1 实验 1.1 Linux 系统安装 1、实验目的

从 CD-ROM 安装 Red Hat Linux 操作系统,如 Red Hat Linux7.2,建立后续各个实验的 运行环境。

2、实验内容(以 Red Hat Linux7.2 为例)

Red Hat Linux7.2 安装光盘共有两张,第一张可直接从光盘启动,包含大部分的软件包 和一些安装工具。第二张光盘包含许多附加软件包。以下为安装过程和注意事项。

( 1)启动安装程序。用 Linux 的第一张光盘,从光驱引导启动程序,进入启动界面,显示

提示符 ”boot: ”,选择图形模式进行安装。 ( 2)选择安装界面的使用语言 ( 3)选择默认的键盘设置 ( 4)选择默认的鼠标设置

( 5)选择安装类型。 Red Hat Linux 提供了个人桌面、工作站、服务器和定制等多种安装类

型。本实验选择个人桌面或定制方式。

( 6)进行硬盘分区。 Red Hat Linux 采用了“装载”的处理方式,将 1 个分区和 1 个目录联

系起来,每个分区都是整个文件系统的一部分。

Linux 最少需要 2 个分区: Linux native(文件)分区、 Linux Swap(交换)分区。前者用 于存放 Linux 系统文件,只能用 EXT2 分区类型,在分区时应将载入点设置为“ /”目录; 后

者用作交换空间,将主存内暂时不用的数据缓存起来。建议采用如下分区方案 z SWAP 分区

SWAP 分区大小至少等于实际系统内存容量,一般可取为内存的 2 倍。 z /boot 分区

包含操作系统内核和启动时所用文件。建立单独的/boot 分区后,即使主要根分区 出了问题,系统仍然能够启动。此分区大小约在 50MB-100MB 之间 z /分区

根目录挂载位置。系统运行所需要的其它文件都在该分区,大小约在 1.7GB 到 5GB 之间

初次安装系统时,最好选择自动安装方式。如果安装者对系统比较熟悉,可以用系统配 置的营盘管理工具 Disk Druid 来订制所需分区。 ( 7)将文件系统设置为 EXT2

( 8)配置引导装载程序。选择 LILO 作为引导安装程序。 LILO 可以安装在第一硬盘的主引

导区( MBR)或 Linux 分区的引导扇区。如果使用 LILO 来做双启动,须选择前者;如果

用 Linux 启动软盘或其它系统引导器引导 Linux,选择后者,即将 LILO 安装在 Linux 分 区的引导扇区。

( 9)网络和防火墙配置

( 10)选择默认的语言及其他语言支持 ( 11)时区配置

( 12)设置 root 配置 ( 13)选择软件包组 (14) 筹建引导盘 (15) 配置显卡 (16) 进行安装

2.2 实验 1.2 虚拟机 VM 软件安装 1、实验目的

在配备 Windows操作系统 Host机上,安装虚拟机软件 Virtual PC for Windows或 VMware For Windows, 进行 BIOS 设定, 对硬盘进行分区和格式化,安装 Linux 操作系统,以便

在一台机器上模拟出多种操作系统运行环境。

通过本实验,进一步掌握课堂上所讲的虚拟机的概念。 2、实验内容: 实验前的准备: 1、 获取安装介质 2、 熟悉虚拟机的操作

安装步骤(以 Vmware 为例): 1、 虚拟机软件的安装及设置 ( 1)、安装 VMware,输入虚拟机序列号 ( 2)、创建一个新的虚拟机。 第一步:“File”->“New Virtual Machine”->“Custom”->“Next”->操作系统那栏选 Linux->“N

ext” 。

第二步:设置虚拟机名以及配置文件 ->“Next” 。 第三步:设置虚拟机的内存大小。

第四步:网络连接,使用默认设置->“Next” 。

第五步:磁盘设定,如果你不是想让红旗 Linux 桌面 4.0 终生运行在虚拟机里,请选 “Use a physical disk” ,让虚拟机与当前系统共用同一硬盘,而不是虚拟出一个硬盘。有一定

风险,但是只要不胡乱操作,风险不大->“Next” 。

第六步:指定要使用的硬盘->“Next” ,设置配置文件的位置->“Finish” ,忽略那个风险提示。 ( 3)、光驱软驱默认情况下也是和当前系统共用的,使用 iso 文件引导虚拟机,则“Edit virtual

machine settings”,在左侧列表中选“DVD/CD-ROM”那项,再在右侧选“Use ISO image:”,指

定安装红旗 Linux 桌面 4.1 的 iso 文件。 ( 4)、虚拟机默认不是从光盘引导的,要在它的 BIOS 里改,得先“Start this virtual machine”。

如果出现“Do not forget to ...”的提示框,直接点“OK”。待 VMware 窗口中一大块变黑的时候,

赶快用鼠标点那块黑,那块黑是虚拟机的屏幕。现在你的鼠标和键盘就转为控制虚拟机了。 注意虚拟机屏幕下方的进度条,在走完之前,按“F2”键进行 BIOSs 设定。 ( 5)、用键盘的左右箭头键选中“Boot”标签,用上下箭头键选中“CD-ROM”。同时用“Shift” 键和“+”键,把“CD-ROM”拎到顶上。用键盘的左右箭头键选中“Exit”标签,用上下箭头键选

中“Exit Saving Changes”,回车->“YES”,等待虚拟机重新启动。同时按“Ctrl”和“Alt”键,鼠

标和键盘就从虚拟机中解脱出来了。看到进度条的时候按 VMware 窗口左上方的红方块, 停掉虚拟机。

2.3 实验 1.3 Shell 编程 1、实验目的与内容

编制简单的 Shell 程序,该程序在用户登录时自动执行,显示某些提示信息,如

“ Welcome to Linux”, 并在命令提示符中包含当前时间、当前目录和当前用户名等基本信

echo rmmod clock to turn ig off echo

编译完成之后生成 clock.o 模块文件。 z 内核模块源代码 clock.c #define MODULE

#define MODULE_VERSION “1.0” #define MODULE_NAME “clock” #include #include #include

int proc_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,\printk(\return len; }

// proc_dir_entry 数据结构

struct proc_dir_entry* proc_my_clock; int init_module() {

printk(\

my_clock=create_proc_read_entry(\printk(KERN_INFO“%s %s has initialized.\\n”, MODULE_NAME,MODULE_VERSION); return 0; }

void cleanup_module() {

printk(\

remove_proc_entry(proc_my_clock->name, &proc_root); printk(KERN_INFO“%s %s has removed.\\n”, MODULE_NAME,MODULE_VERSION); }

MODULE_DESCRIPTION(“clock module for gettimeofday of proc.”); EXPORT_NO_SYMBOLS; z 加载内核模块

在系统 root 用户下运行用户态模块命令装载内核模块 #insmod clock.o z 测试

测试源代码 gettime.c

#include #include #include int

main(void) {

struct timeval getSystemTime; char procClockTime[256]; int infile,len;

gettimeofday(&getSystemTime,NULL); infile = open(\Y); len = read(infile,procClockTime,256); close(infile);

procClockTime[len] = '\\0';

printf(\getSystemTime.tv_sec , getSystemTime.tv_usec, procClockTime); sleep(1); }

z 卸载内核模块

在系统 root 用户下运行用户态模块命令卸载内核模块 #rmmod clock.o

3.4 实验 2.4 系统调用 1、实验目的

向现有 Linux 内核加入一个新的系统调用从而在内核空间中实现对用户空间的读写。例 如,设计并实现一个新的内核函数 mycall( ),此函数通过一个引用参数的调用返回当前系 统时间,功能上基本与 gettimeofday( )相同。 2、实验内容与步骤 1.添加新调用的源代码

在/usr/src/linux-2.4.7-10/kernel/sys.c 中添加相应的调用代码 asmlinkage int sys_mycall(struct timeval *tv) { struct timeval ktv;

MOD_INC_USE_COUNT; do_gettimeofday(&ktv);

if (copy_to_user(tv,&ktv,sizeof(ktv))){ MOD_DEC_USE_COUNT; return -EFAULT; }

MOD_DEC_USE_COUNT; return 0; }

2.连接系统调用

a、 修改/usr/src/linux-2.4.7-10/include/asm-i386/unistd.h,

在系统调用列表后面相应位置添加一行 #define _NR_mycall 222 新增加的调用号位 222

b、 修改/usr/src/linux-2.4.7-10/arch/i386/kernel/entry.S 在 sys_call_table[]清单最后添加一行 .long SYMBOL_NAME(sys_mycall) 3. 重建新的 Linux 内核 cd /usr/src/linux-2.4.7-10/ make mrproper make oldconfig made dep make clean make bzImage make modules

make modules_install make install 4.重建引导信息 a、 在 /boot/grub/grub.conf 中 自 己 添 加一条 新 的 启动选 项 , 并使该 选 项 指 向 vimlinuz-2.4.7-10custom b、 重新安装 grub

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

6.修改/usr/lib/bcc/include/unistd.h,在系统调用列表后面相应位置添加一行 #define _NR_mycall 222 3、实验结果测试:

1、 编写程序测试 test.c: #include #include

_syscall1(int,mycall,struct timeval *,thetime) main()

{ struct timeval gettime; struct timeval mycalltime;

gettimeofday(&gettime,NULL); mycall(&mycalltime);

printf(\printf(\}

4 进程管理实验

4.1 实验 3.1 进程行为观察 实验内容

1. 在 Linux 下,分别用 snice、 skill、 top 等命令和/proc 中的有关目录、文件观察系统 中进程运行情况和 CPU 工作情况。 2. 在 Linux 下,用 ptrace()、gdb 跟踪一个进程的运行情况,用 strace 工具跟踪 fork() 过 程,用 ltrace 工具跟踪 execl() 过程。观察并分析跟踪信息。 4.2 实验 3.2 代码分析

实验内容

阅读分析 Linux 中的进程建立模块、进程撤销模块、进程调度模块、系统调用总入口模 块,了解进程的创建、执行、等待、退出等过程。写出代码分析报告。 4.3 实验 3.3 Shell 编程 1、实验目的

通过编写 shell 程序,了解子进程的创建和父进程与子进程间的协同,获得多进程程 序的编程经验。 2、实验内容 1

设计一个简单的 shell 解释程序,能实现基本的 bsh 功能。 ( 1)设计思想:

将每一条命令分子段压入 argv 栈。然后再子进程中调用 execvp()来实现该命令的功能。 shell 程序源代码清单: ( 2)源代码清单 源代码 xsh.c: #include #include #include

#define BUFFERSIZE 256

//最简单的 shell,只是简单的执行命令调用,没有任何的其他功能 int main() {

char buf[BUFFERSIZE],*cmd,*argv[100]; char inchar;

int n,sv,buflength; buflength = 0; for(;;) {

printf(\

//处理过长的命令; inchar = getchar();

while (inchar != '\\n' && buflength < BUFFERSIZE ){ buf[buflength++] = inchar; inchar = getchar(); }

if (buflength > BUFFERSIZE){

printf(\buflength = 0; continue; } else

buf[buflength] = '\\0';

//解析命令行,分成一个个的标记 cmd=strtok(buf,\if(cmd) {

if(strcmp(cmd,\n=0;

argv[n++]=cmd;

while(argv[n++]=strtok(NULL,\if(fork()==0) { execvp(cmd,argv);

fprintf(stderr,\exit(1); }

wait(&sv); buflength = 0; } } }

( 3)测试结果:

[root@localhost Work]# make xsh.c make: Nothing to be done for `xsh.c'. [root@localhost Work]# make xsh make: `xsh' is up to date. [root@localhost Work]# ./xsh => ps

PID TTY TIME CMD 29297 pts/2 00:00:00 bash 29344 pts/2 00:00:00 xsh 29345 pts/2 00:00:00 ps => ps | more

ps: error: Garbage option. usage: ps -[Unix98 options] ps [BSD-style options]

ps --[GNU-style long options] ps --help for a command summary => ps > ps.txt

ps: error: Garbage option. usage: ps -[Unix98 options] ps [BSD-style options]

ps --[GNU-style long options] ps --help for a command summary => ls

fibno_timer.c ksamp.c psTimeInfo.c shell1.c shell2.c timer.c xshbk xsh.c xshrb.c

ksamp ls.txt shell1 shell1.c~ shell3.c xsh xshbk.c xshrb => chsh

Changing shell for root. New shell [/bin/bash]:

#define MODULE

#include #include #include #include #include #include #include #include #include #include struct proc_dir_entry *proc_pf;

struct proc_dir_entry *proc_pfcount, *proc_jiffies; extern unsigned long jiffies, pfcount;

static inline struct proc_dir_entry *proc_pf_create( const char* name, mode_t mode,

get_info_t *get_info)

return create_proc_info_entry( name, mode, proc_pf, get_info); }

int get_pfcount(char *buffer, char **start, off_f offset, int length) {

int length =0;

length = sprintf(buffer, \return length; }

int get_jiffies(char *buffer, char **start, off_f offset, int length) {

int length =0;

length = sprintf(buffer, \return length; }

int init_module(void) {

proc_pf =proc_mkdir(\

proc_pf_create(\proc_pf_create(\return 0; }

void cleanup_module(void)

{

remove_proc_entry(\remove_proc_entry(\remove_proc_entry(\

6. 进程通信

6.1 实验 5.1 观察实验 1、实验内容

在 Linux 下,用 ipcs()命令观察进程通信情况。 2、实验原理

Linux IPC 继承了 Unix System V 及 DSD 等,共有 6 种机制: 信号(signal)、管道(pipe 和命名管道(named piped)、消息队列( message queues)、共享内存( shared memory segments)、信号量( semaphore)、套接字( socket)。 本实验中用到的几种进程间通信方式:

( 1)共享内存段( shared memory segments)方式

– 将2个进程的虚拟地址映射到同一内存物理地址,实现内存共享

– 对共享内存的访问同步需由用户进程自身或其它 IPC 机制实现(如信号量) – 用户空间内实现,访问速度最快。

– Linux 利用 shmid_ds 结构描述所有的共享内存对象。 ( 2)信号量( semaphore)方式 – 实现进程间的同步与互斥 – P/V 操作, Signal/wait 操作

– Linux 利用 semid_ds 结构表示 IPC 信号量 ( 3)消息队列( message queues)方式 – 消息组成的链表,进程可从中读写消息。

– Linux 维护消息队列向量表 msgque,向量表中的每个元素都有一个指向 msqid_ds 结构的指针,每个 msqid_ds 结构完整描述一个消息队列 LINUX 系统提供的 IPC 函数有: z msgget(关键字,方式):创建或打开一个消息队列

z msgsnd(消息队列标志符,消息体指针,消息体大小,消息类型): 向队列传递消息 z msgrcv(消息队列标志符,消息体指针,消息体大小,消息类型): 从队列中取消息 z msgctl(消息队列标志符,获取/设置/删除, maqid_ds 缓冲区指针): 获取或设置 某个队列信息,或删除某消息队列

Linux 系统中,内核, I/O 任务,服务器进程和用户进程之间采用消息队列方式,许多 微内核 OS 中,内核和各组件间的基本通信也采用消息队列方式. 3

、实验结果—示例(见以下截图)

6.2 实验 5.2 代码分析 1、实验内容

阅读 Linux/Minix 中以下模块的调用主线,并写出分析报告 z kill 系统调用内部实现模块调用主线 z pipe 系统调用内部实现模块调用主线 2、实验结果—示例

进程的创建: #include #include #include int main () {

pid_t pid; pid = fork (); if (pid == 0) {

printf (\} else {

pid = fork (); if (pid == 0) {

printf (\} else {

printf (\} }

return 0; }

kill 命令调用主线:

先向 Linux 系统的内核发送一个系统操作信号和某个程序的进程标识号,然后系统内核 就可以对进程标识号指定的进程进行操作。比如在 top 命令中,我们看到系统运行许多进程,

有时就需要使用 kill 中止某些进程来提高系统资源。在讲解安装和登陆命令时,系统多个 虚拟控制台的作用是当一个程序出错造成系统死锁时,可以切换到其它虚拟控制台工作关闭 这个程序。 管道间通信: #include #include #include #include int main () {

pid_t pid; int pfd[2];

char send_buf[100], recv_buf[100]; pipe (pfd); pid = fork (); if (pid == 0) {

close (pfd[0]);

strncpy (send_buf, \lockf (pfd[1], F_LOCK, 0);

write (pfd[1], send_buf, strlen(send_buf)); sleep (1);

lockf (pfd[1], F_ULOCK, 0); close (pfd[1]); } else {

pid = fork (); if (pid == 0) {

close (pfd[0]);

strncpy (send_buf, \lockf (pfd[1], F_LOCK, 0);

write (pfd[1], send_buf, strlen(send_buf)); sleep (1);

lockf (pfd[1], F_ULOCK, 0); close (pfd[1]); } else {

read (pfd[0], recv_buf, sizeof(recv_buf)); printf (\

read (pfd[0], recv_buf, sizeof(recv_buf)); printf (\wait (0); wait (0);

close (pfd[0]); } }

return 0; }

pipe 的调用主线: pipe (pfd);pid = fork (); 如果 pid == 0,则调用:

close (pfd[0]);strncpy ();lockf ();write ();sleep ();lockf ();函数,通过进程向管道中写入数据, 如果调用不成功,调用了 close (pfd[0]);strncpy ();lockf ();write ();sleep ();lockf ();函数,创 建一个进程,如果成功,通过该进程向管道中写入数据;如果不成功,则从管道中读出数据。 6.3 实验 5.3 进程同步实验 1、实验目的

深入学习 Linux 内核,在学习 linux 内核的同步机制的同时,深入分析各种同步机制 的实现方案,在此基础上设计和编写一套同步原语。 2、实验内容

设计并实现一个新的同步原语,该原语允许多个进程因一个事件而阻塞,直到其他进程 产生这个信号为止。当一个进程产生一个事件的信号时,所有因这个事件而阻塞的进程都取 消阻塞。如果信号产生时,没有进程因为这个信号而阻塞,那么这个信号无效。 实现下列系统调用: int evntopen(int); int evntclose(int);

int evntwait(int evenNum); void evntsig(int evenNum); 3、实验原理

在深入学习软中断信号,信号量和管道的工作原理和实现机制后,我们知道,一个事件 必须有一个事件号,一系列的进程等待这个事件发生,那么肯定需要一个等待队列,所以睡 眠的进程就放到这个队列中去。通过考察 linux 中如 wake_up、 sleep_on 等的实现我们 将构建上述同步原语。

首先建立一个存放事件的队列,通过简单的链表即可实现。 //定义结构,保存事件队列 typedef struct __eventStruct{ int eventNum;

wait_queue_head_t *p; struct __eventStruct *next; } __eventNode;

//两个全局变量记录链表的头尾

__eventNode *lpmyevent_head = NULL; //指向链表头 __eventNode *lpmyevent_end = NULL; //指向链表尾 4、实验步骤及部分代码清单 ( 1)实现函数定义

定义 Open 同步原语,当 Open 一个事件的时候,有两种情况:一是事件已经存在,只 需要返回 Event 事件号即可,第二种是事件链表中没有该事件,简单的处理办法直接 返回-1,表示事件不存在。当传递参数 0 的时候,将产生一个新事件。 int eventopen(int eventNum) {

__eventNode *newNode; __eventNode *prevNode; if(eventNum)

if(!scheventNum,&prev)) return -1;

else return eventNum; else {

newNode = (__eventNode *)kmalloc( sizeof(__eventNode), GFP_KERNEL);

newNode->p =(wait_queue_head_t *)kmalloc( sizeof(wait_queue_head_t)); newNode->next = NULL;

newNode->p->task_list.next = &newNode->p->task_list;

newNode->p->task_list_prev = &newNode->p->task_list; if(!lpmyevent_head) {

newNode->eventNum =2;

lpmyevent_head =lpmyevent_end =newNode; return newNode->eventNum; } else {

newNode->eventNum = lpmyevent_end->eventNum + 2; lpmyevent_end->next = newNode; lpmyevent_end = newNode; }

return newNode->eventNum; }

return 0; }

定义 evntsig(eventNum),evntsig()要完成的功能是唤醒所有等待该事件发生的 进程。我们只需要在事件列表上找到该事件,然后调用 wake_up()。 int evntsig(int eventNum) {

__eventNode *tmp = NULL; __eventNode *prev = NULL;

if(!(tmp = scheventNum(eventNum,&prev)) return;

wake_up(tmp->p); return 1; }

evntwait()将一个进程加到他要等待的事件队列中去。 int evntwait(int eventNum) {

__eventNode *tmp;

__eventNode *prev = NULL; wait_queue_t wait; unsigned long flags;

if(tmp =scheventNum(eventNum,&prev)) {

wait.task = current;

current->state = TASK_UNINTERRUPTIBLE; write_lock_irqsave(&temp->p->lock,flags);

__add_wait_queue(tmp->p,&wait); write_unlock(&tmp->p-lock); schedule();

write_lock_irq(&tmp->p->lock);

__remove_wait_queue(tmp->p,&wait);

write_unlock_irqrestore(&tmp->p->lock,flags); } }

至于 evntclose()先在事件队列中找到该事件,然后根据事件在该链表中的位置进行

特定的处理,之后唤醒所有睡眠在该事件上的事件,最后将这个事件从链表中删除,释 放内存。

int evntclose(int eventNum) {

__eventNode *prev;

__eventNode *releaseItem; evntsig(eventNum);

if(releaseItem = scheventNum(eventNum,&prev)) {

if (releaseItem == lpmyevent_end) lpmyevent_end = prev;

if (releaseItem == lpmyevent_head) {

lpmyevent_head = lpmyevent_head->next; goto wake; }

prev->next = releadeItem->next; } wake:

if(releaseItem) {

kfree(releaseItem); return 1; }

return 0; }

最后是一个辅助调度函数 scheventNum()函数。他有两个参数 eventNum 和 prev,

前者是事件号,后者是要返回的一个 __eventNode 类型的指向指针的指针,它将指向要 要寻找节点的上一节点。函数返回要寻找的事件节点。

__eventNode * scheventNum(int eventNum,__eventNode **prev) {

__eventNode *tmp = lpmyevent_head; *prev = NULL; while(tmp) {

if(tmp->eventNum == eventNum) return tmp; *prev = tmp; tmp =tmp->next; }

return NULLL; }

( 2)同步原语实现的函数转化为系统调用, 修改系统初始化代码,编译内核。 7 I/O设备管理实验 7.1 实验 6.1. 观察实验 1、实验内容

z 用 stat 命令查看机器上硬盘特别文件的 I 节点内容

z 在 Linux 下,查看/proc 与内存管理相关的文件,解释显示结果 2、实验结果—示例(见以下截图) 7.2 实验6.2 代码分析

阅读 Linux/Minix 中以下模块的调用主线( 1) print 函数内部实现模块调用 主线。( 2) scan 函数内部实现模块调用主线。写出分析报告。 7.3 实验6.3 编程实验 1、 实验目的

编写一个 daemon 进程,该进程定时执行 ps 命令,然后将该命令的输出写至文件 F1 尾 部。通过此实验,掌握 Linux I/O 系统相关内容。 2、 实验原理

在这个程序中,首先 fork 一个子程序,然后,关闭父进程,这样,新生成的子进程被 交给 init 进程接管,并在后台执行。

新生成的子进程里,使用 system 系统调用,将 ps 的输出重定向,输入到 f1.txt 里面。 3、源代码清单 下面是源程序: main() { int p; p = fork(); if(p > 0) {

exit(0); }

else if(p == 0) {

for(i = 0; i < 100; i++) {

sleep(100);

system(\} } else {

perror(\} }

7.4 实验6.4 设备驱动程序 1、实验目的

了解 Linux 的设备驱动程序的组织结构和设备管理机制,编写简单的字符设备和块设备 驱动程序。 2、实验内容

a. 编写字符型设备驱动程序,该字符设备包括5个基操作: scull_open()、 scull_write()、 scull_read、 scull_ioctl、 scull_release() , 同时还需要编写一个测试程序

b. 编写字符型设备驱动程序,该字符设备包括5个基操作: sbull_open()、 sbull_write()、 sbull_read、 sbull_ioctl、 sbull_release() 8. 文件系统管理实验 8.1 实验 7.1 代码分析 1、实验内容

阅读 Linux/Minix 中有关文件模块的调用主线,并写出分析报告,包括 z 文件建立模块,即系统调用 create() z 文件删除模块,即系统调用 rm() z 读/写模块,即 read/write 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 }

第 5 7 8 6:“ namei” ( 7 5 1 8 )将一路径名变换成一个“ inode”指针。“ uchar”是一个过程的

名字,它从用户程序数据区一个字符一个字符地取得文件路径名。

5 7 8 7:一个空“ inode”指针表示出了一个错,或者并没有具有给定路径名的文件存在。 5 7 8 8:对于出错的各种条件,请见 U P M 的 C R E AT ( I I )。 5 7 9 0:“ maknode” ( 7 4 5 5 )调用“ ialloc”创建一内存“ inode”,然后对其赋初值,并

使

其进入适当的目录。注意,显式地清除了“粘住”位( I S V T X )。 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 并且相应文件未被打开时,将删除该文件。在创

建文件时,该字段由“ m a k n o d e”赋初值为 1。系统调用“ link” ( 5 9 4 1 )可将其值加

1,系统调用“ unlink” ( 3 5 2 9 )则可将其值减 1。创建临时“工作文件”的程序应当在其终

止前执行“ unlink”系统调用将这些文件删除。 注意,“ unlink”系统调用本身并没有删除文件。当引用计数( i _ count )被减为 0 时( 7 3 5 0、 7 3 6 2 ),才删除该文件。

为了减少在程序或系统崩溃时遗留下来的临时文件所带来的问题,程序员应当遵守下列 约定:

(1) 在打开临时文件后立即对其执行“ unlink”操作。

(2) 应在“ tmp”目录下创建临时文件。在文件名中包括进程标识数就可构成一惟一文件名 C. 读写模块分析

int myrm_dir(char *path) {

DIR *dp;

struct dirent *dirp;

char *dirname, ch, *msg;

/* if iflag is 1, ask whether go into this directory */ msg = (char *)malloc(strlen(path) + 30); if (iflag) {

msg = strcat(strcpy(msg, path), \if (ch = getyn(msg), ch == 'n' || ch == 'N') return(0); }

/* open the directory */

if ((dp = opendir(path)) == NULL) {

fprintf(stderr, \return(-1); }

chdir(path);

/* read each entry in the directory and remove it*/ dirp = (struct dirent *)malloc(sizeof(struct dirent)); while ((dirp = readdir(dp)) != NULL) {

if (strcmp(dirp->d_name, \continue;

myrm1(dirp->d_name); }

/* close the directory */ if (closedir(dp)) {

fprintf(stderr, \return(-1); }

chdir(\

if ((dirname = strrchr(path, '/')) != NULL) dirname++;

else dirname = path;

/* remove this empty directory */ if (rmdir(dirname))

fprintf(stderr, \else if (vflag)

printf(\}

int myrm_file(char *path) { char ch, *msg; if (iflag) {

msg = (char *)malloc(strlen(path) + 25);

msg = strcat(strcpy(msg, path), \if (ch = getyn(msg), ch == 'n' || ch == 'N') return(0); }

if (unlink(path)) {

fprintf(stderr, \return(-1); }

if (vflag)

printf(\}

/* get yes or no from the keyboard */ char getyn(char *msg) {

char first,ch;

/* give out some tips */ printf(\while (1) {

first = ch = getchar();

while (first != '\\n' && (ch = getchar()) != '\\n') ;

if (first == 'y' || first == 'Y' || first == 'n' || first == 'N') return(first);

printf(\} }

int usage(void) {

printf(\exit(0); }

其中,系统调用 stat/fstat 用于从一个 i 节点获得文件的状态信息, stat 用给出的路径名 从磁盘寻找指定文件的 i 节点, fstat 用给出的已打开文件句柄从核心活动 i 节点表中寻找,

stat 和 fstat 将数据放入调用者提供的 stat 结构中。 #include #include

int stat(path,sbuf) /* get file status */ char *path; /* path name */

struct stat *sbuf; /* status information */ /* return O on success or -1 on error */ int fstat(fd,sbuf) /* get file sttus */ int fd; /* file descriptor */ struct stat *sbnf;

/* returns O on success or -1 on error */

本文来源:https://www.bwwdw.com/article/yyfa.html

Top