定义要实现函数的功能,编写函数,在这里我们主要是添加一个系统功能调用,在/kernel目录下添加源文件myapi.c,同时修改该目录下的Makefile文件将该源文件加入内核编译的目标中去,只需要Makefile中的开始部分的obj-y =***语句最后添加myapi.o依赖项即可,sys_userlog函数的具体实现见后面的部分,在此为了测试给出如下简单的定义:
#include<linux/linkage.h> /* 包含asmlinkage 宏的头文件*/
asmlinkage long sys_userlog(char *logbuf)
{
return 0;
}
2、修改位于arch/x86/kernel/syscall_table_32.S 目录下的syscall_table.S文件中的系统调用表,在系统调用表的最末端添加一项: .long sys_userlog,同时计算出系统调用号,此处为311;
3、修改位于arch/x86/include/asm/unistd_32.h 文件,在调用号宏定义列表中添加一项#define __NR_userlog 311,同时把系统调用数的宏NR__syscalls的数值加1;
4、配置内核,为了不破坏上一次实验的结果,修改一下内核版本名称,然后编译安装内核,由于修改的内容影响到内核中的很多文件,编译的过程还是比较漫长。
5、最后给出一个简单的测试用例,用来在新内核启动后测试新的系统功能调用是否已经可以使用。
/***********************************
* testapp.c
***********************************/
#include<stdio.h>
#include<errno.h>
#include<asm/unistd.h>
#define __NR_userlog 311
//_syscall1(long,userlog,char *,logbuf) //2.6以上的内核对应的发行版没有这个宏了,可以从内核对应的头文件中copy到unistd中
int main()
{
syscall(333,1);//centos5.5 下用这个
printf("userlog return:%d\n",userlog(NULL));
}
由于C库还不支持我们的系统调用,所以只有使用上面的原始形式来调用,观察在Linux系统编程中常用的unistd.h头文件可以看到该文件也是使用上面这种原始的调用形式然后封装了提供给上层的。
6、将上面的程序编译后在修改后的内核上运行,得到结果如下图,得到预期结果,说明系统调用添加成功。
下面需要按实验要求来设计系统调用的功能,今天先写到这里。
内核可以读写文件:
在用户态,可以使用open,close,read,write等系统调用来访问文件。那么内核中呢?使用filp->open等函数进行操作,但是这些操作又有些需要注意的地方。
在实际应用中,我们可以把系统运行的一些状态记录到内存中,但内存毕竟小,而且在系统重启后,数据会丢失。那么是否可以在内核中开启一个线程将这些数据记录内存中的数据拷贝到磁盘的文件上呢?
在调用filp->f_op->read和filp->f_op->write等对文件的操作之前,应该先设置FS。
默认情况下,filp->f_op->read或者filp->f_op->write会对传进来的参数buff进行指针检查。如果不是在用户空间会拒绝访问。因为是在内核模块中,所以buff肯定不在用户空间,所以要增大其寻址范围。
拿filp->f_op->write为例来说明:
filp->f_op->write最终会调用access_ok ==> range_ok.
而range_ok会判断访问的地址是否在0 ~ addr_limit之间。如果在,则ok,继续。如果不在,则禁止访问。而内核空间传过来的buff肯定大于addr_limit。所以要set_fs(get_ds())。
这些函数在asm/uaccess.h中定义。以下是这个头文件中的部分内容:
#define MAKE_MM_SEG(s) ((mm_segment_t) { (s) })
#define KERNEL_DS MAKE_MM_SEG(-1UL)
#define USER_DS MAKE_MM_SEG(PAGE_OFFSET)
#define get_ds() (KERNEL_DS)
#define get_fs() (current_thread_info()->addr_limit)
#define set_fs(x) (current_thread_info()->addr_limit = (x))
#define segment_eq(a, b) ((a).seg == (b).seg)
可以看到set_fs(get_ds())改变了addr_limit的值。这样就使得从模块中传递进去的参数也可以正常使用了。
#include<linux/linkage.h>
#include<asm/uaccess.h>
#include<linux/kernel.h>
#include<linux/stat.h>
#include<linux/fcntl.h>
#include<asm/unistd.h>
#include<linux/syscalls.h>
asmlinkage long sys_userlog(char *logbuf,int *len,int r_w)
{
int log_fd;
if(0 > *len)
{
return 1;
}
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
if(0 == r_w) /*read one last log record*/
{
if(0 > (log_fd = sys_open("/var/log/my_userlog",O_RDONLY,0)))
{
set_fs(old_fs);
return 2;
}
sys_lseek(log_fd,-1-*len,2);
sys_read(log_fd,logbuf,*len); /*return real readed size*/
sys_close(log_fd);
}
else /*add one log record at end of log file*/
{
if(0 > (log_fd = sys_open("/var/log/my_userlog",O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR)))
{
set_fs(old_fs);
return 3;
}
sys_write(log_fd,logbuf,*len);
sys_write(log_fd,"\n",1); /*open a new line*/
sys_close(log_fd);
}
set_fs(old_fs);
return 0;
}