昨天咱們對字符設備進行了初步的瞭解,而且實現了簡單的字符設備驅動,今天咱們繼續對字符設備的某些方法進行完善。linux
今天咱們會分析到如下內容:git
1. 字符設備控制方法實現github
2. 揭祕系統調用本質app
在昨天咱們實現的字符設備中有open、read、write等方法,因爲這些方法咱們在之前編寫應用程序的時候,相信你們已經有所涉及因此就沒單獨列出來分析,今天咱們主要來分析一下咱們之前接觸較少的控制方法。async
1. 設備控制簡介函數
1. 何爲設備控制:咱們所接觸的大部分設備,除了讀、寫、打開關閉等方法外,還應該具備控制方法,好比:控制電機轉速、串口配置波特率等。這就是對設備的控制方法。this
2. 用戶如何進行設備控制:相似與咱們在用戶空間使用read、open等函數對設備進行操做,咱們在用戶空間對設備控制的函數是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) Direction(2bit):參數傳遞方向,可能的取值,_IOC_NODE(沒有數據傳輸)、_IOC_READ(從設備讀)、_IOC_WRITE(向設備寫)
4) Size(13/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
該段代碼中咱們先會獲取系統調用的標號剛纔讓你們記住的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。
至此咱們的分析到此結束,固然整個過程當中還有一部分異常處理沒有說到,咱們在分析中斷的時候一塊分析。
今天的分析到此結束,感謝你們的關注。