linux内核编程入门:hello world 及 基础socket通信模块

此文转载至:https://www.cnblogs.com/bitor/p/9608725.html

  首先得了解一下什么是内核模块: 模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。

  这样说吧,模块就是整个内核的一部分。但是跟C程序中函数不一样的一点是,内核模块可以在它所认为适当的时候,插入到内核或者从内核中删除,而且还不影响内核的正常运行。从而可以在必要的时候对内核进行裁剪,这样能够更好的适应于用户的需求。

  废话少说了。我们现在就开始进入内核编写的阶段,看一看怎么样将一个C程序一步步变成相应的内核模块。

  每一个学习过编程语言的人都知道,第一个示例程序肯定是hello world。我们内核编程的第一个例子也不例外,就是编写一个hello world模块。

  首先让我们在电脑里编写一段C语言代码,hello.c。代码如下:

//必要的头文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
//模块许可证声明(必须)
MODULE_LICENSE("Dual BSD/GPL");
//模块加载函数(必须)
static int hello_init(void)
{
    printk(KERN_ALERT "Hello World enter/n");
    return 0;
}
//模块卸载函数(必须)
static void hello_exit(void)
{
    printk(KERN_ALERT "Hello World exit/n");
}
//模块的注册
module_init(hello_init);
module_exit(hello_exit);
//声明模块的作者(可选)
MODULE_AUTHOR("XXX");
//声明模块的描述(可选)
MODULE_DESCRIPTION("This is a simple example!/n");
//声明模块的别名(可选)
MODULE_ALIAS("A simplest example");

  以上就是一个最简单的内核模块的例子程序。我们通过这个例子来分析一下内核模块的特点。

  1. 在内核模块的开始一部分,跟C语言的一般程序一样,是模块所需要的头文件。
  2. 模块许可证声明,这部分是必须有的。模块许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将收到内核被污染(kernel tainted)的警告。大多数情况下,内核模块应遵守GPL兼容许可权。Linux2.6内核模块最常见的是以MODULE_LICENSE(“Dual BSD/GPL”)语句声明模块采用BSD/GPL双LICENSE。
  3. 模块加载函数,这部分是必须的。模块加载函数必须以“module_init(函数名)“的形式被指定。它返回整形值,若初始化成功,应返回0。在上面那个例子当中,hello_init()函数就是模块加载函数需要执行的,主要是打印一条信息。
  4. 跟模块加载函数相对应的就是模块卸载函数,这部分也是必须的。模块卸载函数在模块卸载的时候执行,不返回任何值,必须以“module_exit(函数名)“的形式来指定。在上面的例子中,hello_exit()函数就是模块卸载函数需要执行的,只要是打印了一条退出信息。
  5. 函数最后的一部分,是模块声明与描述部分。这部分可以有,也可以省略。在Linux内核模块中,我们可以用:

MODULE_AUTHOR 声明模块的作者
MODULE_DESCRIPTION 声明模块的描述
MODULE_VERSION 声明模块的版本
MODULE_DEVICE_TABLE 声明模块的设备表
MODULE_ALIAS 声明模块的别名

注:

  1. module_init()是驱动程序初始化的入口点。它就相当于c语言程序中的main()函数。对于内置的模块,内核在引导时调用该引导点,对于可加载模块则在模块插入到内核时才调用。
  2. 模块加载函数和模块卸载函数中都用到了printk()函数,该函数是由内核定义的,功能与C库中的printf()类似,它把要打印的信息输出到终端或系统日志中。在本例中,我们是将初始化的打印信息输出到日志中,我们在看它对应的输出时,这时可以用dmesg命令来查看。

  编写完了.c文件,下面我们就要对其进行对应的操作,要把一个普通的.c文件变成我们所需要的内核文件。一般我们理解,应该是应用几条Linux下的命令就可以搞定(如gcc,g++……),这里的理解是对的,我们就是需要几个命令就OK。但是我们知道,编译这个需要敲打的命令过于多,要输入内核版本的号,路径,和编写模块的路径与信息。如果每次都输入这么多,那肯定是太麻烦。这时我们就想到了Makefile文件,通过它来管理一个庞大的项目是再好不过的。下面我们就在刚才.c文件目录下编写一个Makefile文件。对应的代码如下:

obj-m += hello.o
#generate the path
CURRENT_PATH:=$(shell pwd)
#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)
#the absolute path
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
#complie object
all:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
#clean
clean:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

  makefile详解可参考:https://www.cnblogs.com/guanguangreat/p/7920378.html

  首先第一句话就是指定要编译的文件。

  pwd是获得当前的相对路径,然后就是获得当前的内核版本号,我们可以用uname -r命令,这样我们就获得了当前内核的绝对路径。这样做的一个好处,就是你可以在不同的内核版本中进行移植,而且可读性也增强了。

  对于其他的Makefile语法上的问题就不在这里介绍。不会的,可以看看相应的语法介绍。

  有了Makefile文件后,我们就离成功不远了。在.c文件的同一目录下,执行make命令,系统会在当前目录下生成好多个文件。其中就有与之相关的.o和.ko文件。hello.ko就是模块目标文件。到此,模块编译好了。

  模块编译好了,但是还不能为我们工作。下面就是将目标模块插入到内核和从内核中删除。这里需要用到两个命令,insmod和rmmod 我们光看这两个命令单词就能猜出他们的意思。输入命令:sudo insmod hello.ko(注意要用sudo),这时没有任何提示,很多人会很奇怪,刚才不是说过,模块加载后,程序中要对应输出一条提示信息,怎么这里什么都没有。大家不要急,再想一想刚才所用到的打印信息的函数printk(),它与我们平常C库的printf()函数不一样,不是运行在用户界面上的,所以肯定不会在终端上显示出信息。要看信息必须要进入到日志文件中。这时我们再输入命令进到系统日志:dmesg,我们把界面拖到最后,会发现有一条信息,Hello World enter。哈哈,这正是我们所需要的,说明我们刚才编写的模块已经插入到内核当中了。接下来再试一试删除命令,输入命令:sudo rmmod hello.ko,这时跟刚才的插入命令一样,没有什么反应。再输入命令打开系统日志,我们会发现在刚才 Hello World enter命令后面会有一个新的信息Hello World exit,这说明我们的模块卸载成功。这样我们就大功告成,庆祝一下吧。

  除了加载和删除模块,我们也可以用命令 lsmod 来查看当前系统中加载的所有模块及模块间的依赖关系。如果刚才我们加载了hello模块并没有删除,用这个命令,我们会在其中找到hello这一项,这样也可以说明我们自己编写的模块加载成功。相应的,如果我们删除了该模块,用这个命令后,hello模块不会出现。lsmod命令实际上读取并分析/proc/modules文件。

  使用modinfo<模块名>命令可以获得模块的信息,包括模块的作者,说明,参数……也就是我们刚才编写模块时可选的几个部分。

附录(内核态编程socket通信代码示例):

server.c

/* server.c */
#include<linux/in.h>  
#include<linux/inet.h>  
#include<linux/socket.h>  
#include<net/sock.h>  
  
#include<linux/init.h>  
#include<linux/module.h>  

int server_core(void)
{
    struct socket *sock, *cSock;
    struct sockaddr_in s_addr;
    unsigned short portNum = 0x8888;
    int ret = 0;

    memset(&s_addr, 0, sizeof(s_addr));
    s_addr.sin_family = AF_INET;//IPv4
    s_addr.sin_port = htons(portNum);//主机字节序转网络字节序
    s_addr.sin_addr.s_addr = htonl(INADDR_ANY);//绑定当前设备所有网卡

    sock = (struct socket *)kmalloc(sizeof(struct socket), GFP_KERNAL);
    cSock = (struct socket*)kmalloc(sizeof(struct socket), GFP_KERNAL);

    /* create a socket */
    ret = sock_create_kern(AF_INET, SOCK_STREAM, 0, &sock);
    if (ret)
    {
        printk("server:socket_create error !\n");
    }
    printk("server:socket_create ok!\n");

    /* bind the socket */
    ret = sock->ops->bind(sock, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
    if (ret <= 0)
    {
        printk("server:bind error!\n");
        return ret;
    }
    printk("server:bind ok!\n");

    /* listen */
    ret = sock->ops->listen(sock, 10);
    if (ret < 0)
    {
        printk("server:listen error!\n");
        return ret;
    }
    printk("server:listen OK!\n");

    /* accept */
    ret = sock->ops->accept(sock, cSock, 10);
    if (ret < 0)
    {
        printk("server:accept error!\n");
        return ret;
    }
    printk("server:accept ok! connection established\n");

    /* kmalloc a receive buffer */
    char *recvbuf = NULL;
    recvbuf = kmalloc(1024, GFP_KERNEL);
    if (NULL == recvbuf)
    {
        printk("server:recvbuf kmalloc error!\n");
        return -1;
    }
    memset(recvbuf, 0, 1024);

    /* receive message from client */
    struct kvec vec;
    struct msghdr msg;
    memset(&vec, 0, sizeof(vec));
    memset(&msg, 0, sizeof(msg));
    vec.iov_base = recvbuf;
    vec.iov_len = 1024;
    ret = kernel_recvmsg(client_sock, &msg, &vec, 1, 1024, 0); /* receive message */
    printk("receive message: %s\n", recvbuf);

    /* release socket */
    sock_release(sock);
    sock_release(cSock);
    return ret;
}

static int server_init(void)
{
    printk("server init: \n");
    return (server_core());
}

static void server_exit(void)
{
    printk("good bye!\n");
}

module_init(server_init);
module_exit(server_exit);

MODULE_LICENSE("GPL");

client.c

/*client.c*/  
#include<linux/in.h>  
#include<linux/inet.h>  
#include<linux/socket.h>  
#include<net/sock.h>  
  
#include<linux/init.h>  
#include<linux/module.h>  
  
int myclient(void)
{  
    struct socket *sock;  
    struct sockaddr_in s_addr;  
    unsigned short portnum = 0x8888;  
    int ret = 0;  
  
    memset(&s_addr,0,sizeof(s_addr));  
    s_addr.sin_family = AF_INET;  
    s_addr.sin_port = htons(portnum);  
       
    s_addr.sin_addr.s_addr=in_aton("10.1.13.132"); /* server ip is 10.1.13.132 */  
    sock = (struct socket *)kmalloc(sizeof(struct socket), GFP_KERNEL);  
  
    /* create a socket */  
    ret = sock_create_kern(AF_INET, SOCK_STREAM, 0, &sock);  
    if(ret < 0)
    {  
        printk("client:socket create error!\n");  
        return ret;  
    }  
    printk("client: socket create ok!\n");  
  
    /*connect server*/  
    ret = sock->ops->connect(sock, (struct sockaddr *)&s_addr, sizeof(s_addr), 0);  
    if(ret != 0)
    {  
        printk("client:connect error!\n");  
        return ret;  
    }  
    printk("client:connect ok!\n");  
  
    /*kmalloc sendbuf*/  
    char *sendbuf=NULL;  
    sendbuf = kmalloc(1024,GFP_KERNEL);  
    if(sendbuf == NULL)
    {  
        printk("client: sendbuf kmalloc error!\n");  
        return -1;  
    }  
    memset(sendbuf, 1, 1024);      
      
    struct kvec vec;  
    struct msghdr msg;  
  
    vec.iov_base = sendbuf;  
    vec.iov_len = 1024;  
  
    memset(&msg, 0, sizeof(msg));  
  
    ret = kernel_sendmsg(sock,&msg,&vec,1,1024); /*send message */  
    if(ret < 0)
    {  
        printk("client: kernel_sendmsg error!\n");  
        return ret;  
    }else if(ret!=1024)
    {  
        printk("client: ret!=1024");  
    }  
    printk("client:send ok!\n");  
  
    return ret;  
}  
  
static int client_init(void)
{  
    printk("client:init\n");  
    return (myclient());  
}  
  
static void client_exit(void)
{  
    printk("client exit!\n");  
}  
  
module_init(client_init);  
module_exit(client_exit);  
MODULE_LICENSE("GPL"); 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值