Linux內核分析(六)----字符設備控制方法實現|揭祕系統調用本質

Linux內核分析(六)node

昨天咱們對字符設備進行了初步的瞭解,而且實現了簡單的字符設備驅動,今天咱們繼續對字符設備的某些方法進行完善。linux

今天咱們會分析到如下內容:git

1.      字符設備控制方法實現github

2.      揭祕系統調用本質app

 

在昨天咱們實現的字符設備中有openreadwrite等方法,因爲這些方法咱們在之前編寫應用程序的時候,相信你們已經有所涉及因此就沒單獨列出來分析,今天咱們主要來分析一下咱們之前接觸較少的控制方法。async

 

l  字符設備控制方法實現ide

1.       設備控制簡介函數

1.        何爲設備控制:咱們所接觸的大部分設備,除了讀、寫、打開關閉等方法外,還應該具備控制方法,好比:控制電機轉速、串口配置波特率等。這就是對設備的控制方法。this

2.        用戶如何進行設備控制:相似與咱們在用戶空間使用readopen等函數對設備進行操做,咱們在用戶空間對設備控制的函數是ioctl其原型爲 int ioctl(int fd, int cmd, …)//fd爲要控制的設備文件的描述符,cmd是控制命令,…依據第二個參數相似與咱們的printf等多參函數。 spa

3.        Ioctl調用驅動那個函數:在咱們的用戶層進行ioctl調用的時候驅動會根據內核版本不一樣調用不一樣的函數,有如下:

1)        2.6.36之前的內核版本會調用 long (*ioctl) (struct inode*,struct file *, unsigned int, unsigned long); 

2)        2.6.36之後的內核會調用 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 

2.       Ioctl實現

1.        控制命令解析:咱們剛纔說到ioctl進行控制的時候有個cmd參數其爲int類型的也就是32位,咱們的linux爲了讓這32位更加有意義,所表示的內容更多,因此將其分爲了下面幾個段

1)        Type(類型/幻數8bit):代表這是屬於哪一個設備的命令

2)        Number(序號8bit):用來區分統一設備的不一樣命令

3)        Direction2bit):參數傳遞方向,可能的取值,_IOC_NODE(沒有數據傳輸)、_IOC_READ(從設備讀)、_IOC_WRITE(向設備寫)

4)        Size13/14bit()):參數長度

2.        定義命令:咱們的控制命令如此複雜,爲了方便咱們的linux系統提供了固定的宏來解決命令的定義,具體以下:

1)         _IO(type,nr); :定義不帶參數的命令

2)         _IOR(type,nr,datatype); :從設備讀參數命令

3)         _IOW(type,nr,datatype); :向設備寫入參數命令

下面定義一個向設備寫入參數的命令例子

 #define MEM_CLEAR _IOW(‘m’,0,int)//一般用一個字母來表示命令的類型 

3.        Ioctl實現:下面咱們去向咱們上次實現的字符設備中添加ioctl方法,並實現設備重啓命令(虛擬重啓),對於不支持的命令咱們返回-EINVAL代碼以下,總體工程在https://github.com/wrjvszq/myblongs(我從此會將本身博文中提到的代碼都放在這個倉庫中)

 

 1 long mem_ioctl(struct file *fd, unsigned int cmd, unsigned long arg){
 2     switch(cmd){
 3     case MEM_RESTART:
 4         printk("<0> memdev is restart");
 5         break;
 6     default:
 7         return -EINVAL;
 8     }
 9     return 0;
10 } 

l  揭祕系統調用本質

因爲我本身的PC的調用過程不太熟悉,下面以arm的調用過程分析一下咱們用戶層調用read以後發生了什麼,是怎麼調用到咱們驅動寫的read函數的呢,咱們下面進行深刻剖析。

1.       代碼分析

咱們首先使用獲得arm上可執行的應用程序 arm-linux-gcc -g -static read_mem.c -o read_mem 而後使用 arm-linux-objdump -D -S read_mem >dump 獲得彙編文件,咱們找到main函數的彙編實現

 1  int main(void)
 2 {
 3     8228:    e92d4800     push    {fp, lr}
 4     822c:    e28db004     add    fp, sp, #4    ; 0x4
 5     8230:    e24dd008     sub    sp, sp, #8    ; 0x8
 6     int fd = 0;
 7     8234:    e3a03000     mov    r3, #0    ; 0x0
 8     8238:    e50b3008     str    r3, [fp, #-8]
 9     int test = 0;
10     823c:    e3a03000     mov    r3, #0    ; 0x0
11     8240:    e50b300c     str    r3, [fp, #-12]
12 
13     fd = open("/dev/memdev0",O_RDWR);
14     8244:    e59f004c     ldr    r0, [pc, #76]    ; 8298 <main+0x70>
15     8248:    e3a01002     mov    r1, #2    ; 0x2
16     824c:    eb0028a3     bl    124e0 <__libc_open>
17     8250:    e1a03000     mov    r3, r0
18     8254:    e50b3008     str    r3, [fp, #-8]
19     read(fd,&test,sizeof(int));
20     8258:    e24b300c     sub    r3, fp, #12    ; 0xc
21     825c:    e51b0008     ldr    r0, [fp, #-8]
22     8260:    e1a01003     mov    r1, r3
23     8264:    e3a02004     mov    r2, #4    ; 0x4
24     8268:    eb0028e4     bl    12600 <__libc_read>//咱們的read函數最終調用了__libc_read
25 
26     printf("the test is %d\n",test);
27     826c:    e51b300c     ldr    r3, [fp, #-12]
28     8270:    e59f0024     ldr    r0, [pc, #36]    ; 829c <main+0x74>
29     8274:    e1a01003     mov    r1, r3
30     8278:    eb000364     bl    9010 <_IO_printf>
31 
32     close(fd);
33     827c:    e51b0008     ldr    r0, [fp, #-8]
34     8280:    eb0028ba     bl    12570 <__libc_close>
35     return 0;
36     8284:    e3a03000     mov    r3, #0    ; 0x0
37 }

 

上面咱們發現read最終調用了__libc_read函數咱們繼續在彙編代碼中找到該函數

 

 1 00012600 <__libc_read>:
 2    12600:    e51fc028     ldr    ip, [pc, #-40]    ; 125e0 <__libc_close+0x70>
 3    12604:    e79fc00c     ldr    ip, [pc, ip]
 4    12608:    e33c0000     teq    ip, #0    ; 0x0
 5    1260c:    1a000006     bne    1262c <__libc_read+0x2c>
 6    12610:    e1a0c007     mov    ip, r7
 7    12614:    e3a07003     mov    r7, #3    ; 0x3
 8    12618:    ef000000     svc    0x00000000
 9    1261c:    e1a0700c     mov    r7, ip
10    12620:    e3700a01     cmn    r0, #4096    ; 0x1000
11    12624:    312fff1e     bxcc    lr
12    12628:    ea0008b4     b    14900 <__syscall_error>
13    1262c:    e92d408f     push    {r0, r1, r2, r3, r7, lr}
14    12630:    eb0003b9     bl    1351c <__libc_enable_asynccancel>
15    12634:    e1a0c000     mov    ip, r0
16    12638:    e8bd000f     pop    {r0, r1, r2, r3}
17    1263c:    e3a07003     mov    r7, #3    ; 0x3//系統調用標號,一會解釋你們先記主
18    12640:    ef000000     svc    0x00000000
19    12644:    e1a07000     mov    r7, r0
20    12648:    e1a0000c     mov    r0, ip
21    1264c:    eb000396     bl    134ac <__libc_disable_asynccancel>
22    12650:    e1a00007     mov    r0, r7
23    12654:    e8bd4080     pop    {r7, lr}
24    12658:    e3700a01     cmn    r0, #4096    ; 0x1000
25    1265c:    312fff1e     bxcc    lr
26    12660:    ea0008a6     b    14900 <__syscall_error>
27    12664:    e1a00000     nop            (mov r0,r0)
28    12668:    e1a00000     nop            (mov r0,r0)
29    1266c:    e1a00000     nop            (mov r0,r0)

在上面代碼中大部分彙編指令都知道用法,可是svc調用引發注意,經過查閱資料才發現,咱們應用程序經過svc 0x00000000能夠產生異常,進入內核空間。

而後呢,系統處理異常,這中間牽扯好多代碼還有中斷的一些知識,咱們找時間在專門分析,總之通過一大堆的處理最後它會跳到entry-common.S中的下面代碼   

 1     .align    5
 2 ENTRY(vector_swi)
 3     sub    sp, sp, #S_FRAME_SIZE
 4     stmia    sp, {r0 - r12}            @ Calling r0 - r12
 5  ARM(    add    r8, sp, #S_PC        )
 6  ARM(    stmdb    r8, {sp, lr}^        )    @ Calling sp, lr
 7  THUMB(    mov    r8, sp            )
 8  THUMB(    store_user_sp_lr r8, r10, S_SP    )    @ calling sp, lr
 9     mrs    r8, spsr            @ called from non-FIQ mode, so ok.
10     str    lr, [sp, #S_PC]            @ Save calling PC
11     str    r8, [sp, #S_PSR]        @ Save CPSR
12     str    r0, [sp, #S_OLD_R0]        @ Save OLD_R0
13     zero_fp
14 
15     /*
16      * Get the system call number.
17      */
18 
19 #if defined(CONFIG_OABI_COMPAT)
20 
21     /*
22      * If we have CONFIG_OABI_COMPAT then we need to look at the swi
23      * value to determine if it is an EABI or an old ABI call.
24      */
25 #ifdef CONFIG_ARM_THUMB
26     tst    r8, #PSR_T_BIT
27     movne    r10, #0                @ no thumb OABI emulation
28     ldreq    r10, [lr, #-4]            @ get SWI instruction
29 #else
30     ldr    r10, [lr, #-4]            @ get SWI instruction
31   A710(    and    ip, r10, #0x0f000000        @ check for SWI        )
32   A710(    teq    ip, #0x0f000000                        )
33   A710(    bne    .Larm710bug                        )
34 #endif
35 #ifdef CONFIG_CPU_ENDIAN_BE8
36     rev    r10, r10            @ little endian instruction
37 #endif
38 
39 #elif defined(CONFIG_AEABI)
40 
41     /*
42      * Pure EABI user space always put syscall number into scno (r7).
43      */
44   A710(    ldr    ip, [lr, #-4]            @ get SWI instruction    )
45   A710(    and    ip, ip, #0x0f000000        @ check for SWI        )
46   A710(    teq    ip, #0x0f000000                        )
47   A710(    bne    .Larm710bug                        )
48 
49 #elif defined(CONFIG_ARM_THUMB)
50 
51     /* Legacy ABI only, possibly thumb mode. */
52     tst    r8, #PSR_T_BIT            @ this is SPSR from save_user_regs
53     addne    scno, r7, #__NR_SYSCALL_BASE    @ put OS number in
54     ldreq    scno, [lr, #-4]
55 
56 #else
57 
58     /* Legacy ABI only. */
59     ldr    scno, [lr, #-4]            @ get SWI instruction
60   A710(    and    ip, scno, #0x0f000000        @ check for SWI        )
61   A710(    teq    ip, #0x0f000000                        )
62   A710(    bne    .Larm710bug                        )
63 
64 #endif
65 
66 #ifdef CONFIG_ALIGNMENT_TRAP
67     ldr    ip, __cr_alignment
68     ldr    ip, [ip]
69     mcr    p15, 0, ip, c1, c0        @ update control register
70 #endif
71     enable_irq
72 
73     get_thread_info tsk
74     adr    tbl, sys_call_table        @ load syscall table pointer
View Code

 

該段代碼中咱們先會獲取系統調用的標號剛纔讓你們記住的3,而後呢會去查找sys_call_table咱們找到

 

1     .type    sys_call_table, #object
2 ENTRY(sys_call_table)
3 #include "calls.S"
4 #undef ABI
5 #undef OBSOLETE

 

calls.S中咱們找到了下面東西(列出部分)

 

 1 */
 2 /* 0 */        CALL(sys_restart_syscall)
 3         CALL(sys_exit)
 4         CALL(sys_fork_wrapper)
 5         CALL(sys_read)
 6         CALL(sys_write)
 7 /* 5 */        CALL(sys_open)
 8         CALL(sys_close)
 9         CALL(sys_ni_syscall)        /* was sys_waitpid */
10         CALL(sys_creat)
11         CALL(sys_link)

 

咱們發現咱們剛纔記住的數字3恰好對應的是sys_read,在read_write.c中咱們能夠找到sys_read函數

 

 1 SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
 2 {
 3     struct file *file;
 4     ssize_t ret = -EBADF;
 5     int fput_needed;
 6 
 7     file = fget_light(fd, &fput_needed);
 8     if (file) {
 9         loff_t pos = file_pos_read(file);
10         ret = vfs_read(file, buf, count, &pos);//調用虛擬文件系統的read
11         file_pos_write(file, pos);
12         fput_light(file, fput_needed);
13     }
14 
15     return ret;
16 }

關於SYSCALL_DEFINE3這個宏的解析你們能夠去http://blog.csdn.net/p_panyuch/article/details/5648007 這篇文章查看,在此我就不分析了,咱們繼續找到vfs_read代碼以下

 

 1 ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
 2 {
 3     ssize_t ret;
 4 
 5     if (!(file->f_mode & FMODE_READ))
 6         return -EBADF;
 7     if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
 8         return -EINVAL;
 9     if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
10         return -EFAULT;
11 
12     ret = rw_verify_area(READ, file, pos, count);
13     if (ret >= 0) {
14         count = ret;
15         if (file->f_op->read)//咱們的文件讀函數指針不爲空
16             ret = file->f_op->read(file, buf, count, pos);//執行咱們驅動中的讀函數
17         else
18             ret = do_sync_read(file, buf, count, pos);
19         if (ret > 0) {
20             fsnotify_access(file);
21             add_rchar(current, ret);
22         }
23         inc_syscr(current);
24     }
25 
26     return ret;
27 }

 

2.       過程總結

經過上面的分析咱們已經瞭解的read函數的調用基本過程,下面咱們將read函數的調用過程在進行總結:

1.        尋找svc異常整體入口,並進入內核空間

2.        取出系統調用的標號

3.        根據系統調用標號,在sys_call_table中找到對應的系統調用函數

4.        根據系統函數好比sys_read找到對應的虛擬文件系統的read

5.        虛擬文件系統在調用驅動的read

 

至此咱們的分析到此結束,固然整個過程當中還有一部分異常處理沒有說到,咱們在分析中斷的時候一塊分析。

 

今天的分析到此結束,感謝你們的關注。

相關文章
相關標籤/搜索