Linux内核模块编程-HelloWorld

原创 2015年11月07日 17:00:56

HelloWorld内核

开始断断续续学习内核,大概半年了,多少开始对内核有点感悟了,但是对于这个庞然大物我显得很渺小,在枯燥的内核源码之中似乎没有一点点成功的喜悦,因此我选择学习内核模块编程,通过编写一些内核模块来体验那一点点小小的成就感吧!

什么是内核模块

内核模块是具有独立功能的程序。它可以被单独编译,但是不能单独运行,它的运行必须被链接到内核作为内核的一部分在内核空间中运行

最简单的内核模块

#include <linux/module.h>  //所有模块都必须包含的头文件
#include <linux/kernel.h> //一些宏定义,例如这里的KERN_INFO

int init_module(void)
{
    printk(KERN_INFO "Hello world 1.\n");
    /*  
     * 返回非0表示模块初始化失败,无法载入
     */
    return 0;
}

void cleanup_module(void)
{
    printk(KERN_INFO "Goodbye world 1.\n");
}

//一个模块至少需要两个函数,一个初始化函数这里是init_module在载入内核的时候调用,
//一个结束函数,这里是cleannup_module在从内核中注销的时候调用

一个Makefile来编译这个内核模块

obj-m += hello-1.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

注意:本文所有环节都是基于Centos6.5下测试OK,你可能在有的书上看见Makefile是这样写的

make -C /usr/src/linux-headers-$(shell uname -r) M=$(PWD) modules
其实/lib/modules/$(shell uname -r)/build 这个路径就是上面路径的一个软链接
[root@localhost 2.6.32-431.el6.x86_64]# ls -al build
lrwxrwxrwx. 1 root root 44 Mar 16 05:26 build -> /usr/src/kernels/2.6.32-504.12.2.el6.x86_64/

编写好makefile文件后,使用make进行编译,编译完就出现一个.ko的文件,这个就是内核模块,需要载入运行

载入内核模块进行运行

载入内核模块的方法有很多比如: modprobe 和 insmod前者会分析模块的依赖关系,并且会去指定路径查找内核模块载入,而后者需要指定内核模块的绝对路径进行载入并且不解决模块的依赖关系。这里我们使用insmod来载入内核模块,使用rmmod卸载内核模块
[root@localhost kernel_module]# insmod hello-1.ko
使用dmes查看内核模块的速出
Hello world 1.
卸载内核模块
[root@localhost kernel_module]# rmmod hello-1 
dmesg查看输出
Goodbye world 1.

内核模块编程和应用程序编程的异同

  • 内核模块编程是不能去使用标准库(比如malloc free等)和一些第三方的库
  • 内核模块编程是没有内存保护的,如果内存访问错误,就会出现oops错误
  • 内核模块编程是没有main函数的,只有一个初始化函数,和一个提出函数
  • 内核模块编程需要使用内核提供的头文件和API
  • 内核模块编程的标准输出是输出到文件,而不是输出到屏幕
  • 内核模块编程的debug是不能使用gdb来进行调试的。

内核模块进阶

内核模块的编程不仅仅是上面的一个HelloWorld,内核模块编程还有一些更高级的写法,下面会一一介绍:

去掉init_module/cleanup_module

在上面的HelloWorld模块中,你会发现初始化函数和退出函数好像是固定的名称,那么有没有办法自己自定义名称呢其实是可以的,你可以自己自定义名称,然后进行注册即可(注册其实就是做了一个函数指针的赋值而已)
下面是自定义名称的写法:

//不需要固定内核模块的初始化函数的名字和结束的名字
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int hello_2_init(void)
{
    printk(KERN_INFO "Hello,world 2\n");
    return 0;
}

static void  hello_2_exit(void)
{
    printk(KERN_INFO "Goodbye,world 2\n");
}

//这两个函数来注册模块初始化和模块结束
module_init(hello_2_init);
module_exit(hello_2_exit);

__init/__initdata/__exit

在有的内核模块编程的书籍或者介绍内核模块编程的博客中,你或许会发现有这样的一些特殊关键字__init ,_initdata ,__exit等等,其实这些都是gcc的扩展属性:__init 宏最常用的地方是驱动模块初始化函数的定义处,其目的是将驱动模块的初始化函数放入名叫.init.text的输入段。当内核启动完毕后,这个段中的内存会被释放掉供其他使用。__initdata宏用于数据定义,目的是将数据放入名叫.init.data的输入段。其它几个宏也类似。

模块描述信息

可以使用modinfo去查看一个模块的模块信息,下面是自己编写的模块和系统自带的模块的两个模块信息的对比

[root@localhost kernel_module]# modinfo hello-1.ko
filename:       hello-1.ko
srcversion:     0D3956C127A907CC9E7114F
depends:        
vermagic:       2.6.32-504.12.2.el6.x86_64 SMP mod_unload modversions 

[root@localhost kernel_module]# modinfo  /lib/modules/2.6.32-431.el6.x86_64/kernel/fs/ext4/ext4.ko 
filename:       /lib/modules/2.6.32-431.el6.x86_64/kernel/fs/ext4/ext4.ko
license:        GPL
description:    Fourth Extended Filesystem
author:         Remy Card, Stephen Tweedie, Andrew Morton, Andreas Dilger, Theodore Ts'o and others
srcversion:     345EBDA2AEFF60FFED78864
depends:        mbcache,jbd2
vermagic:       2.6.32-431.el6.x86_64 SMP mod_unload modversions 

从上面的对比可知,自己编写的模块的模块信息很少,没有作者信息,没有许可证信息等等,其实这些都可以设置

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

#define DRIVER_AUTHOR "zyf"
#define DRIVER_DESC   "A sample driver"

static int __init init_hello_4(void)
{
    printk(KERN_INFO "Hello, world 4\n");
    return 0;
}

static void __exit cleanup_hello_4(void)
{
    printk(KERN_INFO "Goodbye, world 4\n");
}

module_init(init_hello_4);
module_exit(cleanup_hello_4);

//模块的许可证 
MODULE_LICENSE("GPL");
//模块的作者
MODULE_AUTHOR(DRIVER_AUTHOR);
//模块的描述
MODULE_DESCRIPTION(DRIVER_DESC);

模块参数

在用户态编写程序的时候我们都应该清楚,是可以给程序传递参数的,那么同样内核模块同样也有这样的需求,下面的例子展示了如何去给内核模块传递参数:

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/stat.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYF");
static short int myshort = 1;
static int myint = 420;
static long int mylong = 9999;
static char *mystring = "blah";
static int myintArray[2] = {-1,-1};
static int arr_argc = 0;
//需要使用module_param来对参数进行说明,指明这个参数的类型,权限等charp是字符指针
//定义数组参数需要使用module_param_array
module_param(myshort,short,S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
MODULE_PARM_DESC(myshort,"A short integer");
module_param(myint,int,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
MODULE_PARM_DESC(myint,"A integer");
module_param(mylong,long,S_IRUSR);
MODULE_PARM_DESC(mylong,"A long integer");
module_param(mystring,charp,0000);
MODULE_PARM_DESC(mystring,"A character string");
module_param_array(myintArray,int,&arr_argc,0000);
MODULE_PARM_DESC(myintArray,"An array of integer");
static int __init hello_5_init(void)
{
    int i;
    printk(KERN_INFO "Hello, world 5\n=============\n");
    printk(KERN_INFO "myshort is a short integer: %hd\n", myshort);
    printk(KERN_INFO "myint is an integer: %d\n", myint);
    printk(KERN_INFO "mylong is a long integer: %ld\n", mylong);
    printk(KERN_INFO "mystring is a string: %s\n", mystring);
    for (i = 0; i < (sizeof myintArray / sizeof (int)); i++)
    {
        printk(KERN_INFO "myintArray[%d] = %d\n", i, myintArray[i]);
    }
    printk(KERN_INFO "got %d arguments for myintArray.\n", arr_argc);
    return 0;
}

static void __exit hello_5_exit(void)
{
    printk(KERN_INFO "Goodbye,world 5\n");
}

module_init(hello_5_init);
module_exit(hello_5_exit);
/*

载入模块的时候,如果不指定参数就是上面的默认值,如果要指定参数的话
可以像下面这样来指定参数。
insmod hello-5.ko mystring="superc" myint=444 

*/

模块文件分割

在用户态写程序的时候,你会将一个大的程序分割成好几个文件,这样程序脉络就显的很清晰。在这里我们将初始化函数和退出函数分开在两个文件中编写。

start.c中
#include <linux/kernel.h>   /* We're doing kernel work */
#include <linux/module.h>   /* Specifically, a module */

int init_module(void)
{
    printk(KERN_INFO "Hello, world - this is the kernel speaking\n");
    return 0;
}

stop.c中
#include <linux/kernel.h>   /* We're doing kernel work */
#include <linux/module.h>   /* Specifically, a module  */

void cleanup_module()
{
    printk(KERN_INFO "Short is the life of a kernel module\n");
}

那么Makefile编译的时候需要设置成这样:
obj-m += startstop.o
startstop-objs := start.o stop.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

小白学Linux之内核模块编程

Linux内核模块编程Linux内核模块编程是一个很重要的知识点。尤其是编写底层驱动程序时,一定会涉及到它。内核模块编程也是Tiger哥学习Linux时第一节课所接触的知识。由此可以看出它的impor...
  • tigerjb
  • tigerjb
  • 2010年11月15日 20:29
  • 19791

【Linux开发】编写属于你的第一个Linux内核模块

曾经多少次想要在内核游荡?曾经多少次茫然不知方向?你不要再对着它迷惘,让我们指引你走向前方…… 内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中,它八成就是了。Linux内核和它的用户空...

linux内核模块编程实例

linux内核模块编程实例 学号:201400814125 班级:计科141 姓名:刘建伟 1.确定本机虚拟机中的Ubuntu下Linux的版本 通过使用命令uname -a/una...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

Linux内核模块的概念和基本的编程方法

Linux内核模块的概念和基本的编程方法 标签: Linux内核模块 2013-06-14 18:29 1864人阅读 评论(0) 收藏 举报 分类: ...

Linux内核模块编程之Helloworld!

http://www.hustyx.com/ubuntu/4/ 下一个项目要做基于Linux的设备驱动开发,其实就是做Ophone的移植工作。刚找了本Linux设备驱动来学习学习。 首...

【Linux】【Kernel】一个简单的内核模块例子

kernel 内核模块 交叉编译

Linux---我的第一个内核模块之Hello World

1、什么是内核模块? 内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),简称为模块。Linux内核之所以提供模块机...
  • L_in12
  • L_in12
  • 2015年11月04日 17:24
  • 688

linux内核驱动模块编程框架---(hello world模块)

linux内核驱动模块编程框架,模块Makefile文件,模块的常用操作命令(模块的加载卸载命令);重点来说下注册的回调函数,static int __init hello_init(void)和st...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux内核模块编程-HelloWorld
举报原因:
原因补充:

(最多只允许输入30个字)