linux系统调用

1.系统调用功能

    在Linux内核中增加一个系统调用,并编写对应的linux应用程序。利用该系统调用:(1)能够返回指定进程(通过指定PID)的任务描述符;(2)能够返回指定进程(通过指定PID)的进程地址空间的布局和统计信息(代码段、数据段、BSS段、堆、栈等区域的位置和大小、包含多少个虚拟内存区VMA、每个VMA的属性、该进程页表的地址、已映射的物理内存大小等。)(该题目需要研究Linux 进程描述符和内存描述符mm_struct。)

2.实验设计及环境

    Linux系统中添加新的系统调用有两种方法。方法一:直接修改内核源码,在内核中增加一个系统调用实现函数,并将该函数的名字添加到system_call_table中。该函数在system_call_table中的索引即为新增系统调用的系统调用号。最后重新编译内核源码,生成新的内核。方法二:不用编译内核,在内核模块中实现一个系统调用实现函数,并将该函数的名字添加到system_call_table中。本实验采用方法一实现添加系统调用。

    实验环境:ubuntu14.0+Linux内核3.13.0,利用apt-get install linux-source-3.13.0命令下载内核,下载的内核默认存放在/usr/src目录下,然后对该目录下的linux-source-3.13.0- tar.bz2进行解压缩,解压缩的文件放在Linux-source-3.13.0目录下。接下来开始添加系统调用。

3.实验实现

3.1添加系统调用

第一步:编辑syscall_64.tbl文件

syscall_64.tbl文件位于linux-source-3.13.0/arch/x86/syscalls/sysc all_64.tbl(ubuntu是64位),主要定义系统调用号。本实验定义新添加的系统调用号为314,在314号添加“314common mycall sys_ni_syscall”sys_ni_syscall是最简单的系统调用服务例程,表面上看,它可能并没有什么用处,但是,它在sys_call_table中占据了很多位置。多数位置上的sys_ni_syscal都代表了那些已经被内核中淘汰或者没有实际用处的系统调用。


第二步:编辑unisted.h文件

 Unistd.h位于/usr/include/asm-generic/unistd.h,为每个系统调用规定唯一的编号。添加“#define __NR_mycall 314  __SYSCALL(__Nrmycall, sys_mycall)”由于新添加了一个系统调用,__NR_syscalls需要加1,由原来的274变为275,表明有275个系统调用函数。

linux系统调用_第1张图片

第三步:编辑syscalls.h文件

syscalls.h文件位于linux-source-3.13.0/include/linux/syscalls.h,用于声明系统调用函数。在文件最后添加“asmlinkage long sys_call(pid_t pid,char __user * buf);”,asmlinkage宏是系统调用函数的关键字,表示函数通过堆栈而不是通过寄存器传递参数。sys_mycall是函数名,第一个参数是pid进程号,第二个参数用于存储task_struct分信息,由内核态传递到用户态。


第四步:编辑mycall.c文件

在linux-source-3.13.0/kernel/目录下,新建mycall.c文件。mycall.c文件运行在内核态,为用户态提供服务。实验要求由pid获得task_struct的相关信息。把需要获去的task_struct的信息,封装为一个结构体,最后把该结构体传到用户态。linux内核提供find_task_by_vpid(),pid是唯一标志一个进程,通过该函数,直接获得该pid标志的进程task_struct。

#include 
#include 
#include 
#include 
#include 
#include 
struct mycall_taskInfo
{
int pid;
unsigned long start_code;           /* 代码段起始地址  */
unsigned long end_code;
unsigned long start_data;	    /* 数据段起始地址  */
unsigned long end_data;
unsigned long start_brk;            /* brk地址    */
unsigned long brk; 
unsigned long start_stack;          /* 栈起始地址  */
unsigned long end_stack;
unsigned long pgd;                  /* 虚拟地址页表首地址  */
int vma_count;	               /* mm_struct中,number of VMAs */
unsigned long  start_vma[256];       /*  每个VMA的起始地址 */
unsigned long  end_vma[256];
unsigned long vmas_flags[256];      /* VMA的属性,VM_area_struct */	};

asmlinkage long sys_mycall(pid_t pid,char __user * buf)
{
struct task_struct * task=NULL;          /* 任务描述符  */
struct mm_struct * task_mm=NULL;         /* 内存描述符  */
struct vm_area_struct * VMAList=NULL;    /* 虚拟地址区域  */
struct mycall_taskInfo  a[1];
unsigned long *pgd;
task=find_task_by_vpid(pid);          /*   由pid找到任务描述符  */
if(task==NULL)
printk("do not find task!!!!");
else
printk("find task!!!\n");
a[0].pid=task->pid;
task_mm=task->mm;                 
if(!task_mm)
{
task_mm=task->active_mm;
if(!task_mm)
{
printk("mm_struct error!!!!");
return;
}
}
pgd=task_mm->pgd;  
a[0].pgd=* pgd;
a[0].start_code=task_mm->start_code;
a[0].end_code=task_mm->end_code;
a[0].start_data=task_mm->start_data;
a[0].end_data=task_mm->end_data;
a[0].start_brk=task_mm->start_brk;
a[0].brk=task_mm->brk;
a[0].start_stack=task_mm->start_stack;
a[0].end_stack=task_mm->arg_start;
pid_t count=0;
VMAList=task_mm->mmap;              /*    list of VMAs    */
while(count<256 && VMAList )
{
a[0].start_vma[count]=VMAList->vm_start;  /* 每个VMA的起始地址 */
a[0].end_vma[count]=VMAList->vm_end;
a[0].vmas_flags[count]=VMAList->vm_flags; 
VMAList=VMAList->vm_next;
count++;
}

a[0].vma_count=count;
if(copy_to_user((struct mycall_taskInfo *)buf,a,sizeof(struct mycall_taskInfo)))
return sizeof(struct mycall_taskInfo);
else 
return 0;
}

至此,添加系统调用函数完成,接下来,开始对内核重新编译。

第五步:编辑Makefile文件

修改linux-source-3.13.0/kernel/Makefile文件,把mycall.o加到obj-y中。

3.2编译内核

    编译内核需要安装编译环境必要库:

  •     sudo apt-get build-essential
  •     sudo apt-get install libncurses5-dev

安装好编译环境,为了减少编译内核耗费的时间,把/boot/原核的配置文件拷贝到linux-source-3.13.0下,并命名为.config。即输入命令:

  • cp /boot/config-3.13.0-32.generic/usr/src/linux-source-3.13.0/.config

编译内核指令:

  •     makemenuconfig
  •     makebzImage
  •     makemodules
  •     makemodules_install
  •     makeinstall

3.3测试代码

#include
#include

struct mycall_taskInfo
{
int pid;
unsigned long start_code;           /* 代码段起始地址  */
unsigned long end_code;
unsigned long start_data;	    /* 数据段起始地址  */
unsigned long end_data;
unsigned long start_brk;            /* brk地址    */
unsigned long brk; 
unsigned long start_stack;          /* 栈起始地址  */
unsigned long end_stack;
unsigned long pgd;                  /* 虚拟地址页表首地址  */
int vma_count;	                   /* mm_struct中,number of VMAs */
unsigned long  start_vma[256];       /*  每个VMA的起始地址 */
unsigned long  end_vma[256];
unsigned long vmas_flags[256];      /* VMA的属性,VM_area_struct */
};

int  main()
{
struct mycall_taskInfo a[1]; 
int i; 
syscall(314,getpid(),a);  
printf("进程pid: %d\n",a[0].pid);
printf("代码段地址: %lu---%lu\n",a[0].start_code,a[0].end_code);
printf("数据段段地址: %lu---%lu\n",a[0].start_data,a[0].end_data);
printf("栈区地址: %lu---%lu\n",a[0].start_stack,a[0].end_stack);
printf("堆区地址: %lu---%lu\n",a[0].start_brk,a[0].brk);
printf("虚拟地址页表首地址: %lu\n", a[0].pgd);
printf("进程的VMAs的个数为%d\n",a[0].vma_count);
for( i=0;i

结果截图:

linux系统调用_第2张图片


问题1:无法确定指定进程号是多少

进程号是程序运行时分配的,事先不知道进程的进程号是多少。而且进程号的取值范围为0-PID_MAX_DEFAULT,顺序使用和循环使用。随便指定进程号,无法保证其task_struct存在。查找发现,用户态有getpid()函数,直接获得该进程的pid。这样即可以确定该pid一定存在task_struct。同时也能确定该pid由哪个程序运行产生。

 

问题2:内核态与用户态数据的转换

    在系统调用中获得的数据信息需要传送到用户态进行输出显示,但内核态的数据和用户态的数据不可以直接进行传递。Linux提供copy_from_user()copy_to_user()

这两个函数,负责在用户空间和内核空间传递数据。因此我们在测试程序中,将空数组a的地址作为参数传递给内核模块程序,在内核中使用copy_to_user()函数将内核中的数组信息传递给用户态下的地址。copy_to_user()复制成功返回0,复制失败返回未复制成功的字节数。


你可能感兴趣的:(linux系统调用)