1. 程式人生 > >Linux裝置驅動--系統呼叫

Linux裝置驅動--系統呼叫

1 開發環境

    Host:Ubuntu14.04(64bit)

    Target: smdk2410

    Kernel: linux-2.6.39.4

2 系統呼叫表

    所有系統呼叫都定義在系統呼叫表中,當系統呼叫中斷髮生時,系統就根據系統呼叫號在該表中查詢需要執行的系統呼叫函式。這裡先說明系統呼叫表是如何定義的:

(1)sys_call_table

    .type   sys_call_table, #object
ENTRY(sys_call_table)
#include "calls.S"
#undef ABI
#undef OBSOLETE
/* arch/arm/kernel/entry-common.S */

(2)calls.S

    特定平臺的calls.S中定義了該平臺的系統呼叫表,arm平臺的系統呼叫表如下所示(部分):

/* 0 */     CALL(sys_restart_syscall)
        CALL(sys_exit)
        CALL(sys_fork_wrapper)
        CALL(sys_read)
        CALL(sys_write)
/* 5 */     CALL(sys_open)
        CALL(sys_close)
        CALL(sys_ni_syscall)        /* was sys_waitpid */
        CALL(sys_creat)
        CALL(sys_link)
/* 10 */    CALL(sys_unlink)
        CALL(sys_execve_wrapper)
        CALL(sys_chdir)
        CALL(OBSOLETE(sys_time))    /* used by libc4 */
/* 原始檔:arch/arm/kernel/calls.S */

(3)CALL()

    上述巨集CALL()定義如下:    

#define CALL(x) .long x
/* 原始檔:arch/arm/kernel/entry-common.S */

    因此,CALL(sys_exit)可展開為:

.long sys_exit
    其它的類似。

3 系統呼叫入口

    系統呼叫是從什麼地方開始的呢?答案是sys_syscall標號處,每當出發系統呼叫中斷時,就自動跳轉到該標號處繼續執行:

/*============================================================================                                                                                                                               
 * Special system call wrappers
 */
@ r0 = syscall number
@ r8 = syscall table
sys_syscall:
        bic scno, r0, #__NR_OABI_SYSCALL_BASE
        cmp scno, #__NR_syscall - __NR_SYSCALL_BASE
        cmpne   scno, #NR_syscalls  @ check range
        stmloia sp, {r5, r6}        @ shuffle args
        movlo   r0, r1
        movlo   r1, r2
        movlo   r2, r3
        movlo   r3, r4
        ldrlo   pc, [tbl, scno, lsl #2] 
        b   sys_ni_syscall
ENDPROC(sys_syscall)
/* 原始檔:arch/arm/kernel/entry-common.S */

    由上原始碼可見,系統呼叫入口為sys_syscall標號,r0暫存器儲存系統呼叫號,r8暫存器儲存系統呼叫表,程式根據r0r8確定系統呼叫函式(例如sys_read()),然後跳轉到該系統呼叫函式繼續執行。

4 系統呼叫號

    上述系統呼叫表是使用匯編定義的,為了便於使用,使用C語言定義了系統呼叫號。

    特定平臺的unstd.h中定義了該平臺的系統呼叫號,arm平臺的系統呼叫號如下所示(部分):

/*
 * This file contains the system call numbers.
 */
#define __NR_restart_syscall        (__NR_SYSCALL_BASE+  0)
#define __NR_exit           (__NR_SYSCALL_BASE+  1)
#define __NR_fork           (__NR_SYSCALL_BASE+  2)
#define __NR_read           (__NR_SYSCALL_BASE+  3)
#define __NR_write          (__NR_SYSCALL_BASE+  4)
#define __NR_open           (__NR_SYSCALL_BASE+  5)
#define __NR_close          (__NR_SYSCALL_BASE+  6)
                    /* 7 was sys_waitpid */
#define __NR_creat          (__NR_SYSCALL_BASE+  8)
#define __NR_link           (__NR_SYSCALL_BASE+  9)
#define __NR_unlink         (__NR_SYSCALL_BASE+ 10)
#define __NR_execve         (__NR_SYSCALL_BASE+ 11)
#define __NR_chdir          (__NR_SYSCALL_BASE+ 12)
/* 標頭檔案:arch/arm/include/asm/unistd.h */
:不同平臺的系統呼叫號不一定相同,但是必須和系統呼叫表對應。

5 系統呼叫函式宣告

    在syscalls.h標頭檔案中,聲明瞭所有系統呼叫(平臺無關):

asmlinkage long sys_time(time_t __user *tloc);
asmlinkage long sys_stime(time_t __user *tptr);
asmlinkage long sys_gettimeofday(struct timeval __user *tv,
                struct timezone __user *tz);
asmlinkage long sys_settimeofday(struct timeval __user *tv,
                struct timezone __user *tz);
asmlinkage long sys_adjtimex(struct timex __user *txc_p);
asmlinkage long sys_times(struct tms __user *tbuf);
/* 標頭檔案:include/linux/syscalls.h */

6 系統呼叫函式實現

    上述標頭檔案只是聲明瞭系統呼叫,這裡以sys_read()為例重點分析系統呼叫函式的實現。

    試圖通過“grep -rnw sys_read”來搜尋sys_read()系統呼叫的實現時,卻找不到

    通過分析原始碼發現,系統呼叫都通過類似於SYSCALL_DEFINE3()的巨集(數字3表示系統呼叫引數個數為3)來定義,例如:

(1)sys_read()

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
    struct file *file;
    ssize_t ret = -EBADF;
    int fput_needed;
    
    file = fget_light(fd, &fput_needed);
    if (file) {
        loff_t pos = file_pos_read(file);
        ret = vfs_read(file, buf, count, &pos);
        file_pos_write(file, pos);
        fput_light(file, fput_needed);
    }

    return ret;
}
/* 原始檔:fs/read_write.c */

    展開上述巨集SYSCALL_DEFINE3()得:

asmlinkage long sys_read(unsigned int, fd, char __user *, buf, size_t, count)
{
    struct file *file;
    ssize_t ret = -EBADF;
    int fput_needed;
    
    file = fget_light(fd, &fput_needed);
    if (file) {
        loff_t pos = file_pos_read(file);
        ret = vfs_read(file, buf, count, &pos); /* 呼叫vfs_read()! */
        file_pos_write(file, pos);
        fput_light(file, fput_needed);
    }

    return ret;
}
/* 原始檔:fs/read_write.c */

    其它巨集如下所示:

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
/* 標頭檔案:include/linux/syscalls.h */

(2)vfs_read()

    通過分析上述sys_read()函式發現,它呼叫的一個關鍵函式是vfs()_read():

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
    ssize_t ret;

    if (!(file->f_mode & FMODE_READ))
        return -EBADF;
    if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
        return -EINVAL;
    if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
        return -EFAULT;

    ret = rw_verify_area(READ, file, pos, count);
    if (ret >= 0) {
        count = ret;
        if (file->f_op->read)
            ret = file->f_op->read(file, buf, count, pos); /* 呼叫file->f_op->read()!*/
        else
            ret = do_sync_read(file, buf, count, pos);
        if (ret > 0) {
            fsnotify_access(file);
            add_rchar(current, ret);
        }                                                                                                                                                                                                    
        inc_syscr(current);
    }   

    return ret;
}

EXPORT_SYMBOL(vfs_read);
/* 原始檔:fs/read_write.c */
    分析上述vfs_read()可知,它最終呼叫了f_op->read()。在編寫裝置驅動程式時,很重要的一步就是實現f_op->read(),這裡就是呼叫該f_op->read()的地方!

參考資料