系统调用与模块编程

前言

  转载请注明出处http://www.cnblogs.com/dvd0423/p/4183443.html

  内核让人最爽的地方就是它给你站在山上看风景的感觉,一切尽收眼底。就像0号博文说的,不管它有没有用,知其所以然总是好的。  

  这个系列的内容围绕Linux内核展开,涉及的主要是我做KVM的过程中遇到的部分,网络、调度、KVM等等。虽然是底层的东西但是搞应用的人看一看也没有坏处。我们都知道内核太庞大,要想全了解几乎不可能,所以我们只能根据自己的需要去针对性的学习。而如此庞大的项目却能被组织的有条不紊,层次分明,让一个初学者面对一个庞然大物不至于无从下手,只能感慨人外有人,天外有天啊。其实内核发展到现在,已经有很多深度各异的书籍和文档资料,也有越来越多的人加入到社区中去,你遇到的问题别人都遇到过,所以现在内核已经不是那么难学了。不得不说我们能快快速入手内核,主要还是因为我们站在了巨人的头上,算了还是站到肩膀上吧。

  我的这个系列文章介绍内核的同时会介绍《unix高级环境编程》的知识,并结合着我所了解的高层应用去认识内核,这样不管对底层还是对高层都是一种认识的加深。这一篇文章主要介绍系统调用、模块编程和钩子函数。在这里为什么我不从编译安装开始呢?因为我在学校兼职搞集群运维,我现在最讨厌的就是搭建集群环境了(这一部分请自行百度)。

系统调用

       内核是一间毛胚房,有了系统调用后变成了精装修,而应用程序就是让房子有立体感的家具。所有的应用程序都必须要经过系统调用。虽然有点夸张但听到这句话就知道为什么我先讲系统调用了,它是用户访问内核的入口。高层应用能够创建进程,网络通信,内存操作,读取文件和各种shell命令等都是它的直接功劳。而我们看到的c++/java/pathon等等五花八门的语言库,都是封装了系统调用而已,本质都是一样的。如果我们想和内核打交道又不想深入内核源码,那了解下系统调用大有裨益。比如在java里面

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

syscall

  下面就让我们实现自己的系统调用以此加深对系统调用这个工具的认识。首先要实现自己的系统调用首先要在系统调用表/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;
}

  然后最麻烦的事情就是重新编译内核了。

模块编程

  由于精力有限,我没去了解模块化在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);

mode4.c

  写完代码就编写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

Makefile

  在命令行里输入如下命令:

# make
# insmod mode4.ko             //加载模块
# dmesg                        //显示如下信息,保存在目录/var/log/dmesg中

  

  

# rmmod mode4.ko                 //卸载模块,显示Module 4 Exit!
# make clean                     //清除编译文件

  我们还可以给模块传递命令行参数,用module_param()或module_param_array()宏实现。具体使用自己看源码吧。

  用模块编程还可以给内核加入一些你想要的功能。模块编程也就是自定义操作系统的开始。

3 钩子函数

这个不好实现,原理大概是用户拦截内核消息,修改后返回内核,内核根据用户的设置选择不同的运行方式。以后在网络模块会提到。很多命令就是用钩子函数实现对操作系统的修改。

  今天就写到这里。说实话写到这里又感觉不想写了,主要原因是当我懂了一件事的时候,我再写每一句话都觉得是多余的,都是很显然很简单的废话。但是我想还是有一些同学不懂的,希望我写的能帮到别人。毕竟你苦苦思考了很久,结果别人一句话就帮你解决了问题,那种心情经历过的人都懂。

by 糖球

时间: 2024-10-03 22:41:35

系统调用与模块编程的相关文章

Linux模块编程框架

Linux模块编程框架 Linux是单内核系统,可通用计算平台的外围设备是频繁变化的,不可能将所有的(包括将来即将出现的)设备的驱动程序都一次性编译进内核,为了解决这个问题,Linux提出了可加载内核模块(Loadable Kernel Module,LKM)的概念,允许一个设备驱动通过模块加载的方式,在内核运行起来之后"融入"内核,加载进内核的模块和本身就编译进内核的模块一模一样.一个程序在编译的地址的相对关系就已经确定了,运行的时候只是进行简单的偏移,为了使模块加载进内核后能够被放

linux module 模块编程

转载自:http://blog.csdn.net/eroswang/archive/2008/09/13/2924875.aspx 摘要Linux内核模块编程的资料有些纷繁复杂,有的过于简单,有的过于庞杂,我试图用笔记的形式想读者展示怎样来进程Linux模块编程,力图做到简明扼要,这篇文章也是作为本人备忘的资料,所以有些地方过于简略是难免的.本来这篇文章的目的就是让用户知其然,至于所以然还是请参考相应的资料,其实最好的资料莫过于Linux Kernel Source. 适用范围: Linux K

Python常用模块——系统调用os模块

Python常用模块--系统调用os模块 OS模块 os模块提供了很多允许你的程序与操作系统直接交互的功能. 得到当前工作目录,即当前Python脚本工作的目录路径: os.getcwd() 返回指定目录下的所有文件和目录名:os.listdir() 函数用来删除一个文件:os.remove() 删除多个目录:os.removedirs(r"c:\python") 检验给出的路径是否是一个文件:os.path.isfile() 检验给出的路径是否是一个目录:os.path.isdir(

Python常用模块——系统调用sys模块

Python常用模块--系统调用sys模块 sys 模块 sys.argv 命令行参数List,第一个元素是程序本身路径 sys.exit(n) 退出程序,正常退出时exit(0) sys.version 获取Python解释程序的版本信息 sys.maxint 最大的Int值 sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值 sys.platform 返回操作系统平台名称 sys.stdout.write('please:') #标准输出 , 引出进度条的例子

Linux系统调用及用户编程接口(API)

系统调用 所谓系统调用是指操作系统提供给用户程序调用的一组"特殊"接口,用户程序可以通过这组"特殊"接口来获得操作系统内核提供的服务.例如用户可以通过进程控制相关的系统调用来创建进程.实现进程调度.进程管理等. 为什么用户程序不能直接访问系统内核提供的服务呢?这是由于在Linux中,为了更好地保护内核空间,将程序的运行空间分为内核空间和用户空间(也就是常称的内核态和用户态),它们分别运行在不同的级别上,在逻辑上是相互隔离的.因此,用户进程在通常情况下不允许访问内核数

Linux - 模块编程初试

计算机网络的课程设计要做防火墙,老师没有限制在什么系统上面做,所以决定在Linux上实现.找了一下相关的资料,发现其实Linux有提供Netfilter/Iptables,为用户提供防火墙的功能,稍微看了一下,使用Iptables能够很方便地配置用户想要的防火墙,但是好像只能做过滤.数据报修改以及网络地址转换,好像不能做获取其中信息的功能,而且看了一下网上其他人的提问或者博客,好像想做类似的功能还是需要直接使用Netfilter.而如果想要使用Netfiler的话,需要编写hook函数,这个过程

Android照相机模块编程 照片颠倒问题及查询摄像头参数问题的解决

这两天编程弄Android照相机模块,设置好各种参数后,发现预览的时候,照片是颠倒了,不是上下颠倒而是颠倒90°. 我的手机是华为U9200,用的Android4.0.3,后来看到http://www.cnblogs.com/skyseraph/archive/2012/03/26/2418665.html这篇文章,增加代码:myCamera.setDisplayOrientation(90); 然后预览就正常了.但是拍摄的照片在电脑上打开一看,仍然是倾斜的.倾斜了90°,后来加上这句:myPa

系统调用方式文件编程-open

通过Linux系统调用函数编写应用程序,该应用程序实现文件的复制功能 文件描述符--在Linux系统中,所有打开的文件也对应一个数字,这个数字由系统来分配. 1.打开文件--open 头文件:#include<sys/types.h>.#include<sys/stat.h>.#include<fcntl.h> 函数原型:int open(const char *pathname,int flags); int open(const char *pathname,int

第7课-系统调用方式文件编程

一.核心理论:文件描述符(1)成年公民有身份证编号(2)打开的文件对应文件描述符.(3)实质就是一串数字.(4)作用就是能够区分所有打开的文件.二.函数学习man命令默认是按照Linux系统内置手册顺序查找你要搜索的关键词,一旦找到就不继续查找了.相应的顺序是1命令,2系统调用,3库函数.2.1 打开文件2.1.1 函数名Open2.1.2 函数原形(1)int open(const char* pathname,int flags);(2)int open(const char* pathna