前言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 糖球