在Linux-2.6.26.3/net/ipv4/af_inet.c
文件中有一个名为inet_init()的函数对协议进行了初始化。inet_init()函数使用proto_register()函数来注册每个内嵌协议。
软中断机制的核心元素:
中断事件处理过程:
Linux系统最用同时注册32个软中断,目前系统使用了6个软中断。其中一个为taskle机制,该机制用来实现下半部,描述软中断的核心数据结构为中断向量表,其定义如下:
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
因为内核层和用户层在网络方面的差别很大,在内核的网络层的sk_buff结构占有重要的地位,几乎所有的处理与此结构有关系。
sk_buff主要成员:
socket数据在内核中的流程主要包括初始化、销毁、接收和发送网络数据源。其过程设计网卡驱动、网络协议栈和应用层的接口函数。
创建socket()其实就是创建一个socket实例,然后创建一个文件描述符结构。创建套接字文件描述符会互相建立关联,即建立相互连接的的指针,并且初始化这些文件的读写操作映射到read()、write()函数上来。
在初始化套接字的时候,同时初始化socket的操作函数(proto_ops结构)
创建socket的同时还创建sock结构的数据空间。还会初始化三个队列:receive_queue,send_queue,backlog_queue。
网络数据接收依次经过网卡驱动和协议栈程序。
linux对网络数据的发送过程的处理与接收过程相反。在一端对socket进行write()的过程中。首先会把write的字符串缓冲区整理成msghdr的数据结构形式,然后调用sock_sendmsg()把msghdr的数据传送至inet层。
msghdr结构中数据区的每个数据包,创建sk_buff结构,填充数据,挂至发送队列。一层层往下层协议传递。以下的每层协议将不再对数据进行复制,而是对sk_buff结构直接进行操作。
内核模块编程和典型的应用程序的区别:
典型应用有一个main程序,而内核模块需要一个初始化函数和清理函数,在向内核中插入模块时调用初始化函数,卸载内核模块时调用清理函数。
加载内核相对于直接编写内核模块有很大方便性:
- 不用重新编译内核
- 可以动态加载和卸载,调试使用方便。
编写C语言程序:
//必要的头文件
#include
#include
#include
//模块许可证声明(必须)
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");
编写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
执行make命令能得到
以上这些文件,使用sudo insmod Hello.ko
来加载内核:
但是能发现并没有任何消息提示,这是因为printk函数并不是在命令行将信息打印而是在系统日志里面,需要使用指令dmesg
进入系统日志:
也可以使用lsmod查看内核依赖关系:
最后,输入rmmod
指令卸载内核
自动调用模块的的初始化函数,进行模块的初始化,主要是资源申请。
使用命令rmmod卸载内核模块,模块清除函数自动调用。主要状态重置和资源释放。
并不是强制要求必须声明。内核可以识别以下四种许可方式:GPLa Dual BSD/GPLa Dual MPL/GPLa Proprietary。如果没有采用以上许可证方式的声明则假定为私有的,内核加载这种模块会被“污染”。
内核加载模块过程
内核卸载模块过程
在IP包的IPv4协议栈上的传递过程中,有五个检查点,并且各引入了一对NF_HOOK()宏函数的一个响应的调用:
利用这5个参考点,查阅用户注册的回调函数,根据用户定义的回调函数来监视进出的网络数据包,是netfilter的基本实现框架。
netfilter定义了一个全局变量来存储用户的回调函数:
struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];
其中,NPROTO是协议类型,可以是TCP、UDP或者IP协议。NF_MAX_HOOKS是挂接的钩子最大数量。
以上介绍的五个检查点对应五个钩子函数。
如果是要做包过滤,需要用到NF_IP_LOCAL_IN钩子函数。
netfilter的框架是在协议栈处理过程中调用NF_HOOK(),插入处理过程来实现的。
#ifdef CONFIG_NETFILTER
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))
#else
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)
#endif /*CONFIG_NETFILTER*/
以上为NF_HOOK()函数的定义。从该函数宏可知,当编译了netfilter时(#ifdef CONFIG_NETFILTER )就会调用nf_hook,slow()函数。如果没有的话则调用NF_HOOK宏中的参数okfn。
netfilter的钩子函数的返回值可以为NF_ACCEPT、 NF_DROP、 NF_STOLEN、NF_QUERE、 NF_REPEAT
注册和注销钩子函数的接口主要有:nf_register_hook、nf_unregister_hook、nf_register_sockopt 、nf_unregister_sockopt
nf_hook_ops结构
struct nf_hook_ops
{
struct list_head list; //钩子链表
nf_hookfn *hook; //钩子处理函数
struct module //模块所有者
int pf; //钩子的协议族
int hooknum; //钩子的位置值
int priority; //钩子的优先级,默认情况为继承优先级
};
typedef unsigned int nf_hookfn(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *));
okfn()函数是当回调函数为空时,netfilter调用的处理函数。
void nf_register_net_hook(struct net *net, const struct nf_hook_ops *ops);
void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *ops);
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct nf_hook_ops nfhoLocalIn; //定义实现钩子函数的结构体
MODULE_LICENSE("Dual BSD/GPL");
unsigned int hello_hookfn(void* priv, struct sk_buff* skb, const struct nf_hook_state* state) //回调函数
{
printk(KERN_ALERT "Hello World Hook\n");
return NF_ACCEPT;
}
int init_module() //初始化模块
{
nfhoLocalIn.hook = hello_hookfn; //构建结构体
nfhoLocalIn.pf = PF_INET;
nfhoLocalIn.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &nfhoLocalIn); //注册钩子
printk("My nf register\n");
return 0;
}
void cleanup_module()
{
nf_unregister_net_hook(&init_net, &nfhoLocalIn); //注销钩子
printk("My nf unregister\n");
}