内核东西(netlink通信 工作队列)

 

1/31/2009
使用sockopt实现内核与用户之间通信

使用sockopt实现内核与用户之间通信

1. Linux下的sockopt

Linux提供了多种通信方式来实现内核和用户之间的数据通信,基于socket的sockopt是最常用也比较简单易用的一种方式。它的本质和ioctl()很相似,只是ioctl()需要创建新的设备文件,而sockopt只需要创建一个socket套接字便可以使用户与内核进行通信。

这里分别从内核和用户两方面来介绍sockopt的使用。

2. 内核中使用sockopt

在内核中,Netfilter提供了struct nf_sockopt_ops来将sockopt的操作定义为一个节点来加入链表中。同时提供了注册/解注册函数来使用sockopt。

数据结构和函数

sockopt操作结构体

struct nf_sockopt_ops

{

struct list_head list;

int pf;

/* Non-inclusive ranges: use 0/0/NULL to never get called. */

int set_optmin;

int set_optmax;

int (*set)(struct sock *sk, int optval, void __user *user, unsigned int len);

int (*compat_set)(struct sock *sk, int optval, void __user *user, unsigned int len);

int get_optmin;

int get_optmax;

int (*get)(struct sock *sk, int optval, void __user *user, int *len);

int (*compat_get)(struct sock *sk, int optval, void __user *user, int *len);

/* Number of users inside set() or get(). */

unsigned int use;

struct task_struct *cleanup_task;

};

注册和解注册函数

int nf_register_sockopt(struct nf_sockopt_ops *reg)

void nf_unregister_sockopt(struct nf_sockopt_ops *reg)

用户数据读写函数

int copy_from_user(void *to, const void __user *from, int n)

int copy_to_user(void __user *to, const void *from, int n)

 

内核模块代码:

sockopt_srv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/netfilter_ipv4.h>
#include <linux/init.h>
#include <asm/uaccess.h>

#define SOCKET_OPS_BASE          128
#define SOCKET_OPS_SET       (SOCKET_OPS_BASE)
#define SOCKET_OPS_GET      (SOCKET_OPS_BASE)
#define SOCKET_OPS_MAX       (SOCKET_OPS_BASE + 1)

#define KMSG          "a message from kernel"
#define KMSG_LEN      sizeof("a message from kernel")

MODULE_LICENSE("GPL");

static int recv_msg(struct sock *sk, int cmd, void *user, unsigned int len)
{
    int ret = 0;
    printk(KERN_INFO "sockopt: recv_msg()/n");
    /*
    switch(cmd)
    {
    case IMP1_SET:
    {
        char umsg[64];
        memset(umsg, 0, sizeof(char)*64);
        copy_from_user(umsg, user, sizeof(char)*64);
        printk("umsg: %s", umsg);
    }
    break;
    }
    */
    if (cmd == SOCKET_OPS_SET)
    {
        char umsg[64];
        int len = sizeof(char)*64;
        memset(umsg, 0, len);
        ret = copy_from_user(umsg, user, len);
        printk("recv_msg: umsg = %s. ret = %d/n", umsg, ret);       
    }
    return 0;
}

static int send_msg(struct sock *sk, int cmd, void *user, int *len)
{
    int ret = 0;
    printk(KERN_INFO "sockopt: send_msg()/n");
    if (cmd == SOCKET_OPS_GET)
    {
        ret = copy_to_user(user, KMSG, KMSG_LEN);
        printk("send_msg: umsg = %s. ret = %d. success/n", KMSG, ret);   
    }
    return 0;
}

static struct nf_sockopt_ops test_sockops =
{
    .pf = PF_INET,
    .set_optmin = SOCKET_OPS_SET,
    .set_optmax = SOCKET_OPS_MAX,
    .set = recv_msg,
    .get_optmin = SOCKET_OPS_GET,
    .get_optmax = SOCKET_OPS_MAX,
    .get = send_msg,
};

static int __init init_sockopt(void)
{
    printk(KERN_INFO "sockopt: init_sockopt()/n");
    return nf_register_sockopt(&test_sockops);
}

static void __exit fini_sockopt(void)
{
    printk(KERN_INFO "sockopt: fini_sockopt()/n");
    nf_unregister_sockopt(&test_sockops);
}

module_init(init_sockopt);
module_exit(fini_sockopt);

 

3. 用户使用sockopt

Linux同样提供了一组用户接口来读写sockopt

#include <sys/types.h>

#include <sys/socket.h>

int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);

int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);

 

 

在用户进程中,我们可以创建一个socket,然后通过socket来调用getsockopt/setsockopt来和内核空间通信。

用户代码

sockopt_clt.c

#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <string.h>
#include <errno.h>

#define SOCKET_OPS_BASE      128
#define SOCKET_OPS_SET       (SOCKET_OPS_BASE)
#define SOCKET_OPS_GET      (SOCKET_OPS_BASE)
#define SOCKET_OPS_MAX       (SOCKET_OPS_BASE + 1)

#define UMSG      "a message from userspace"
#define UMSG_LEN  sizeof("a message from userspace")

char kmsg[64];

int main()
{
    int sockfd;
    int len;
    int ret;

    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if(sockfd < 0)
    {
        printf("can not create a socket/n");
        return -1;
    }

    /*call function recv_msg()*/
    ret = setsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_SET, UMSG, UMSG_LEN);
    printf("setsockopt: ret = %d. msg = %s/n", ret, UMSG);
    len = sizeof(char)*64;

    /*call function send_msg()*/
    ret = getsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_GET, kmsg, &len);
    printf("getsockopt: ret = %d. msg = %s/n", ret, kmsg);
    if (ret != 0)
    {
        printf("getsockopt error: errno = %d, errstr = %s/n", errno, strerror(errno));
    }

    close(sockfd);
    return 0;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

Linux系统软中断

Linux系统软中断

1. Linux软中断

中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。例如,假设一个数据块已经达到了网线,当中断控制器接受到这个中断请求信号时,Linux内核只是简单地标志数据到来了,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行(如把数据移入一个缓冲区,接受数据的进程就可以在缓冲区找到数据)。

因此,内核把中断处理分为两部分:上半部(top half)和下半部(bottom half),上半部(就是中断服务程序)内核立即执行,而下半部(就是一些内核函数)留着稍后处理:

快速的“上半部”来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常,除了在设备和一些内存缓冲区(如果你的设备用到了DMA,就不止这些)之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分做的工作很少。

下半部运行时是允许中断请求的,而上半部运行时是关中断的,这是二者之间的主要区别。

内核到底什时候执行下半部,以何种方式组织下半部?这就是我们要讨论的下半部实现机制,这种机制在内核的演变过程中不断得到改进,在以前的内核中,这个机制叫做bottom half(简称bh),在2.4以后的版本中有了新的发展和改进,改进的目标使下半部可以在多处理机上并行执行,并有助于驱动程序的开发者进行驱动程序的开发。下面主要介绍常用的小任务(Tasklet)机制及2.6内核中的工作队列机制。除此之外,还简要介绍2.4以前内核中的下半部和任务队列机制。

2. softirq软中断

struct softirq_action

{

void (*action)(struct softirq_action *);

void *data;

};

static struct softirq_action softirq_vec[32] __cacheline_aligned;

typedef struct

{

unsigned int __softirq_active; //活动中断向量标志

unsigned int __softirq_mask; //软中断向量的屏蔽掩码

unsigned int __local_irq_count;

unsigned int __local_bh_count;

unsigned int __syscall_count;

unsigned int __nmi_count; /* arch dependent */

} ____cacheline_aligned irq_cpustat_t;

static inline void __cpu_raise_softirq(int cpu, int nr)

{

softirq_active(cpu) |= (1<<nr);

}

enum

{

HI_SOFTIRQ = 0, //高优先级的软中断

NET_TX_SOFTIRQ, //网络数据的中断

NET_RX_SOFTIRQ, //网络数据的中断

TASKLET_SOFTIRQ //tasklet中断

};

static inline void raise_softirq(int nr)

void __init softirq_init()

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)

asmlinkage void do_softirq()

3. tasklet小任务

Tasklet机制是一种较为特殊的软中断。Tasklet一词的原意是“小片任务”的意思,这里是指一小段可执行的代码,且通常以函数的形式出现。软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。

小任务是指对要推迟执行的函数进行组织的一种机制。小任务机制是实现下半部的最佳选择。小任务可以动态创建,使用方便,执行起来也比较快。我们既可以静态地创建小任务,也可以动态地创建它。选择那种方式取决于到底是想要对小任务进行直接引用还是一个间接引用。如果准备静态地创建一个小任务(也就是对它直接引用)。

tasklet数据结构:

struct tasklet_struct

{

struct tasklet_struct *next;     /*指向链表中的下一个结构*/

     unsigned long state;        /* 小任务的状态 */

     atomic_t count;    /* 引用计数器 */

     void (*func) (unsigned long);    /* 要调用的函数 */

     unsigned long data;      /* 传递给函数的参数 */

};

enum

{

TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */

TASKLET_STATE_RUN /* Tasklet is running (SMP only) */

};

 

 

 

 

多个tasklet可以通过tasklet描述符中的next成员指针链接成一个单向对列。为此,Linux专门在头文件include/linux/interrupt.h中定义了数据结构tasklet_head来描述一个tasklet对列的头部指针。

struct tasklet_head

{

struct tasklet_struct *list;

};

Tasklet主要函数:

Tasklet声明:

#define DECLARE_TASKLET(name, func, data) /

struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) /

struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

tasklet初始化

void tasklet_init( struct tasklet_struct *t,

void (*func)(unsigned long),

unsigned long data);

tasklet调度函数

static inline void tasklet_schedule(struct tasklet_struct *t)

tasklet开启和停止

static inline void tasklet_enable(struct tasklet_struct *t)

static inline void tasklet_disable(struct tasklet_struct *t)

tasklet任务结束

void tasklet_kill(struct tasklet_struct *t);

void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu);

tasklet同步控制

inline int tasklet_trylock(struct tasklet_struct *t)

inline void tasklet_unlock(struct tasklet_struct *t)

inline void tasklet_unlock_wait(struct tasklet_struct *t)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4. workqueue工作队列

工作队列(work queue)是另外一种将工作推后执行的形式 ,它和我们前面讨论的所有其他形式都有不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。

那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。

工作队列数据结构

struct work_struct

{

unsigned long pending; /* 这个工作正在等待处理吗?*/

struct list_head entry; /* 连接所有工作的链表 */

void (*func) (void *); /* 要执行的函数 */

void *data; /* 传递给函数的参数 */

void *wq_data; /* 内部使用 */

struct timer_list timer; /* 延迟的工作队列所用到的定时器 */

};

struct execute_work

{

struct work_struct work;

};

struct workqueue_struct

{

struct cpu_workqueue_struct *cpu_wq;

const char *name;

struct list_head list; /* Empty if single thread */

};

工作创建:

DECLARE_WORK(name, void (*func) (void *), void *data);

工作初始化:

INIT_WORK(struct work_struct *work, woid(*func) (void *), void *data);

工作队列创建:

struct workqueue_struct * create_workqueue(char* name)

struct workqueue_struct * create_singlethread_workqueue(char* name)

工作队列销毁:

void destroy_workqueue(struct workqueue_struct *wq);

static inline int cancel_delayed_work(struct work_struct *work)

工作调度

schedule_work(struct work_struct *work)

schedule_delayed_work(struct work_struct *work, unsigned long delay)

工作队列处理函数原型:

void work_handler(void *data);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1/25/2009
编写自己的Linux设备中断服务程序

编写自己的Linux设备中断服务程序 作者:JuKevin

1. 设备中断
Linux系统中断可以分为硬中断和软中断两种。软中断又是和硬中断相对应的,"硬中断是外部设备对CPU的中断","软中断通常是硬中断服务程序对内核的中断","信号则是由内核(或其他进程)对某个进程的中断"。

2. 设备中断相关数据结构和函数

中断请求函数:
int request_irq(unsigned int irq,
                irqreturn_t (*handler)(int, void *, struct pt_regs *),
                unsigned long irqflags,
                const char *devname,
                void *dev_id)

中断请求flag:
unsigned long irqflags
IRQF_SHARED                Interrupt is shared
IRQF_DISABLED                Disable local interrupts while processing
IRQF_SAMPLE_RANDOM        The interrupt can be used for entropy

中断释放函数
void free_irq(unsigned int irq, void *dev_id)

中断处理函数原型
irqreturn_t (*handler)(int, void *, struct pt_regs *)

irqreturn_t            返回值类型
IRQ_NONE        means we didn't handle it.
IRQ_HANDLED     means that we did have a valid interrupt and handled it.
IRQ_RETVAL(x)     selects on the two depending on x being non-zero (for handled)

#define IRQ_NONE            (0)
#define IRQ_HANDLED    (1)
#define IRQ_RETVAL(x)    ((x) != 0)

struct net_device
{
    char            name[IFNAMSIZ];
    struct hlist_node    name_hlist;

    unsigned long        mem_end;    /* shared mem end    */
    unsigned long        mem_start;    /* shared mem start    */
    unsigned long        base_addr;    /* device I/O address    */
    unsigned int            irq;        /* device IRQ number    */

    unsigned char        if_port;    /* Selectable AUI, TP,..*/
    unsigned char        dma;        /* DMA channel        */

    unsigned long        state;

    struct net_device    *next;
    /* The device initialization function. Called only once. */
    int (*init)(struct net_device *dev);

    /* Net device features */
    unsigned long        features;

    unsigned int            flags;    /* interface flags (a la BSD)    */
    unsigned short        gflags;
   unsigned short      priv_flags;

    /* Interface address info. */
    unsigned char        perm_addr[MAX_ADDR_LEN];
    unsigned char        addr_len;   
    unsigned short         dev_id;   
    …
}

 

 

3. 自己编写设备中断服务程序

myirq.c源文件

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

static int irq;
static char *interface = NULL;

module_param(interface, charp, 0644);
module_param(irq, int, 0644);

//中断服务例程
static irqreturn_t myirq(int irq, void *dev_id, struct pt_regs *regs)
{
    printk (KERN_INFO " myirq…");
    struct net_device *dev = (struct net_device *)dev_id;
    printk (KERN_INFO "interrupt: dev_id=%d irq=%d name=%s/n", *(int*)dev_id, irq, interface);

    return IRQ_NONE;
}

//自己的设备中断模块初始化函数
static int __init devirq_init(void)
{
    printk (KERN_INFO "devirq_init");
    //regist irq
    //if (request_irq(irq,& myirq,SA_SHIRQ,interface,&irq)){//early than 2.6.23
    if (request_irq( irq, myirq, SA_SHIRQ, interface, (void*)&irq))
    {
        //later than 2.6.23
        printk(KERN_ERR "devirq: cannot register IRQ: %s %d/n", interface, irq);
        return -EIO;
    }
    printk("%s Request on IRQ %d succeeded/n", interface, irq);
    return 0;
}

//设备中断模块退出函数
static void __exit devirq_exit(void)
{
    printk (KERN_INFO "devirq_exit/n");
    free_irq(irq, &irq);
    printk(KERN_INFO "Free IRQ %d/n", irq);
    return;
}

MODULE_AUTHOR("Kevin");
MODULE_LICENSE("GPL");
module_init(devirq_init);
module_exit(devirq_exit);

 

 

 

 


Linux下设备中断表保存在/proc目录下的interrupts文件中。

我们可以查看一下它的内容
cat /proc/interrupts
          CPU0       CPU1
  0:         38369      22506  IO-APIC-edge  timer
  1:        37          2    IO-APIC-edge  i8042
  6:         2          2    IO-APIC-edge  floppy
  7:         0          0    IO-APIC-edge  parport0
  8:         0          1    IO-APIC-edge  rtc
  9:         0          0       IO-APIC-level  acpi
12:        230          3      IO-APIC-edge  i8042
15:        927        802     IO-APIC-edge  ide1
169:            0          0   IO-APIC-level uhci_hcd:usb1, Ensoniq AudioPCI
177:       7255       2307   IO-APIC-level  ioc0
185:        178         14   IO-APIC-level  eth0

我们看到, 以太网驱动eth0的IRQ是185, 我们编译好自己的模块程序,然后装载模块,并输入参数,来替换eth0的中断服务程序:
insmod myirq.ko irq=185 interface=myirq

这时候再查看中断表:
cat /proc/interrupts
          CPU0       CPU1
  0:     38369      22506    IO-APIC-edge  timer
  1:        37          2    IO-APIC-edge  i8042
  6:         2          2    IO-APIC-edge  floppy
  7:         0          0    IO-APIC-edge  parport0
  8:         0          1    IO-APIC-edge  rtc
  9:         0          0    IO-APIC-level  acpi
12:        230         3    IO-APIC-edge  i8042
15:        927        802       IO-APIC-edge  ide1
169:      0          0   IO-APIC-level    uhci_hcd:usb1, Ensoniq AudioPCI
177:       7255       2307   IO-APIC-level  ioc0
185:        178         14   IO-APIC-level  eth0  myirq

我们看到,自己的myirq服务例程已经替换掉了原有的以太网eth0的中断服务例程了。

我们来验证一下,用dmesg查看模块日志:

myirq Request on IRQ 185 succeeded
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq

设备eth0的中断服务程序已经被我们自己编写的中断服务程序所替换掉了。

 

 

 

 

 

 

1/7/2009
LINUX内核中Netfilter Hook的使用
 
LINUX内核中Netfilter Hook的使用 作者:JuKevin
 
HookLinux Netfilter中重要技术,使用hook可以轻松开发内核下的多种网络处理程序。下面简单介绍一下hook及其使用。
1.      hook相关数据结构
 
struct nf_hook_ops
{
       struct list_head list;
 
       /* User fills in from here down. */
       nf_hookfn *hook;
       struct module *owner;
       int pf;
       int hooknum;
      
       /* Hooks are ordered in ascending priority. */
       int priority;
};
 
主要成员介绍
int pf; 协议家族类型
int hooknum hook执行点,它表示在报文处理的具体什么阶段执行hook函数。
Linux有以下几种执行点:
NF_IP_PRE_ROUTING          在报文作路由以前执行;
NF_IP_FORWARD                  在报文转向另一个NIC以前执行;
NF_IP_POST_ROUTING       在报文流出以前执行;
NF_IP_LOCAL_IN                 在流入本地的报文作路由以后执行;
NF_IP_LOCAL_OUT             在本地报文做流出路由前执行。
 
nf_hookfn *hook; hook处理回调函数。其定义为:
typedef unsigned int nf_hookfn(
unsigned int hooknum,        //hook执行点
struct sk_buff **skb, //sk buffer数据
const struct net_device *in, //输入设备
const struct net_device *out, //输出设备
int (*okfn)(struct sk_buff *) //
)
 
nf_hookfn执行后需要返回以下返回值:
NF_ACCEPT  继续正常的报文处理;
NF_DROP      将报文丢弃;
NF_STOLEN  由钩子函数处理了该报文,不要再继续传送;
NF_QUEUE    将报文入队,通常交由用户程序处理;
NF_REPEAT   再次调用该钩子函数。
 
最后一个参数为hook优先级,内核定义了以下多种优先级:
enum nf_ip_hook_priorities
{
  NF_IP_PRI_FIRST = INT_MIN,
  NF_IP_PRI_CONNTRACK = -200,
  NF_IP_PRI_MANGLE = -150,
  NF_IP_PRI_NAT_DST = -100,
  NF_IP_PRI_FILTER = 0,
  NF_IP_PRI_NAT_SRC = 100,
  NF_IP_PRI_LAST = INT_MAX,
};
 
2.      hook注册/注销
 
注册和注销函数使用起来非常简单,我们来看一下它们的函数原型:
 
单个hook注册和注销函数
int nf_register_hook(struct nf_hook_ops *reg);
void nf_unregister_hook(struct nf_hook_ops *reg);
 
多个hook注册和注销函数
int nf_register_hooks(struct nf_hook_ops *reg, unsigned int n);
void nf_unregister_hooks(struct nf_hook_ops *reg, unsigned int n);
 
3.      一个使用hook来监听主机ICMP报文的简单内核模块程序
 
#include <linux/init.h>
#include <linux/types.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/netfilter_ipv4.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/ip.h>
 
static unsigned int icmp_srv(unsigned int hook,
                                               struct sk_buff **pskb,
                                               const struct net_device *in,
                                               const struct net_device *out,
                                               int (*okfn)(struct sk_buff *)
                                               )
{
       //printk(KERN_INFO"hook_icmp::icmp_srv()/n");
       struct iphdr *iph = (*pskb)->nh.iph;
      
       if(iph->protocol == IPPROTO_ICMP)
       {
              printk(KERN_INFO"hook_icmp::icmp_srv: receive ICMP packet/n");
              printk(KERN_INFO"src: ");
       }
      
       return NF_ACCEPT;
}
 
static struct nf_hook_ops icmpsrv_ops =
{
       .hook = icmp_srv,
       .pf = PF_INET,
       .hooknum = NF_IP_PRE_ROUTING,
       .priority = NF_IP_PRI_FILTER -1,
};
 
static int __init init_hook_icmp(void)
{
       return nf_register_hook(&icmpsrv_ops);
}
 
static void __exit fini_hook_icmp(void)
{
       nf_unregister_hook(&icmpsrv_ops);
}
 
MODULE_LICENSE("GPL");
 
module_init(init_hook_icmp);
module_exit(fini_hook_icmp);
 
编译改模块之后,加载该模块,之后可以在DOS下用ping命令来测试。
linux中用dmesg查看,可以看到收到的icmp报文
hook_icmp::icmp_srv: receive ICMP packet
hook_icmp::icmp_srv: receive ICMP packet
hook_icmp::icmp_srv: receive ICMP packet
hook_icmp::icmp_srv: receive ICMP packet
 
 
1/2/2009
Linux下开发简单的可加载内核模块(2.6内核)
Linux下开发简单的可加载内核模块(2.6内核)  作者:JuKevin
 
1.       模块代码结构
 
头文件
模块宏声明
初始化函数
退出函数
入口出口函数设置
 
最简单的hello.c源文件
//头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
 
//模块宏声明
MODULE_LICENSE( "GPL" );
MODULE_DESCRIPTION( "Fortune Cookie Kernel Module" );
MODULE_AUTHOR( "M. Tim Jones" );
 
//初始化函数
static int __init mod_init_func(void)
{
       printk(KERN_EMERG "Hello, world/n");
 
       return 0;
}
 
//模块推出函数
static void __exit mod_exit_func(void)
{
       printk(KERN_EMERG "Goodbye, world/n");
}
//入口出口函数设置
module_init(mod_init_func);
module_exit(mod_exit_func);
 
 
2.       模块代码编译
 
2.6内核环境中编译模块代码和2.4有很大区别,2.6中,内核已经存在一个写好的Makefile模板,在目录/lib/modules/$(shell uname -r)/build/下。因此我们编写的Makefile中只需要指定需要编译的模块代码的名称以及当前目录即可。
 
Makefile模板
 
SRC = /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
obj-m := hello.o
 
default:
       $(MAKE) -C $( SRC) M=$(PWD) modules
 
clean:
       rm –f *.o *.ko .*.cmd .*.flags *.mod.c
 
还有一个完美点的例子,优点是它可以区分出来自己是在内核源码树里面被编译的还是在源码树之外被编译的:
 
#如果已定义KERNELRELEASE,则说明是从内核构造系统调用的,因此可利用其内建语句。
ifneq($(KERNELRELEASE),)
       obj-m :=hello.o
#否则,是直接从命令行调用的,这时要调用内核构造系统
else
       KERNELDIR ?= /lib/modules/$(shell uname -r)/build
       PWD := $(shell pwd)
default:
       $(MAKE) –C $(KERNELDIR) M=$(PWD) modules
endif
 
注意,Makefile的文件名必须是头字母大写的Makefile
 
我们在hello.c当前目录下运行make,编译成功后会生成以下文件:
hello.ko 
hello.mod.c 
hello.mod.o 
hello.o
Module.symvers
 
其中, hello.ko就是我们需要的模块执行文件,它是一个特殊的ELF文件。
我们运行objdump –t hello.ko,可以查看其symbol table.
 
3.       模块管理命令
 
Linux提供了命令来管理模块
(1)    insmod 安装模块
(2)    rmmod 卸载模块
(3)    lsmod  查看已加载模块
 
我们首先使用insmod加载模块:
insmod hello.ko
这是可以看到输出 Hello, world
如果在控制台中看不到,我们可以使用命令dmesg来查看系统输出
然后再卸载模块
rmmod hello
这是可以看到输出 Goodbye, world.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值