国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Linux > 正文

Linux內(nèi)核分析(六)----字符設備控制方法實現(xiàn)|揭秘系統(tǒng)調(diào)用本質(zhì)

2024-06-28 13:22:06
字體:
供稿:網(wǎng)友
linux內(nèi)核分析(六)----字符設備控制方法實現(xiàn)|揭秘系統(tǒng)調(diào)用本質(zhì)

Linux內(nèi)核分析(六)

昨天我們對字符設備進行了初步的了解,并且實現(xiàn)了簡單的字符設備驅(qū)動,今天我們繼續(xù)對字符設備的某些方法進行完善。

今天我們會分析到以下內(nèi)容:

1. 字符設備控制方法實現(xiàn)

2. 揭秘系統(tǒng)調(diào)用本質(zhì)

在昨天我們實現(xiàn)的字符設備中有open、read、write等方法,由于這些方法我們在以前編寫應用程序的時候,相信大家已經(jīng)有所涉及所以就沒單獨列出來分析,今天我們主要來分析一下我們以前接觸較少的控制方法。

l 字符設備控制方法實現(xiàn)

1. 設備控制簡介

1. 何為設備控制:我們所接觸的大部分設備,除了讀、寫、打開關閉等方法外,還應該具有控制方法,比如:控制電機轉(zhuǎn)速、串口配置波特率等。這就是對設備的控制方法。

2. 用戶如何進行設備控制:類似與我們在用戶空間使用read、open等函數(shù)對設備進行操作,我們在用戶空間對設備控制的函數(shù)是ioctl其原型為int ioctl(int fd, int cmd, …)//fd為要控制的設備文件的描述符,cmd是控制命令,…依據(jù)第二個參數(shù)類似與我們的PRintf等多參函數(shù)。

3. Ioctl調(diào)用驅(qū)動那個函數(shù):在我們的用戶層進行ioctl調(diào)用的時候驅(qū)動會根據(jù)內(nèi)核版本不同調(diào)用不同的函數(shù),有以下:

1) 2.6.36以前的內(nèi)核版本會調(diào)用long (*ioctl) (struct inode*,struct file *, unsigned int, unsigned long);

2) 2.6.36以后的內(nèi)核會調(diào)用long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

2. Ioctl實現(xiàn)

1. 控制命令解析:我們剛才說到ioctl進行控制的時候有個cmd參數(shù)其為int類型的也就是32位,我們的linux為了讓這32位更加有意義,所表示的內(nèi)容更多,所以將其分為了下面幾個段

1) Type(類型/幻數(shù)8bit):表明這是屬于哪個設備的命令

2) Number(序號8bit):用來區(qū)分統(tǒng)一設備的不同命令

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

4) Size(13/14bit()):參數(shù)長度

2. 定義命令:我們的控制命令如此復雜,為了方便我們的linux系統(tǒng)提供了固定的宏來解決命令的定義,具體如下:

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

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

3) _IOW(type,nr,datatype);:向設備寫入?yún)?shù)命令

下面定義一個向設備寫入?yún)?shù)的命令例子

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

3. Ioctl實現(xiàn):下面我們?nèi)ハ蛭覀兩洗螌崿F(xiàn)的字符設備中添加ioctl方法,并實現(xiàn)設備重啟命令(虛擬重啟),對于不支持的命令我們返回-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 揭秘系統(tǒng)調(diào)用本質(zhì)

由于我自己的PC的調(diào)用過程不太熟悉,下面以arm的調(diào)用過程分析一下我們用戶層調(diào)用read之后發(fā)生了什么,是怎么調(diào)用到我們驅(qū)動寫的read函數(shù)的呢,我們下面進行深入剖析。

1. 代碼分析

我們首先使用得到arm上可執(zhí)行的應用程序arm-linux-gcc -g -static read_mem.c -o read_mem然后使用arm-linux-objdump -D -S read_mem >dump得到匯編文件,我們找到main函數(shù)的匯編實現(xiàn)

 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    ; 0x011     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    ; 0x216     824c:    eb0028a3     bl    124e0 <__libc_open>17     8250:    e1a03000     mov    r3, r018     8254:    e50b3008     str    r3, [fp, #-8]19     read(fd,&test,sizeof(int));20     8258:    e24b300c     sub    r3, fp, #12    ; 0xc21     825c:    e51b0008     ldr    r0, [fp, #-8]22     8260:    e1a01003     mov    r1, r323     8264:    e3a02004     mov    r2, #4    ; 0x424     8268:    eb0028e4     bl    12600 <__libc_read>//我們的read函數(shù)最終調(diào)用了__libc_read25 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, r330     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    ; 0x037 }

上面我們發(fā)現(xiàn)read最終調(diào)用了__libc_read函數(shù)我們繼續(xù)在匯編代碼中找到該函數(shù)

 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, ip10    12620:    e3700a01     cmn    r0, #4096    ; 0x100011    12624:    312fff1e     bxcc    lr12    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, r016    12638:    e8bd000f     pop    {r0, r1, r2, r3}17    1263c:    e3a07003     mov    r7, #3    ; 0x3//系統(tǒng)調(diào)用標號,一會解釋大家先記主18    12640:    ef000000     svc    0x0000000019    12644:    e1a07000     mov    r7, r020    12648:    e1a0000c     mov    r0, ip21    1264c:    eb000396     bl    134ac <__libc_disable_asynccancel>22    12650:    e1a00007     mov    r0, r723    12654:    e8bd4080     pop    {r7, lr}24    12658:    e3700a01     cmn    r0, #4096    ; 0x100025    1265c:    312fff1e     bxcc    lr26    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調(diào)用引起注意,通過查閱資料才發(fā)現(xiàn),我們應用程序通過svc 0x00000000可以產(chǎn)生異常,進入內(nèi)核空間。

然后呢,系統(tǒng)處理異常,這中間牽扯好多代碼還有中斷的一些知識,我們找時間在專門分析,總之經(jīng)過一大堆的處理最后它會跳到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 PC11     str    r8, [sp, #S_PSR]        @ Save CPSR12     str    r0, [sp, #S_OLD_R0]        @ Save OLD_R013     zero_fp14 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 swi23      * value to determine if it is an EABI or an old ABI call.24      */25 #ifdef CONFIG_ARM_THUMB26     tst    r8, #PSR_T_BIT27     movne    r10, #0                @ no thumb OABI emulation28     ldreq    r10, [lr, #-4]            @ get SWI instruction29 #else30     ldr    r10, [lr, #-4]            @ get SWI instruction31   A710(    and    ip, r10, #0x0f000000        @ check for SWI        )32   A710(    teq    ip, #0x0f000000                        )33   A710(    bne    .Larm710bug                        )34 #endif35 #ifdef CONFIG_CPU_ENDIAN_BE836     rev    r10, r10            @ little endian instruction37 #endif38 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_regs53     addne    scno, r7, #__NR_SYSCALL_BASE    @ put OS number in54     ldreq    scno, [lr, #-4]55 56 #else57 58     /* Legacy ABI only. */59     ldr    scno, [lr, #-4]            @ get SWI instruction60   A710(    and    ip, scno, #0x0f000000        @ check for SWI        )61   A710(    teq    ip, #0x0f000000                        )62   A710(    bne    .Larm710bug                        )63 64 #endif65 66 #ifdef CONFIG_ALIGNMENT_TRAP67     ldr    ip, __cr_alignment68     ldr    ip, [ip]69     mcr    p15, 0, ip, c1, c0        @ update control register70 #endif71     enable_irq72 73     get_thread_info tsk74     adr    tbl, sys_call_table        @ load syscall table pointer
View Code

該段代碼中我們先會獲取系統(tǒng)調(diào)用的標號剛才讓大家記住的3,然后呢會去查找sys_call_table我們找到

1     .type    sys_call_table, #object2 ENTRY(sys_call_table)3 #include "calls.S"4 #undef ABI5 #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)

我們發(fā)現(xiàn)我們剛才記住的數(shù)字3剛好對應的是sys_read,在read_write.c中我們可以找到sys_read函數(shù)

 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);//調(diào)用虛擬文件系統(tǒng)的read11         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 這篇文章查看,在此我就不分析了,我們繼續(xù)找到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)//我們的文件讀函數(shù)指針不為空16             ret = file->f_op->read(file, buf, count, pos);//執(zhí)行我們驅(qū)動中的讀函數(shù)17         else18             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. 過程總結

通過上面的分析我們已經(jīng)了解的read函數(shù)的調(diào)用基本過程,下面我們將read函數(shù)的調(diào)用過程在進行總結:

1. 尋找svc異常總體入口,并進入內(nèi)核空間

2. 取出系統(tǒng)調(diào)用的標號

3. 根據(jù)系統(tǒng)調(diào)用標號,在sys_call_table中找到對應的系統(tǒng)調(diào)用函數(shù)

4. 根據(jù)系統(tǒng)函數(shù)比如sys_read找到對應的虛擬文件系統(tǒng)的read

5. 虛擬文件系統(tǒng)在調(diào)用驅(qū)動的read。

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

今天的分析到此結束,感謝大家的關注。


發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 怀化市| 桃源县| 昌邑市| 青龙| 台中市| 灵寿县| 顺昌县| 静宁县| 丰都县| 剑阁县| 永登县| 大兴区| 镇远县| 奉节县| 株洲市| 潍坊市| 永和县| 宝坻区| 巴马| 五华县| 加查县| 潜江市| 星子县| 奈曼旗| 竹北市| 庆元县| 泗阳县| 新营市| 隆安县| 久治县| 阿城市| 泗水县| 佛学| 五台县| 昌邑市| 北宁市| 郧西县| 宁阳县| 西乌珠穆沁旗| 思南县| 封丘县|