內(nèi)核態(tài)與用戶態(tài)
早期工程師們?cè)?a href="http://m.1cnz.cn/v/tag/527/" target="_blank">操作系統(tǒng)上編寫程序的時(shí)候,自己寫個(gè)程序可以訪問(wèn)別人的程序地址,甚至是操作系統(tǒng)占用的地址,這樣就很容易一不小心就直接把操作系統(tǒng)給干掛了,所以那個(gè)時(shí)候的程序員編寫程序都得小心翼翼的
計(jì)算機(jī)核心的資源,一般有:內(nèi)存,I/O端口,特殊機(jī)器指令等,這些資源必須得保護(hù)起來(lái),規(guī)定哪些程序可以去訪問(wèn),哪些程序不能去訪問(wèn)
所以引入了特權(quán)級(jí)別的概念,由硬件設(shè)備商直接來(lái)提供硬件級(jí)別的支持,最常見(jiàn)的就是給CPU指令集的權(quán)限分級(jí)來(lái)控制CPU的訪問(wèn)權(quán)限
比如 Intel CPU指令集操作的權(quán)限由高到低劃為4級(jí):Ring0、Ring1、Ring2、Ring3,其中Ring0權(quán)限最高,可以使用所有CPU指令集,Ring3權(quán)限最低,僅能使用部分CPU指令,比如不能使用操作硬件資源的CPU指令:I/O操作、內(nèi)存分配等操作;另外CPU處于Ring3狀態(tài)不能訪問(wèn)Ring0的地址空間,包括代碼和數(shù)據(jù)
CPU指令集,就是CPU中用來(lái)計(jì)算和控制計(jì)算機(jī)系統(tǒng)的一套指令的集合,實(shí)現(xiàn)軟件指揮硬件執(zhí)行的媒介,常見(jiàn)的CPU指令集有X86、ARM、MIPS、Alpha、RISC等
那么CPU是如何記錄這些特權(quán)級(jí)信息的?
我們這里以80386CPU為例,前文提到過(guò)CPU里面有許多段寄存器(CS、DS、SS、ES、FS、GS等)。這些段寄存器里面存放段選擇符(也叫段選擇子)
段選擇符中包含請(qǐng)求特權(quán)級(jí)RPL(CPL)字段,通過(guò)段選擇符可以去查找全局描述符表GDT、局部描述符表LDT中對(duì)應(yīng)的項(xiàng),需要先進(jìn)行特權(quán)級(jí)檢查;這些項(xiàng)中都包含DPL字段(規(guī)定訪問(wèn)該段的權(quán)限級(jí)別),只有DPL >= max {CPL, RPL},才允許訪問(wèn)
CPL很特殊,跟蹤當(dāng)前CPU正在執(zhí)行的代碼所在段的描述符中DPL的值,總是等于CPU的當(dāng)前特權(quán)級(jí)
內(nèi)核態(tài)與用戶態(tài)都是操作系統(tǒng)的層面的概念,和CPU硬件沒(méi)有必然的聯(lián)系;由于硬件已經(jīng)提供了一套特權(quán)級(jí)使用的相關(guān)機(jī)制,Linux操作系統(tǒng)沒(méi)有必要重新"造輪子",直接使用了硬件的Ring0和Ring3這兩個(gè)級(jí)別的權(quán)限,也就是使用Ring3作為用戶態(tài),Ring0作為內(nèi)核態(tài)
那么有人會(huì)問(wèn)為什么Linux系統(tǒng)僅使用了Ring0和Ring3這兩個(gè)級(jí)別?
因?yàn)镃PU給的權(quán)限管理細(xì)度不夠,比如Intel CPU中Ring2和Ring3在操作系統(tǒng)里安全情況沒(méi)有區(qū)別,Ring1下的系統(tǒng)權(quán)限又需要經(jīng)常調(diào)用Ring0特權(quán)指令,頻繁切換特權(quán)級(jí)成本過(guò)高,操作系統(tǒng)不如將Ring2合并到Ring3,將Ring1劃入Ring0特權(quán)級(jí)
另一方面不是每種處理器都像x86一樣支持4個(gè)權(quán)限級(jí)別,有些處理器可能只支持2個(gè)級(jí)別,更少的特權(quán)級(jí)別,便于移植其他處理器架構(gòu)上
我們?cè)賮?lái)看下linux的體系架構(gòu)圖:
我們可以發(fā)現(xiàn)Linux系統(tǒng)從整體上看,被劃分為用戶態(tài)和內(nèi)核態(tài)
內(nèi)核態(tài)
內(nèi)核態(tài)是處于操作系統(tǒng)的最核心處,Ring0特權(quán)級(jí),擁有操作系統(tǒng)的最高權(quán)限,能夠控制所有的硬件資源,掌控各種核心數(shù)據(jù),并且能夠訪問(wèn)內(nèi)存中的任意地址;由內(nèi)核態(tài)統(tǒng)一管理這些核心資源,減少有限資源的訪問(wèn)和使用沖突;在內(nèi)核里發(fā)生的任何程序異常都是災(zāi)難性的,會(huì)導(dǎo)致整個(gè)操作系統(tǒng)的奔潰
用戶態(tài)
用戶態(tài),就是我們通常編寫程序的地方,處于Ring3特權(quán)級(jí),權(quán)限較低;這一層次的程序沒(méi)有對(duì)硬件的直接控制權(quán)限,也不能直接訪問(wèn)地址的內(nèi)存。在這種模式下,即使程序發(fā)生崩潰也不會(huì)影響其他程序,可恢復(fù)
什么是系統(tǒng)調(diào)用
當(dāng)計(jì)算機(jī)啟動(dòng)的時(shí)候,CPU處于Ring0狀態(tài),這個(gè)時(shí)候所有的指令都可以執(zhí)行,通過(guò)主引導(dǎo)程序?qū)⒋疟P扇區(qū)中的操作系統(tǒng)程序加載到內(nèi)存中,從而啟動(dòng)操作系統(tǒng)(需要注意一下,本文的操作系統(tǒng) 以Linux0.12為例子)
也就是說(shuō)當(dāng)Linux0.12啟動(dòng)的時(shí)候,是在權(quán)限最高級(jí)別的內(nèi)核態(tài)運(yùn)行的;同時(shí)對(duì)內(nèi)存進(jìn)行劃分,劃出一部分(內(nèi)核區(qū))專門給內(nèi)核使用,這部分內(nèi)存只能被內(nèi)核使用;主內(nèi)存區(qū)域給其他應(yīng)用軟件使用。對(duì)這部分感興趣地,可以看看筆者之前的文章Linux0.12內(nèi)核源碼解讀(6)-main.c
當(dāng)操作系統(tǒng)啟動(dòng)完成后,CPU就切換到Ring3級(jí)別上,操作系統(tǒng)同時(shí)進(jìn)入用戶態(tài),之后的應(yīng)用程序代碼都運(yùn)行在權(quán)限最低級(jí)別的用戶態(tài)上,通常我們能編寫的程序都運(yùn)行在用戶態(tài)上
需要格外注意一下,CPU特權(quán)級(jí)其實(shí)并不會(huì)對(duì)操作系統(tǒng)的用戶造成什么影響!有人會(huì)和Linux的用戶權(quán)限搞混淆,無(wú)論是根用戶(root),管理員,訪客還是一般用戶,它們都屬于用戶;而所有的用戶代碼都在用戶態(tài)Ring3上執(zhí)行,所有的內(nèi)核代碼都在內(nèi)核態(tài)Ring0上執(zhí)行,和Linux用戶的身份權(quán)限并沒(méi)有關(guān)系!
因?yàn)槲覀兙帉懙某绦蚨歼\(yùn)行在用戶態(tài)上,是無(wú)法對(duì)內(nèi)存和I/O端口的訪問(wèn),可以說(shuō)基本上無(wú)法與外部世界交互,但是我們平時(shí)工作的時(shí)候訪問(wèn)磁盤、寫文件,這些都是必要的需求,怎么辦?
那就需要通過(guò)執(zhí)行系統(tǒng)調(diào)用system call,操作系統(tǒng)會(huì)切換到內(nèi)核態(tài),由內(nèi)核去統(tǒng)一執(zhí)行相關(guān)操作(大哥幫小弟去執(zhí)行);當(dāng)執(zhí)行完操作系統(tǒng)再切換回用戶態(tài)。這樣方便集中管理,減少有限資源的訪問(wèn)和使用沖突
系統(tǒng)調(diào)用是操作系統(tǒng)專門為用戶態(tài)運(yùn)行的進(jìn)程與硬件設(shè)備之間進(jìn)行交互提供了一組接口,是用戶態(tài)主動(dòng)要求切換到內(nèi)核態(tài)的一種方式
系統(tǒng)調(diào)用是怎么實(shí)現(xiàn)的
接下來(lái)我們就結(jié)合Linux0.12的源碼一起來(lái)看看系統(tǒng)調(diào)用是怎么實(shí)現(xiàn)的?
庫(kù)函數(shù)write
本文以一個(gè)常見(jiàn)的庫(kù)函數(shù)write函數(shù)為例來(lái),來(lái)更方便大家理解,開(kāi)始發(fā)車:
?
?
//??lib/write.c #define?__LIBRARY__ #include??//頭文件 _syscall3(int,write,int,fd,const?char?*,buf,off_t,count)?//定義write的實(shí)現(xiàn),:fd -?文件描述符;buf -?寫緩沖區(qū)指針;count -?寫字節(jié)數(shù)
?
?
write.c這個(gè)文件主要是定義write的實(shí)現(xiàn),_syscall3(*,write,*)函數(shù)的主要功能是,向文件描述符fd指定的文件寫入count個(gè)字節(jié)的數(shù)據(jù)到緩沖區(qū)buf中
需要注意一下#define __LIBRARY__這個(gè)宏定義,這里定義直接原因是為了包括在unistd.h中的內(nèi)嵌匯編代碼
庫(kù)函數(shù)擴(kuò)展匯編宏
因?yàn)開(kāi)syscall3這個(gè)函數(shù)定義在/include/unistd.h中,來(lái)看下源碼:
?
?
//??/include/unistd.h #ifdef?__LIBRARY__?#?若提前定義__LIBRARY__,則以后內(nèi)容被包含 ... #define?__NR_write?4?//系統(tǒng)調(diào)用號(hào),用作系統(tǒng)調(diào)用函數(shù)表中索引值 ... //定義有3個(gè)參數(shù)的,?定義系統(tǒng)調(diào)用嵌入式匯編宏函數(shù) //%0?- eax(__res),%1 - eax(__NR_name),%2 - ebx(a),%3 - ecx(b),%4 - edx(c)。 #define?_syscall3(type,name,atype,a,btype,b,ctype,c)? type?name(atype?a,btype?b,ctype?c)? {? long?__res;? __asm__?volatile?("int?$0x80"??????????????????????????????????????????????//?調(diào)用系統(tǒng)中斷?0x80 ?:?"=a"?(__res)???????????????????????????????????????????????????????????//?返回值eax(__res) ?:?"0"?(__NR_##name),"b"?((long)(a)),"c"?((long)(b)),"d"?((long)(c)));????//輸入為:系統(tǒng)中斷調(diào)用號(hào)__NR_name,還有另外3個(gè)參數(shù) if?(__res>=0)??????????????????????????????????????????????????????????????//?如果返回值>=0,則直接返回該值 ?return?(type)?__res;? errno=-__res;??????????????????????????????????????????????????????????????//?否則置出錯(cuò)號(hào),并返回-1 return?-1;????????????????????????????????????????????????????????????????? } #endif?/*?__LIBRARY__?*/ ... int?write(int?fildes,?const?char?*?buf,?off_t?count);?//write系統(tǒng)調(diào)用的函數(shù)原型定義 ...
?
?
只有在lib/write.c中先定義了#define __LIBRARY__,那么才能在/include/unistd.h中,找到系統(tǒng)調(diào)用號(hào)和內(nèi)嵌匯編_syscall3();不然就代表它不需要進(jìn)行系統(tǒng)調(diào)用,這樣就可以忽略u(píng)nistd.h中和系統(tǒng)調(diào)用相關(guān)的宏定義,非常的優(yōu)雅
其實(shí)我們可以把write.c中的write函數(shù)再重新整合一下:
?
?
int?write(int?fd,const?char*?buf,off_t?count)? {? long?__res;? __asm__?volatile?(?"int?$0x80"? :?"=a"?(__res)? :?""?(__NR_write),?"b"?((long)(fd)),?"c"?((long)(buf)),?"d"?((long)(count)));? if?(__res>=0)? return?(type)?__res;? errno=-__res;? return?-1;? }
?
?
這樣大家就能更容易明白#define __LIBRARY__的作用
上面int $0x80"表示調(diào)用系統(tǒng)中斷0x80 ** ,其實(shí)系統(tǒng)調(diào)用的本質(zhì)還是通過(guò)中斷(0x80)去實(shí)現(xiàn)的**!操作系統(tǒng)中真的是處處離不開(kāi)中斷。中斷相關(guān)知識(shí)不了解的,可以看看筆者之前寫過(guò)的一篇文章圖解計(jì)算機(jī)中斷
另外由于程序處于用戶態(tài)無(wú)法直接操作硬件資源,所以需要進(jìn)行系統(tǒng)調(diào)用,切換到內(nèi)核態(tài);也就是說(shuō)用戶程序如果使用庫(kù)函數(shù)write,會(huì)進(jìn)行系統(tǒng)調(diào)用
而系統(tǒng)調(diào)用,其實(shí)就是去調(diào)用int 0x80中斷,然后把三個(gè)參數(shù)fd、buf、count依次存入ebx、ecx、edx寄存器
還有#define __NR_write4 ,定義了系統(tǒng)調(diào)用號(hào);_NR_write會(huì)被存入eax寄存器;當(dāng)調(diào)用返回后,從eax取出返回值,存入__res,建立了用戶棧和內(nèi)核棧的聯(lián)系。至于__NR_write的作用下文再講解
int 0x80中斷 調(diào)用對(duì)應(yīng)的中斷處理函數(shù)
我們來(lái)看下中斷是調(diào)用對(duì)應(yīng)的中斷處理函數(shù)的流程圖:
當(dāng)發(fā)生中斷的時(shí)候,CPU獲取到中斷向量號(hào)后,通過(guò)IDTR,去查找IDT中斷描述符表,得到相應(yīng)的中斷描述符;然后根據(jù)描述符中的對(duì)應(yīng)中斷處理程序的入口地址,去執(zhí)行中斷處理程序
早在linux0.12啟動(dòng)時(shí),會(huì)進(jìn)行調(diào)度程序初始化main.c/sched_init(),其源碼:
?
?
//?????/kernel/sched.c ... void?sched_init(void) { ?... ?set_system_gate(0x80,&system_call);//設(shè)置系統(tǒng)調(diào)用中斷門 } ...
?
?
set_system_gate在之前的文章Linux0.12內(nèi)核源碼解讀(7)-陷阱門初始化講解過(guò),不再贅述
需要注意的是:在用戶態(tài)和內(nèi)核態(tài)運(yùn)行的進(jìn)程使用的棧是不同的,分別叫做用戶棧和內(nèi)核棧, 兩者各自負(fù)責(zé)相應(yīng)特權(quán)級(jí)別狀態(tài)下的函數(shù)調(diào)用;所以當(dāng)執(zhí)行系統(tǒng)調(diào)用中斷int 0x80從用戶態(tài)進(jìn)入內(nèi)核態(tài)時(shí),會(huì)從用戶棧切換到內(nèi)核棧,系統(tǒng)調(diào)用返回時(shí),還要切換回用戶棧,繼續(xù)完成用戶態(tài)下的函數(shù)調(diào)用(這也叫做被中斷進(jìn)程上下文的保存與恢復(fù))
其中其關(guān)鍵作用的是,CPU會(huì)可以自動(dòng)通過(guò)TR寄存器找到當(dāng)前進(jìn)程的TSS,然后根據(jù)里面ss0和esp0的值找到內(nèi)核棧的位置,完成用戶棧到內(nèi)核棧的切換。先了解一下,這塊等進(jìn)程那塊我們會(huì)再詳細(xì)聊聊
set_system_gate(0x80,&system_call)這句整體作用是,設(shè)置系統(tǒng)調(diào)用中斷門,將0x80中斷和函數(shù)system_call綁定在一起,換句話說(shuō)system_call就是0x80的中斷處理函數(shù)
檢索系統(tǒng)調(diào)用函數(shù)表
我們接著去看system_call函數(shù)的源碼:
?
?
//????/kernel/sys_call.s ... //?int?0x80 _system_call: ?push?%ds??????#?壓棧,?保存原段寄存器值 ?push?%es ?push?%fs??? ?pushl?%eax??#?保存eax原值 ?pushl?%edx?? ?pushl?%ecx??#?push?%ebx,%ecx,%edx?as?parameters ?pushl?%ebx??#?to?the?system?call,??ebx,ecx,edx?中放著系統(tǒng)調(diào)用對(duì)應(yīng)的C語(yǔ)言函數(shù)的參數(shù) ?movl?$0x10,%edx??#?ds,es?指向內(nèi)核數(shù)據(jù)段 ?mov?%dx,%ds ?mov?%dx,%es ?movl?$0x17,%edx??#?fs?指向當(dāng)前局部數(shù)據(jù)段(局部描述符表中數(shù)據(jù)段描述符) ?mov?%dx,%fs ?cmpl?_NR_syscalls,%eax??#?判斷eax是否超過(guò)了最大的系統(tǒng)調(diào)用號(hào),調(diào)用號(hào)如果超出范圍的話就跳轉(zhuǎn)! ?jae?bad_sys_call ?call?_sys_call_table(,%eax,4)???#?間接調(diào)用指定功能C函數(shù)! ?pushl?%eax??????????????????????#??把系統(tǒng)調(diào)用的返回值入棧! ... ret_from_sys_call:??#當(dāng)系統(tǒng)調(diào)用執(zhí)行完畢之后,會(huì)執(zhí)行此處的匯編代碼,從而返回用戶態(tài) ?movl?_current,%eax??#?取當(dāng)前任務(wù)(進(jìn)程)數(shù)據(jù)結(jié)構(gòu)指針->eax ?cmpl?_task,%eax???#?task[0]?cannot?have?signals ?...
?
?
其中 _sys_call_table(,%eax,4),這里的eax寄存器存放的就是_NR_write系統(tǒng)調(diào)用號(hào),_sys_call_table是sys.h中的一個(gè)int (*)()類型的數(shù)組,里面存的是所有的系統(tǒng)調(diào)用函數(shù)地址,也叫做系統(tǒng)調(diào)用函數(shù)表,所以__NR_write也表示系統(tǒng)調(diào)用函數(shù)表中的索引值
那為什么%eax * 4乘上4呢?這是因?yàn)閟ys_call_table[]指針每項(xiàng)4 個(gè)字節(jié),這樣被調(diào)用處理函數(shù)的地址=[_sys_call_table + %eax * 4]
我們?cè)賮?lái)看下sys_call_table的定義:
?
?
//????/include/linux/sys.h ... extern?int?sys_write(); ... fn_ptr?sys_call_table[]?=?{?sys_setup,?sys_exit,?sys_fork,?sys_read, sys_write,?sys_open,?sys_close,?sys_waitpid,?sys_creat,?sys_link, sys_unlink,?sys_execve,?sys_chdir,?sys_time,?sys_mknod,?sys_chmod, sys_chown,?sys_break,?sys_stat,?sys_lseek,?sys_getpid,?sys_mount, sys_umount,?sys_setuid,?sys_getuid,?sys_stime,?sys_ptrace,?sys_alarm, sys_fstat,?sys_pause,?sys_utime,?sys_stty,?sys_gtty,?sys_access, sys_nice,?sys_ftime,?sys_sync,?sys_kill,?sys_rename,?sys_mkdir, sys_rmdir,?sys_dup,?sys_pipe,?sys_times,?sys_prof,?sys_brk,?sys_setgid, sys_getgid,?sys_signal,?sys_geteuid,?sys_getegid,?sys_acct,?sys_phys, sys_lock,?sys_ioctl,?sys_fcntl,?sys_mpx,?sys_setpgid,?sys_ulimit, sys_uname,?sys_umask,?sys_chroot,?sys_ustat,?sys_dup2,?sys_getppid, sys_getpgrp,?sys_setsid,?sys_sigaction,?sys_sgetmask,?sys_ssetmask, sys_setreuid,sys_setregid,?sys_sigsuspend,?sys_sigpending,?sys_sethostname, sys_setrlimit,?sys_getrlimit,?sys_getrusage,?sys_gettimeofday,? sys_settimeofday,?sys_getgroups,?sys_setgroups,?sys_select,?sys_symlink, sys_lstat,?sys_readlink,?sys_uselib?}; //系統(tǒng)調(diào)用總數(shù)目,注意一下:這里相較于linux0.11做了改進(jìn),新增系統(tǒng)調(diào)用不再需要手動(dòng)調(diào)整該數(shù)目! int?NR_syscalls?=?sizeof(sys_call_table)/sizeof(fn_ptr);
?
?
可以知曉這里的call _sys_call_table(,%eax,4)就是調(diào)用系統(tǒng)調(diào)用號(hào)所對(duì)應(yīng)的內(nèi)核系統(tǒng)調(diào)用函數(shù)sys_write
最終執(zhí)行sys_write
sys_write在fs下的read_write.c:
?
?
//???/fs/read_write.c //?寫文件系統(tǒng)調(diào)用 int?sys_write(unsigned?int?fd,char?*?buf,int?count) { ?struct?file?*?file; ?struct?m_inode?*?inode; ??//判斷函數(shù)參數(shù)的有效性 ?if?(fd>=NR_OPEN?||?count?<0?||?!(file=current->filp[fd])) ??return?-EINVAL; ?if?(!count) ??return?0; ??//?取文件相應(yīng)的i節(jié)點(diǎn) ?inode=file->f_inode; ??//?若是管道文件,并且是寫管道文件模式,則進(jìn)行寫管道操作 ?if?(inode->i_pipe) ??return?(file->f_mode&2)?write_pipe(inode,buf,count):-EIO; ??//如果是字符設(shè)備文件,則進(jìn)行寫字符設(shè)備操作 ?if?(S_ISCHR(inode->i_mode)) ??return?rw_char(WRITE,inode->i_zone[0],buf,count,&file->f_pos); ??//?如果是塊設(shè)備文件,則進(jìn)行塊設(shè)備寫操作 ?if?(S_ISBLK(inode->i_mode)) ??return?block_write(inode->i_zone[0],&file->f_pos,buf,count); ??//?若是常規(guī)文件,則執(zhí)行文件寫操作 ?if?(S_ISREG(inode->i_mode)) ??return?file_write(inode,file,buf,count); ?printk("(Write)inode->i_mode=%06o ",inode->i_mode); ?return?-EINVAL; }
?
?
至此庫(kù)函數(shù)write,進(jìn)行系統(tǒng)調(diào)用,最終調(diào)用了sys_write這個(gè)函數(shù)
我們?cè)偻ㄟ^(guò)下圖回顧一下,整個(gè)系統(tǒng)調(diào)用的過(guò)程:
內(nèi)核態(tài)與用戶態(tài)數(shù)據(jù)交互
到這里我們已經(jīng)了解了系統(tǒng)調(diào)用的過(guò)程,還遺留一個(gè)問(wèn)題需要去解決一下,就是內(nèi)核態(tài)與用戶態(tài)如何進(jìn)行數(shù)據(jù)交互?
回顧系統(tǒng)調(diào)用過(guò)程中,我們可以發(fā)現(xiàn)寄存器在其中起到了不可或缺的作用,linus在linux0.12中也是采用類似的方法來(lái)進(jìn)行數(shù)據(jù)交互
我們這里繼續(xù)以sys_write函數(shù)為例,來(lái)看看里面的file_write(inode,file,buf,count);
?
?
//???/fs/file_dev.c //?寫文件函數(shù)?-?根據(jù)?i?節(jié)點(diǎn)和文件結(jié)構(gòu)信息,將用戶數(shù)據(jù)寫入文件中 int?file_write(struct?m_inode?*?inode,?struct?file?*?filp,?char?*?buf,?int?count) { ?off_t?pos; ?int?block,c; ?struct?buffer_head?*?bh; ?char?*?p; ?int?i=0; /* ?*?ok,?append?may?not?work?when?many?processes?are?writing?at?the?same?time ?*?but?so?what.?That?way?leads?to?madness?anyway. ?*/ ?//如果設(shè)置了追加標(biāo)記位,則更新當(dāng)前位置指針到文件最后一個(gè)字節(jié) ?if?(filp->f_flags?&?O_APPEND) ??pos?=?inode->i_size; ?else ??pos?=?filp->f_pos; ??//?i為已經(jīng)寫入的長(zhǎng)度,count為需要寫入的長(zhǎng)度 ?while?(ii_dev,block))) ???break; ??c?=?pos?%?BLOCK_SIZE; ??p?=?c?+?bh->b_data;//?開(kāi)始寫入數(shù)據(jù)的位置 ??bh->b_dirt?=?1;?//標(biāo)記數(shù)據(jù)需要回寫硬盤 ??c?=?BLOCK_SIZE-c;?//算出能寫的長(zhǎng)度 ??if?(c?>?count-i)?c?=?count-i; ??pos?+=?c; ??if?(pos?>?inode->i_size)?{ ???inode->i_size?=?pos; ???inode->i_dirt?=?1; ??} ??i?+=?c; ??while?(c-->0) ???*(p++)?=?get_fs_byte(buf++);//從用戶態(tài)拷貝一個(gè)字節(jié)的數(shù)據(jù)到內(nèi)核態(tài) ??brelse(bh); ?} ??//當(dāng)數(shù)據(jù)已經(jīng)全部寫入文件或者在寫操作過(guò)程中發(fā)生問(wèn)題時(shí)就會(huì)退出循環(huán) ?inode->i_mtime?=?CURRENT_TIME; ?if?(!(filp->f_flags?&?O_APPEND))?{ ??filp->f_pos?=?pos; ??inode->i_ctime?=?CURRENT_TIME; ?} ?return?(i?i:-1); }
?
?
我們這里不展開(kāi)講了,得后面講完磁盤和文件系統(tǒng)再回過(guò)頭來(lái)講講這塊,把目光聚焦于get_fs_byte函數(shù),我們來(lái)看下其源碼:
?
?
//??include/asm/segment.h ? ?//?讀取 fs 段中指定地址處的字節(jié)。 ?//?參數(shù):addr -?指定的內(nèi)存地址。 ?//?%0?-?(返回的字節(jié)_v);%1 -?(內(nèi)存地址 addr)。 ?//?返回:返回內(nèi)存 fs:[addr]處的字節(jié)。 ?//?第 3 行定義了一個(gè)寄存器變量_v,該變量將被保存在一個(gè)寄存器中,以便于高效訪問(wèn)和操作。 extern?inline?unsigned?char?get_fs_byte(const?char?*?addr) { ?unsigned?register?char?_v; ?__asm__?("movb?%%fs:%1,%0":"=r"?(_v):"m"?(*addr)); ?return?_v; } ?//?將一字節(jié)存放在 fs 段中指定內(nèi)存地址處。 ?//?參數(shù):val -?字節(jié)值;addr -?內(nèi)存地址。 ?//?%0?-?寄存器(字節(jié)值 val);%1 -?(內(nèi)存地址 addr)。 extern?inline?void?put_fs_byte(char?val,char?*addr) { __asm__?("movb?%0,%%fs:%1"::"r"?(val),"m"?(*addr)); }
?
?
get_fs_byte函數(shù)是從用戶態(tài)拷貝一個(gè)字節(jié)的數(shù)據(jù)到內(nèi)核態(tài),而put_fs_byte則恰恰相反,從內(nèi)核態(tài)拷貝一個(gè)字節(jié)的數(shù)據(jù)到用戶態(tài)
在系統(tǒng)調(diào)用運(yùn)行整個(gè)過(guò)程中,DS和ES段寄存器指向內(nèi)核數(shù)據(jù)空間,而FS段寄存器被設(shè)置為指向用戶數(shù)據(jù)空間,這可能有人會(huì)問(wèn)為啥?
別忘了在/kernel/sys_call.s中_system_call中的這段:
?
?
_system_call: ... ?movl?$0x10,%edx??#?ds,es?指向內(nèi)核數(shù)據(jù)段 ?mov?%dx,%ds ?mov?%dx,%es ?movl?$0x17,%edx??#?fs?指向當(dāng)前局部數(shù)據(jù)段(局部描述符表中數(shù)據(jù)段描述符) ?mov?%dx,%fs ...
?
?
0x10是全局描述符表GDT中內(nèi)核數(shù)據(jù)段描述符的段值,0x17是局部描述符表LDT中的任務(wù)的數(shù)據(jù)段描述符的段值
所以linux這里利用FS寄存器來(lái)完成內(nèi)核數(shù)據(jù)空間與用戶數(shù)據(jù)空間之間的數(shù)據(jù)復(fù)制,當(dāng)進(jìn)程從中斷調(diào)用中退出時(shí),寄存器會(huì)自動(dòng)從內(nèi)核棧彈出,快捷高效
審核編輯:黃飛
?
評(píng)論
查看更多