文章目錄
- 實作一個專屬系統調用
-
- 實作專屬系統調用
- 實作系統調用
-
- 談一談SYSCALL_DEFINE2宏
- 将專屬系統調用添加到Linux核心
-
- 修改arch/arm/kernel/calls.S檔案
-
- CALL宏的第一次聲明
- CALL宏的第二次聲明與sys_call_table數組
- 修改sys_ni.c
- 修改include/linux/syscalls.h
- 修改arch/arm/include/asm/unistd.h
- C應用程式使用系統調用
-
- 通過syscall函數通路sys_pang
- 運作
- 彙程式設計式使用系統調用
-
- 通過swi指令陷入核心态
- 編譯連結彙編檔案
- 運作
實作一個專屬系統調用
最近在研究Linux核心的記憶體管理,書上說程序所使用的虛拟位址資訊全部儲存在vm_area_struct結構體中,并未給出執行個體,這個結構體也是在核心空間的,是以使用者空間是不能直接通路的,正好最近看到系統調用這一章節,于是想到像核心添加一個自己的系統調用,用來列印目前程序的task_struct程序控制塊中我自己關注的任何資訊。
實作專屬系統調用
在fs目錄下建立一個名為panglib.c的檔案,我們要新添加的系統調用就在這個c檔案裡實作,除了建立panglib.c檔案,我們還需要修改fs目錄下的Makefile檔案,如下圖所示,将panglib.c添加到核心的編譯規則。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiNx8FesU2cfdGLwczX0xiRGZkRGZ0Xy9GbvNGL5EzXlpXazxyRtIWN3ZzS2UUc1Ujc2EWNGdFc18UawVTQClGVF5UMR9Fd4VGdsATNfd3bkFGazxycykFaKdkYzZUbapXNXlleSdVY2pESa9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL1QWOjZjMwUDN4UTZxEWMhRWYxQTYlZmYhVjZwMmZmBzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
實作系統調用
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