文章目录
实现一个专属系统调用
最近在研究Linux内核的内存管理,书上说进程所使用的虚拟地址信息全部保存在vm_area_struct结构体中,并未给出实例,这个结构体也是在内核空间的,所以用户空间是不能直接访问的,正好最近看到系统调用这一章节,于是想到像内核添加一个自己的系统调用,用来打印当前进程的task_struct进程控制块中我自己关注的任何信息。
实现专属系统调用
在fs目录下新建一个名为panglib.c的文件,我们要新添加的系统调用就在这个c文件里实现,除了新建panglib.c文件,我们还需要修改fs目录下的Makefile文件,如下图所示,将panglib.c添加到内核的编译规则。
实现系统调用
panglib.c的内容如下,系统调用名为sys_pang。
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/file.h>
#include <linux/fdtable.h>
#include <linux/fsnotify.h>
#include <linux/module.h>
#include <linux/tty.h>
#include <linux/namei.h>
#include <linux/backing-dev.h>
#include <linux/capability.h>
#include <linux/securebits.h>
#include <linux/security.h>
#include <linux/mount.h>
#include <linux/fcntl.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/personality.h>
#include <linux/pagemap.h>
#include <linux/syscalls.h>
#include <linux/rcupdate.h>
#include <linux/audit.h>
#include <linux/falloc.h>
#include <linux/fs_struct.h>
#include <linux/ima.h>
#include <linux/dnotify.h>
#include <linux/compat.h>
#include "internal.h"
static void print_vm_area(void)
{
struct mm_struct *mymm = current->mm;
struct task_struct *task = NULL;
struct vm_area_struct *pos = NULL;
printk("hello \n");
printk("current process:%s %d\n", current->comm, current->tgid);
#if 1
for(pos = mymm->mmap; pos; pos = pos->vm_next) {
printk("0x%lx-0x%lx\t", pos->vm_start, pos->vm_end);
if(pos->vm_flags & VM_READ) {
printk("r");
} else {
printk("-");
}
if(pos->vm_flags & VM_WRITE) {
printk("w");
} else {
printk("-");
}
if(pos->vm_flags & VM_EXEC) {
printk("x");
} else {
printk("-");
}
printk("\n");
}
#endif
return 0;
}
SYSCALL_DEFINE2(pang, const char __user *,buf,size_t, count)
{
print_vm_area();
return 0;
}
谈一谈SYSCALL_DEFINE2宏
展开后如下:
asmlinkage long sys_pang(__MAP(2,__SC_DECL,__VA_ARGS__)) \
__attribute__((alias(__stringify(SyS_pang)))); \
static inline long SYSC_pang(__MAP(2,__SC_DECL,__VA_ARGS__)); \
asmlinkage long SyS_pang(__MAP(2,__SC_LONG,__VA_ARGS__)); \
asmlinkage long SyS_pang(__MAP(2,__SC_LONG,__VA_ARGS__)) \
{ \
long ret = SYSC_pang(__MAP(2,__SC_CAST,__VA_ARGS__)); \
__MAP(2,__SC_TEST,__VA_ARGS__); \
__PROTECT(2, ret,__MAP(2,__SC_ARGS,__VA_ARGS__)); \
return ret; \
} \
static inline long SYSC_pang(__MAP(2,__SC_DECL,__VA_ARGS__))
{
print_vm_area();
return 0;
}
将专属系统调用添加到Linux内核
修改arch/arm/kernel/calls.S文件
如下图所示,在文件的末尾新增一行 CALL(sys_pang)。
这个CALL是一个宏定义,该宏定义的声明位于arch/arm/kernel/ entry-common.S文件,如下图所示。
CALL宏的第一次声明
宏定义一共声明了两次,其中我们的calls.S使用的是第一个声明,该声明并未使用CALL的传参,而是使用NR_syscalls变量进行赋值然后对NR_syscalls变量进行了加一操作,当calls.S展开完成后第一个CALL就被取消声明然后重新定义了,也就是说第一次声明的CALL宏只是为了统计系统调用的个数,但是注意:在calls.S的结尾的对齐处理。
CALL宏的第二次声明与sys_call_table数组
所有的系统调用全部保存在一个数组里,数组的每一个元素都是指向一个系统调用的函数指针,这个数组名为sys_call_table,对数组的赋值操作如下图所示:
这里又再一次展开了calls.S,但是这次用的宏为:#define CALL(x) .long x,比如我们刚刚在calls.S中新增的系统调用CALL(sys_pang)会被展开为 .long sys_pang,sys_pang是我们在panglib.c中实现的系统调用的函数地址。
到这里你应该也明白了,所谓的系统调用号说的糙一点就是sys_xxx函数在sys_call_table数组中的索引号。
修改sys_ni.c
如下图所示,在文件的末尾新增一行 cond_syscall(sys_pang)。
cond_syscall宏展开如下
#define cond_syscall(x) asm( \
".weak " VMLINUX_SYMBOL_STR(x) "\n\t" \
".set " VMLINUX_SYMBOL_STR(x) "," \
VMLINUX_SYMBOL_STR(sys_ni_syscall))
cond_syscall(sys_pang);语句的意思是:如果存在sys_pang(),则声明这个函数,在程序链接的时候使用这个函数;如果不存在sys_socketcall()这个函数,就使用sys_ni_syscall()函数代替。
你可能要问了,sys_ni_syscall()是何许人也,看下函数实现你就明白了…,相当于告诉用户层你调用的系统调用在内核里并未实现。
asmlinkage long sys_ni_syscall(void)
{
return -ENOSYS;
}
修改include/linux/syscalls.h
修改arch/arm/include/asm/unistd.h
C应用程序使用系统调用
实验平台为Ti的ARM32处理器。
通过syscall函数访问sys_pang
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
int main()
{
char buf[2];
syscall(388,buf,2);
return 0;
}
运行
汇编程序使用系统调用
相比C语言,汇编更能让我们深入理解系统调用的本质。
通过swi指令陷入内核态
.text
.global _start
_start:
add r0,pc,#0
mov r1,#12
mov r7,#388
swi #0
_exit:
mov r7,#1
swi #0
应用层通过swi指令触发软中断,swi的中断服务程序位于arch/arm/kernel/entry-common.S文件,源码如下:
/*=============================================================================
* SWI handler
*-----------------------------------------------------------------------------
*/
.align 5
ENTRY(vector_swi)
#ifdef CONFIG_CPU_V7M
v7m_exception_entry
#else
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0 - r12
ARM( add r8, sp, #S_PC )
ARM( stmdb r8, {sp, lr}^ ) @ Calling sp, lr
THUMB( mov r8, sp )
THUMB( store_user_sp_lr r8, r10, S_SP ) @ calling sp, lr
mrs r8, spsr @ called from non-FIQ mode, so ok.
str lr, [sp, #S_PC] @ Save calling PC
str r8, [sp, #S_PSR] @ Save CPSR
str r0, [sp, #S_OLD_R0] @ Save OLD_R0
#endif
zero_fp
alignment_trap r10, ip, __cr_alignment
enable_irq
ct_user_exit
get_thread_info tsk
/*
* Get the system call number.
*/
#if defined(CONFIG_OABI_COMPAT)
/*
* If we have CONFIG_OABI_COMPAT then we need to look at the swi
* value to determine if it is an EABI or an old ABI call.
*/
#ifdef CONFIG_ARM_THUMB
tst r8, #PSR_T_BIT
movne r10, #0 @ no thumb OABI emulation
USER( ldreq r10, [lr, #-4] ) @ get SWI instruction
#else
USER( ldr r10, [lr, #-4] ) @ get SWI instruction
#endif
ARM_BE8(rev r10, r10) @ little endian instruction
#elif defined(CONFIG_AEABI)
/*
* Pure EABI user space always put syscall number into scno (r7).
*/
#elif defined(CONFIG_ARM_THUMB)
/* Legacy ABI only, possibly thumb mode. */
tst r8, #PSR_T_BIT @ this is SPSR from save_user_regs
addne scno, r7, #__NR_SYSCALL_BASE @ put OS number in
USER( ldreq scno, [lr, #-4] )
#else
/* Legacy ABI only. */
USER( ldr scno, [lr, #-4] ) @ get SWI instruction
#endif
adr tbl, sys_call_table @ load syscall table pointer
#if defined(CONFIG_OABI_COMPAT)
/*
* If the swi argument is zero, this is an EABI call and we do nothing.
*
* If this is an old ABI call, get the syscall number into scno and
* get the old ABI syscall table address.
*/
bics r10, r10, #0xff000000
eorne scno, r10, #__NR_OABI_SYSCALL_BASE
ldrne tbl, =sys_oabi_call_table
#elif !defined(CONFIG_AEABI)
bic scno, scno, #0xff000000 @ mask off SWI op-code
eor scno, scno, #__NR_SYSCALL_BASE @ check OS number
#endif
local_restart:
ldr r10, [tsk, #TI_FLAGS] @ check for syscall tracing
stmdb sp!, {r4, r5} @ push fifth and sixth args
tst r10, #_TIF_SYSCALL_WORK @ are we tracing syscalls?
bne __sys_trace
cmp scno, #NR_syscalls @ check upper syscall limit
adr lr, BSYM(ret_fast_syscall) @ return address
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
add r1, sp, #S_OFF
2: cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
eor r0, scno, #__NR_SYSCALL_BASE @ put OS number back
bcs arm_syscall
mov why, #0 @ no longer a real syscall
b sys_ni_syscall @ not private func
#if defined(CONFIG_OABI_COMPAT) || !defined(CONFIG_AEABI)
/*
* We failed to handle a fault trying to access the page
* containing the swi instruction, but we're not really in a
* position to return -EFAULT. Instead, return back to the
* instruction and re-enter the user fault handling path trying
* to page it in. This will likely result in sending SEGV to the
* current task.
*/
9001:
sub lr, lr, #4
str lr, [sp, #S_PC]
b ret_fast_syscall
#endif
ENDPROC(vector_swi)
编译链接汇编文件
arm-linux-gnueabihf-as syscall.s -o syscall.o
arm-linux-gnueabihf-ld syscall.o -o syscall