实验内容
实验环境
Engintime Linux Lab 实验平台
所有修改过的文件都贴在文末,以便对照,点击前往:sys.c、memory.c、printk.c、unistd.h、sys.h、kernel.h、system_call.s。
int dump_physical_mem()
新建Linux011 Kernel
项目
/include/unistd.h
中定义系统调用符号
在162行插入#define __NR_dump_physical_mem 87
/include/linux/sys.h
中声明系统调用函数
88行插入extern int sys_dump_physical();
179行插入sys_dump_physical_mem
/kernel/sys.c
末尾定义dump_physical_mem
函数
显然,todo就是实验内容的核心部分,它要完成以下功能:
但是,/kernel/sys.c
无法直接访问内存信息,/mm/memory.c
可以。todo要干的事在/mm/memory.c
中很容易完成,所以可以把todo的内容封装成函数在/mm/memory.c
完成,供/kernel/sys.c
调用。
/mm/memory.c
中定义physical_mem()
实现todo
在文(在)件(哪)末(都)尾(行)添加函数定义:
int physical_mem(){
int i, free=0; /* free表示空闲页的总数 */
unsigned long addr; /*分配的物理内存页地址*/
for(i=0 ; i
解释下代码出现的几个陌生变量和函数。 PAGING_PAGES、mem_map[]、get_free_page()、free_page()
table_mapping()
没必要新建项目,我们直接在这个项目上工作就好了。添加系统调用上文已经介绍过了,那这里我偷偷懒,只放图了。
/kernel/sys.c
末尾添加sys_table_mapping()
定义
int sys_table_mapping(){
unsigned long index_of_dir, index_of_table;
unsigned long entry;
unsigned long page_table_base;
unsigned long page_dir_base = 0;
__asm("cli");
calc_mem(); // 显示内存空闲页面数。
// 首先打印输出页目录信息
fprintk(1,"Page Directory(PFN:0x0 | LA:0x00000000)\n");
// 第一层循环,遍历页目录中的所有 PDE
for(index_of_dir = 0; index_of_dir < 1024; index_of_dir++){
entry = ((unsigned long*)page_dir_base)[index_of_dir];
if(!(entry & 1)) /* 跳过无效的 PDE */
continue;
// 输出 PDE 信息
fprintk(1,"\tPDE: 0x%X -> Page Table(PFN:0x%X | LA:0x%08X)\n", index_of_dir, entry >> 12, 0xFFFFF000 & entry);
/* 页目录项的高20位即为页表的物理地址,页表的物理地址即为页表的逻辑地址 */
page_table_base = 0xFFFFF000 & entry;
/* 第二层循环,遍历页表中的所有 PTE */
for(index_of_table = 0; index_of_table < 1024; index_of_table++){
entry = ((unsigned long*)page_table_base)[index_of_table];
if(!(entry & 1)) /* 跳过无效的 PTE */
continue;
// 输出 PTE 信息
fprintk(1,"\t\tPTE: 0x%X -> Physical Page(PFN:0x%X | LA:0x%08X)\n", index_of_table,
entry >> 12, (index_of_dir << 22) | (index_of_table << 12));
}
}
__asm("sti");
return 0;
}
呃,咋一看是不是一脸懵。没关系,让我们来分析一下实验内容。
实验要求显示系统空闲页面(calc_mem()
干的就是这个)和当前页目录、PDE、PTE 。没错,sys_table_mapping
的定义中calc_mem()用来输出系统空闲页面数,剩下的就是输出页目录、PDE、PTE用的。你一定还发现了,代码里出现了陌生的未定义的函数,比如fprintk,实验要求输出到文本文件中,不是屏幕。来看看指导书的解释。
在sys_table_mapping函数中调用了一个名称为fprintk的函数用于向标准输出打印信息,而没有使用printk函数,这是由于本实验需要输出的内容较多,需要输出到文件中以便查看,但是printk虽然可以在屏幕上进行输出,但是却不能输出到文件中,所以需要实现一个fprintk函数。
所以,我们的思路就是,实现函数fprintk,将要输出的内容通过fprintk推到标准输出流stdout中,再将流中的内容保存到文本文档内。
实现fprintk()
在/kernel/printk.c
末添加fprintk的定义,还要添加两个头文件,一个静态声明。
int fprintk( int fd, const char * fmt, ... )
{
va_list args;
int i;
struct file * file;
struct m_inode * inode;
va_start (args, fmt);
i = vsprintf (logbuf, fmt, args);
va_end (args);
if( fd<3 )
{
__asm__ ("push %%fs\n\t"
"push %%ds\n\t"
"pop %%fs\n\t"
"pushl %0\n\t"
"pushl $_logbuf\n\t"
"pushl %1\n\t"
"call _sys_write\n\t"
"addl $8,%%esp\n\t"
"popl %0\n\t"
"pop %%fs"
::"r" (i),"r" (fd):"ax", "dx");
}
else
{
if( !( file=task[0]->filp[fd] ) )
return 0;
inode=file->f_inode;
__asm__ ("push %%fs\n\t"
"push %%ds\n\t"
"pop %%fs\n\t"
"pushl %0\n\t"
"pushl $_logbuf\n\t"
"pushl %1\n\t"
"pushl %2\n\t"
"call _file_write\n\t"
"addl $12,%%esp\n\t"
"popl %0\n\t"
"pop %%fs"
::"r" (i), "r" (file), "r" (inode) );
}
return i;// 返回字符串长度
}
include/linux/kernel.h
中添加fprintk声明
calc_mem()的输出对象也是屏幕,我们要把它改成向流输出,把printk全部改成fprintk。这个函数定义在memory.c的760行。
F7编译,冒得错误。
编写应用程序调用table_mapping
vi meminfo.c
#define __LIBRARY__
#include
#define __NR_table_mapping 88
_syscall0( int, table_mapping )
int main( int argc, char** argv ){
table_mapping();
return 0;
}
gcc meminfo.c -o meminfo
编译
meminfo > a.txt
执行并将标准输出流stdout中的内容输出到文本文件a.txt
sync
同步
mcopy -n a.txt b:a.txt
将a.txt拷贝到软盘b
打开软盘b
以下为所有修改过的文件和一些说明
...
int sys_dump_physical_mem(){
physical_mem();
return 0;
}
int sys_table_mapping(){
unsigned long index_of_dir, index_of_table;
unsigned long entry;
unsigned long page_table_base;
unsigned long page_dir_base = 0;
__asm("cli");
calc_mem(); // 显示内存空闲页面数。
// 首先打印输出页目录信息。格式为:Page Directory(页目录的物理页框号 | 所在线性地址)
fprintk(1,"Page Directory(PFN:0x0 | LA:0x00000000)\n");
// 第一层循环,遍历页目录中的所有 PDE
for(index_of_dir = 0; index_of_dir < 1024; index_of_dir++)
{
entry = ((unsigned long*)page_dir_base)[index_of_dir];
if(!(entry & 1)) /* 跳过无效的 PDE */
continue;
// 输出 PDE 信息,格式如下:
// PDE: 下标 -> Page Table(页表的物理页框号 | 所在的线性地址)
fprintk(1,"\tPDE: 0x%X -> Page Table(PFN:0x%X | LA:0x%08X)\n", index_of_dir, entry >> 12, 0xFFFFF000 & entry);
/* 页目录项的高20位即为页表的物理地址,页表的物理地址即为页表的逻辑地址 */
page_table_base = 0xFFFFF000 & entry;
/* 第二层循环,遍历页表中的所有 PTE */
for(index_of_table = 0; index_of_table < 1024; index_of_table++)
{
entry = ((unsigned long*)page_table_base)[index_of_table];
if(!(entry & 1)) /* 跳过无效的 PTE */
continue;
// 输出 PTE 信息,格式如下:
// PTE: 下标 -> Physical Page(物理页框号 | 所在的线性地址)
fprintk(1,"\t\tPTE: 0x%X -> Physical Page(PFN:0x%X | LA:0x%08X)\n", index_of_table,
entry >> 12, (index_of_dir << 22) | (index_of_table << 12));
}
}
__asm("sti");
return 0;
}
回到目录
...
// 计算内存空闲页面数并显示。
void calc_mem(void)
{
int i,j,k,free=0;
long * pg_tbl;
// 扫描内存页面映射数组mem_map[],获取空闲页面数并显示。
for(i=0 ; i
回到目录
/*
* 当处于内核模式时,我们不能使用printf,因为寄存器fs 指向其它不感兴趣的地方。
* 自己编制一个printf 并在使用前保存fs,一切就解决了。
*/
#include // 标准参数头文件。以宏的形式定义变量参数列表。主要说明了-个
// 类型(va_list)和三个宏(va_start, va_arg 和va_end),用于
// vsprintf、vprintf、vfprintf 函数。
#include // 标准定义头文件。定义了NULL, offsetof(TYPE, MEMBER)。
#include // 内核头文件。含有一些内核常用函数的原形定义。
#include
#include
static char buf[1024];
static char logbuf[1024];
// 下面该函数vsprintf()在linux/kernel/vsprintf.c 中
extern int vsprintf(char * buf, const char * fmt, va_list args);
// 内核使用的显示函数。
int printk(const char *fmt, ...)
{
va_list args;
int i;
va_start (args, fmt); // 参数处理开始函数。在(include/stdarg.h)
i = vsprintf (buf, fmt, args); // 使用格式串fmt 将参数列表args 输出到buf 中。
// 返回值i 等于输出字符串的长度。
va_end (args); // 参数处理结束函数。
__asm__ ("push %%fs\n\t" // 保存fs。
"push %%ds\n\t" "pop %%fs\n\t" // 令fs = ds。
"pushl %0\n\t" // 将字符串长度压入堆栈(这三个入栈是调用参数)。
"pushl $_buf\n\t" // 将buf 的地址压入堆栈。
"pushl $0\n\t" // 将数值0 压入堆栈。是通道号channel。
"call _tty_write\n\t" // 调用tty_write 函数。(kernel/chr_drv/tty_io.c)。
"addl $8,%%esp\n\t" // 跳过(丢弃)两个入栈参数(buf,channel)。
"popl %0\n\t" // 弹出字符串长度值,作为返回值。
"pop %%fs" // 恢复原fs 寄存器。
::"r" (i):"ax", "cx", "dx"); // 通知编译器,寄存器ax,cx,dx 值可能已经改变。
return i; // 返回字符串长度。
}
int fprintk( int fd, const char * fmt, ... )
{
va_list args;
int i;
struct file * file;
struct m_inode * inode;
va_start (args, fmt);
i = vsprintf (logbuf, fmt, args);
va_end (args);
if( fd<3 )
{
__asm__ ("push %%fs\n\t"
"push %%ds\n\t"
"pop %%fs\n\t"
"pushl %0\n\t"
"pushl $_logbuf\n\t"
"pushl %1\n\t"
"call _sys_write\n\t"
"addl $8,%%esp\n\t"
"popl %0\n\t"
"pop %%fs"
::"r" (i),"r" (fd):"ax", "dx");
}
else
{
if( !( file=task[0]->filp[fd] ) )
return 0;
inode=file->f_inode;
__asm__ ("push %%fs\n\t"
"push %%ds\n\t"
"pop %%fs\n\t"
"pushl %0\n\t"
"pushl $_logbuf\n\t"
"pushl %1\n\t"
"pushl %2\n\t"
"call _file_write\n\t"
"addl $12,%%esp\n\t"
"popl %0\n\t"
"pop %%fs"
::"r" (i), "r" (file), "r" (inode) );
}
return i;// 返回字符串长度
}
回到目录
...
#define __NR_dump_physical_mem 87
#define __NR_table_mapping 88
...
回到目录
extern int sys_setup (); // 系统启动初始化设置函数。 (kernel/blk_drv/hd.c)
extern int sys_exit (); // 程序退出。 (kernel/exit.c)
extern int sys_fork (); // 创建进程。 (kernel/system_call.s)
extern int sys_read (); // 读文件。 (fs/read_write.c)
extern int sys_write (); // 写文件。 (fs/read_write.c)
extern int sys_open (); // 打开文件。 (fs/open.c)
extern int sys_close (); // 关闭文件。 (fs/open.c)
extern int sys_waitpid (); // 等待进程终止。 (kernel/exit.c)
extern int sys_creat (); // 创建文件。 (fs/open.c)
extern int sys_link (); // 创建一个文件的硬连接。 (fs/namei.c)
extern int sys_unlink (); // 删除一个文件名(或删除文件)。 (fs/namei.c)
extern int sys_execve (); // 执行程序。 (kernel/system_call.s)
extern int sys_chdir (); // 更改当前目录。 (fs/open.c)
extern int sys_time (); // 取当前时间。 (kernel/sys.c)
extern int sys_mknod (); // 建立块/字符特殊文件。 (fs/namei.c)
extern int sys_chmod (); // 修改文件属性。 (fs/open.c)
extern int sys_chown (); // 修改文件宿主和所属组。 (fs/open.c)
extern int sys_break (); // (-kernel/sys.c)
extern int sys_stat (); // 使用路径名取文件的状态信息。 (fs/stat.c)
extern int sys_lseek (); // 重新定位读/写文件偏移。 (fs/read_write.c)
extern int sys_getpid (); // 取进程id。 (kernel/sched.c)
extern int sys_mount (); // 安装文件系统。 (fs/super.c)
extern int sys_umount (); // 卸载文件系统。 (fs/super.c)
extern int sys_setuid (); // 设置进程用户id。 (kernel/sys.c)
extern int sys_getuid (); // 取进程用户id。 (kernel/sched.c)
extern int sys_stime (); // 设置系统时间日期。 (-kernel/sys.c)
extern int sys_ptrace (); // 程序调试。 (-kernel/sys.c)
extern int sys_alarm (); // 设置报警。 (kernel/sched.c)
extern int sys_fstat (); // 使用文件句柄取文件的状态信息。(fs/stat.c)
extern int sys_pause (); // 暂停进程运行。 (kernel/sched.c)
extern int sys_utime (); // 改变文件的访问和修改时间。 (fs/open.c)
extern int sys_stty (); // 修改终端行设置。 (-kernel/sys.c)
extern int sys_gtty (); // 取终端行设置信息。 (-kernel/sys.c)
extern int sys_access (); // 检查用户对一个文件的访问权限。(fs/open.c)
extern int sys_nice (); // 设置进程执行优先权。 (kernel/sched.c)
extern int sys_ftime (); // 取日期和时间。 (-kernel/sys.c)
extern int sys_sync (); // 同步高速缓冲与设备中数据。 (fs/buffer.c)
extern int sys_kill (); // 终止一个进程。 (kernel/exit.c)
extern int sys_rename (); // 更改文件名。 (-kernel/sys.c)
extern int sys_mkdir (); // 创建目录。 (fs/namei.c)
extern int sys_rmdir (); // 删除目录。 (fs/namei.c)
extern int sys_dup (); // 复制文件句柄。 (fs/fcntl.c)
extern int sys_pipe (); // 创建管道。 (fs/pipe.c)
extern int sys_times (); // 取运行时间。 (kernel/sys.c)
extern int sys_prof (); // 程序执行时间区域。 (-kernel/sys.c)
extern int sys_brk (); // 修改数据段长度。 (kernel/sys.c)
extern int sys_setgid (); // 设置进程组id。 (kernel/sys.c)
extern int sys_getgid (); // 取进程组id。 (kernel/sched.c)
extern int sys_signal (); // 信号处理。 (kernel/signal.c)
extern int sys_geteuid (); // 取进程有效用户id。 (kenrl/sched.c)
extern int sys_getegid (); // 取进程有效组id。 (kenrl/sched.c)
extern int sys_acct (); // 进程记帐。 (-kernel/sys.c)
extern int sys_phys (); // (-kernel/sys.c)
extern int sys_lock (); // (-kernel/sys.c)
extern int sys_ioctl (); // 设备控制。 (fs/ioctl.c)
extern int sys_fcntl (); // 文件句柄操作。 (fs/fcntl.c)
extern int sys_mpx (); // (-kernel/sys.c)
extern int sys_setpgid (); // 设置进程组id。 (kernel/sys.c)
extern int sys_ulimit (); // (-kernel/sys.c)
extern int sys_uname (); // 显示系统信息。 (kernel/sys.c)
extern int sys_umask (); // 取默认文件创建属性码。 (kernel/sys.c)
extern int sys_chroot (); // 改变根系统。 (fs/open.c)
extern int sys_ustat (); // 取文件系统信息。 (fs/open.c)
extern int sys_dup2 (); // 复制文件句柄。 (fs/fcntl.c)
extern int sys_getppid (); // 取父进程id。 (kernel/sched.c)
extern int sys_getpgrp (); // 取进程组id,等于getpgid(0)。(kernel/sys.c)
extern int sys_setsid (); // 在新会话中运行程序。 (kernel/sys.c)
extern int sys_sigaction (); // 改变信号处理过程。 (kernel/signal.c)
extern int sys_sgetmask (); // 取信号屏蔽码。 (kernel/signal.c)
extern int sys_ssetmask (); // 设置信号屏蔽码。 (kernel/signal.c)
extern int sys_setreuid (); // 设置真实与/或有效用户id。 (kernel/sys.c)
extern int sys_setregid (); // 设置真实与/或有效组id。 (kernel/sys.c)
extern int sys_sigpending();
extern int sys_sigsuspend();
extern int sys_sethostname();
extern int sys_setrlimit();
extern int sys_getrlimit();
extern int sys_getrusage();
extern int sys_gettimeofday();
extern int sys_settimeofday();
extern int sys_getgroups();
extern int sys_setgroups();
extern int sys_select();
extern int sys_symlink();
extern int sys_lstat();
extern int sys_readlink();
extern int sys_uselib();
extern int sys_dump_physical_mem();
extern int sys_table_mapping();
// 系统调用函数指针表。用于系统调用中断处理程序(int 0x80),作为跳转表。
// 数组元素为系统调用内核函数的函数指针,索引即系统调用号
fn_ptr sys_call_table[] = {
sys_setup, //0
sys_exit, //1
sys_fork, //2
sys_read, //3
sys_write, //4
sys_open, //5
sys_close, //6
sys_waitpid, //7
sys_creat, //8
sys_link, //9
sys_unlink, //10
sys_execve, //11
sys_chdir, //12
sys_time, //13
sys_mknod, //14
sys_chmod, //15
sys_chown, //16
sys_break, //17
sys_stat, //18
sys_lseek, //19
sys_getpid, //20
sys_mount, //21
sys_umount, //22
sys_setuid, //23
sys_getuid, //24
sys_stime, //25
sys_ptrace, //26
sys_alarm, //27
sys_fstat, //28
sys_pause, //29
sys_utime, //30
sys_stty, //31
sys_gtty, //32
sys_access, //33
sys_nice, //34
sys_ftime, //35
sys_sync, //36
sys_kill, //37
sys_rename, //38
sys_mkdir, //39
sys_rmdir, //40
sys_dup, //41
sys_pipe, //42
sys_times, //43
sys_prof, //44
sys_brk, //45
sys_setgid, //46
sys_getgid, //47
sys_signal, //48
sys_geteuid, //49
sys_getegid, //50
sys_acct, //51
sys_phys, //52
sys_lock, //53
sys_ioctl, //54
sys_fcntl, //55
sys_mpx, //56
sys_setpgid, //57
sys_ulimit, //58
sys_uname, //59
sys_umask, //60
sys_chroot, //61
sys_ustat, //62
sys_dup2, //63
sys_getppid, //64
sys_getpgrp, //65
sys_setsid, //66
sys_sigaction, //67
sys_sgetmask, //68
sys_ssetmask, //69
sys_setreuid, //70
sys_setregid, //71
sys_sigsuspend, //72
sys_sigpending, //73
sys_sethostname, //74
sys_setrlimit, //75
sys_getrlimit, //76
sys_getrusage, //77
sys_gettimeofday, //78
sys_settimeofday, //79
sys_getgroups, //80
sys_setgroups, //81
sys_select, //82
sys_symlink, //83
sys_lstat, //84
sys_readlink, //85
sys_uselib, //86
sys_dump_physical_mem,//87
sys_table_mapping //88
};
回到目录
/*
* 'kernel.h' contains some often-used function prototypes etc
*/
/*
* 'kernel.h'定义了一些常用函数的原型等。
*/
// 验证给定地址开始的内存块是否超限。若超限则追加内存。( kernel/fork.c)。
void verify_area (void *addr, int count);
// 显示内核出错信息,然后进入死循环。( kernel/panic.c)。
volatile void panic (const char *str);
// 标准打印(显示)函数。( init/main.c)。
int printf (const char *fmt, ...);
// 内核专用的打印信息函数,功能与printf()相同。( kernel/printk.c)。
int printk (const char *fmt, ...);
// 往tty 上写指定长度的字符串。( kernel/chr_drv/tty_io.c)。
int tty_write (unsigned ch, char *buf, int count);
// 通用内核内存分配函数。( lib/malloc.c)。
void *malloc (unsigned int size);
// 释放指定对象占用的内存。( lib/malloc.c)。
void free_s (void *obj, int size);
//
int physical_mem();
//
int fprintk( int fd, const char * fmt, ... );
#define free(x) free_s((x), 0)
/*
* This is defined as a macro, but at some point this might become a
* real subroutine that sets a flag if it returns true (to do
* BSD-style accounting where the process is flagged if it uses root
* privs). The implication of this is that you should do normal
* permissions checks first, and check suser() last.
*/
/*
* 下面函数是以宏的形式定义的,但是在某方面来看它可以成为一个真正的子程序,
* 如果返回是true 时它将设置标志(如果使用root 用户权限的进程设置了标志,则用
* 于执行BSD 方式的计帐处理)。这意味着你应该首先执行常规权限检查,最后再
* 检测suser()。
*/
#define suser() (current->euid == 0) // 检测是否是超级用户。
回到目录
...
nr_system_calls = 89 # Linux 0.11 版内核中的系统调用总数。
...
变量 | 描述 | 定义在where |
---|---|---|
PAGING_PAGES | 分页后的物理内存页数 | /mm/memory.c:137行 |
mem_map | 内存映射字节图(1 字节代表1 页内存),每个页面对应的字节用于标志页面当前被引用(占用)次数 | /mm/memory.c:151行 |
函数 | 描述 | 参数 | 返回值 |
---|---|---|---|
get_free_page | 在主内存中取空闲页面 | 无 | 返回空闲页面地址(如果无空闲也则返回0) |
free_page | 释放物理地址addr 开始的一页面内存 | unsigned long addr | 无 |
我要回去