前言html
轉載請註明出處http://www.cnblogs.com/dvd0423/p/4183443.htmljava
內核讓人最爽的地方就是它給你站在山上看風景的感受,一切一覽無餘。就像0號博文說的,無論它有沒有用,知其因此然老是好的。 linux
這個系列的內容圍繞Linux內核展開,涉及的主要是我作KVM的過程當中遇到的部分,網絡、調度、KVM等等。雖然是底層的東西可是搞應用的人看一看也沒有壞處。咱們都知道內核太龐大,要想全瞭解幾乎不可能,因此咱們只能根據本身的須要去針對性的學習。而如此龐大的項目卻能被組織的有條不紊,井井有條,讓一個初學者面對一個龐然大物不至於無從下手,只能感慨人外有人,天外有天啊。其實內核發展到如今,已經有不少深度各異的書籍和文檔資料,也有愈來愈多的人加入到社區中去,你遇到的問題別人都遇到過,因此如今內核已經不是那麼難學了。不得不說咱們能快快速入手內核,主要仍是由於咱們站在了巨人的頭上,算了仍是站到肩膀上吧。c++
個人這個系列文章介紹內核的同時會介紹《unix高級環境編程》的知識,並結合着我所瞭解的高層應用去認識內核,這樣無論對底層仍是對高層都是一種認識的加深。這一篇文章主要介紹系統調用、模塊編程和鉤子函數。在這裏爲何我不從編譯安裝開始呢?由於我在學校兼職搞集羣運維,我如今最討厭的就是搭建集羣環境了(這一部分請自行百度)。shell
1 系統調用編程
內核是一間毛胚房,有了系統調用後變成了精裝修,而應用程序就是讓房子有立體感的傢俱。全部的應用程序都必需要通過系統調用。雖然有點誇張但聽到這句話就知道爲何我先講系統調用了,它是用戶訪問內核的入口。高層應用可以建立進程,網絡通訊,內存操做,讀取文件和各類shell命令等都是它的直接功勞。而咱們看到的c++/java/pathon等等五花八門的語言庫,都是封裝了系統調用而已,本質都是同樣的。若是咱們想和內核打交道又不想深刻內核源碼,那瞭解下系統調用大有裨益。好比在java裏面api
String cmds="java -version"; Process p = Runtime.getRuntime().exec(cmds);
這兩個語句會建立進程執行cmds命令,而在Posix C中用fork()/exec()建立新進程。但他們深刻到linux內核中都是調用do_fork()。瞭解虛擬機的朋友都知道有個Hypercall的接口函數,實現原理和Syscall相似,這裏再也不延伸,未來會講到的。(這裏感謝實驗室的一個外號叫「大海」的同窗)網絡
對於咱們的用戶程序使用strace命令能夠追蹤系統調用。命令格式爲:數據結構
# strace –o log.txt ./hello
下面咱們以open系統調用爲例說明其原理。open在內核中函數原型以下:運維
1 SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) 2 { 3 if (force_o_largefile()) 4 flags |= O_LARGEFILE; 5
6 return do_sys_open(AT_FDCWD, filename, flags, mode); 7 }
而在用戶空間系統調用函數原型爲:
long open(const char *filename, int flags, int mode);
固然也能夠用另外一種方式調用即syscall()函數,詳細使用方法用man命令查找。上面函數等價於:
1 /*
2 #define __NR_restart_syscall 0 3 #define __NR_exit 1 4 #define __NR_fork 2 5 #define __NR_read 3 6 #define __NR_write 4 7 #define __NR_open 5 8 ... 9 */
10 syscall(num,const char *, filename, int, flags, int, mode); //num是調用號,後面是參數,這裏是5
下面就讓咱們實現本身的系統調用以此加深對系統調用這個工具的認識。首先要實現本身的系統調用首先要在系統調用表/arch/sh/include/uapi/asm/unistd_64.h中添加調用號,並將總調用數加1。
#define __NR_firstsyscall 380 //添加的部分
#define NR_syscalls 381 //原本爲380
其次要在系統調用表syscall_table.s中添加相應的表項。
ENTRY(sys_call_table) .long sys_restart_syscall /* 0 - old "setup()" system call, * used for restarting */ ... .long sys_kcmp .long sys_finit_module /*添加本身的*/ .long sys_firstsyscall /*380*/
第三實現系統調用的具體程序
SYSCALL_DEFINE3(firstsyscall, int, value){ printk("fuckDW"); return value; }
最後在用戶空間實現系統調用:
#include <linux/unistd.h> #include <syscall.h> #include <sys/types.h> #include <stdio.h>
int main(int argc, char** argv) { printf("%ld\n",syscall(380, 423)); return 0; }
而後最麻煩的事情就是從新編譯內核了。
2 模塊編程
因爲精力有限,我沒去了解模塊化在linux中的實現原理,我用它主要來提取內核源碼中的一些數據結構。這個在後面講到網絡和調度的時候會體現它的做用。要在本身模塊中使用內核的參數,首先用EXPORT_SYMBOL(init_net)宏聲明讓init_net變量能夠調用。
下面函數實現了顯示全部網絡設備的功能,固然咱們還能夠隨便的提取並改變內核中的任意數據結構:
1 /*
2 *init_net是全局變量,模塊內能夠調用。這裏用hello world函數就能夠代替get_devs()函數,看不懂沒關係,這不是重點。 3 *printf對應內河中的printk 4 */
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/kernel.h>
8 #include <linux/netdevice.h>
9 #include <net/net_namespace.h>
10 #include <linux/netdevice.h>
11 #include <linux/list.h>
12
13 MODULE_LICENSE("GPL"); //許可聲明,要加進來
14
15
16 static int get_devs(void) 17 { 18 struct net_device *a_dev = dev_get_by_name(&init_net, "eth0"); //獲得
19 struct list_head *p; 20 struct net_device *temp_dev; 21 int i = 0; 22
23 list_for_each(p, &(a_dev->dev_list)){ 24 temp_dev = list_entry(p, struct net_device, dev_list); 25
26 printk("%d\t%s\n", (++i), temp_dev->name); 27 } 28 dev_put(a_dev); 29 return 0; 30 } 31 //加載模塊運行的函數
32 static int __init mode4_init(void) 33 { 34 printk("Module 4 Init!\n"); 35 get_devs(); 36 return 0; 37 } 38
39 static void __exit mode4_exit(void) 40 { 41 printk("Module 4 Exit!\n"); 42
43 } 44
45 module_init(mode4_init);//註冊模塊
46 module_exit(mode4_exit);
寫完代碼就編寫Makefile文件以下:
1 obj-m += mode4.o 2 PWD:=$(shell pwd) 3 KDIR:=/usr/src/kernels/$(shell uname -r)/
4
5 all: 6 $(MAKE) -C $(KDIR) M=$(PWD) modules 7
8 clean: 9 $(MAKE) -C $(KDIR) M=$(PWD) clean
在命令行裏輸入以下命令:
# make # insmod mode4.ko //加載模塊
# dmesg //顯示以下信息,保存在目錄/var/log/dmesg中
# rmmod mode4.ko //卸載模塊,顯示Module 4 Exit!
# make clean //清除編譯文件
咱們還能夠給模塊傳遞命令行參數,用module_param()或module_param_array()宏實現。具體使用本身看源碼吧。
用模塊編程還能夠給內核加入一些你想要的功能。模塊編程也就是自定義操做系統的開始。
3 鉤子函數
這個很差實現,原理大概是用戶攔截內核消息,修改後返回內核,內核根據用戶的設置選擇不一樣的運行方式。之後在網絡模塊會提到。不少命令就是用鉤子函數實現對操做系統的修改。
今天就寫到這裏。說實話寫到這裏又感受不想寫了,主要緣由是當我懂了一件事的時候,我再寫每一句話都以爲是多餘的,都是很顯然很簡單的廢話。可是我想仍是有一些同窗不懂的,但願我寫的能幫到別人。畢竟你苦苦思考了好久,結果別人一句話就幫你解決了問題,那種心情經歷過的人都懂。
by 糖球