1. 程式人生 > >netlink socket(linux核心模組與使用者態之間通訊例項)

netlink socket(linux核心模組與使用者態之間通訊例項)

本文通過一個程式設計例項來更深入地瞭解netlink。

1.1        實現內容

1.          功能

Ø  實現一個併發的echo伺服器程式,它(接收端)將接收到字串進行轉換處理後,返回給傳送端;允許有多個傳送端同時存在;

Ø  字串的處理包括:直接返回、全部轉換為大寫、全部轉換為小寫;處理方式應可以配置,配置方式包括全域性(預設)及每“傳送-接收對(xmit/recv peer)”的。配置轉換時,不應該影響正在處理的字串;

Ø  為模擬處理過程的延時,接收端中每個字元轉換新增200ms的延遲;

Ø  接收端中需統計其處理字元的數量,並且給外部提供命令形式的查詢手段;

Ø  具備必要的例外處理,如記憶體不足等、client未關閉連結即意外退出等;  

1.2        驗收成果

1.        程式結構(應用專業方向)

Ø  以有連結的client-server方式實現echo程式  

Ø  存在多個client,client與server在同一個系統中,它們之間採用domain socket或netlink socket相連;client之間沒有關聯

Ø  client是一個程式的多個例項,而server只允許執行一個例項  

2.        程式結構(核心專業方向)

Ø  核心模組作為接收端(伺服器),而傳送端(客戶端)是使用者空間的應用程式;

Ø  核心模組可動態載入與解除安裝

Ø  驅動相關組:

·          建立虛擬字元型裝置,用於接受來自客戶端的字元

·          使用ioctl控制介面來配置echo處理方式,ioctl的cmd自定義;

·          併發使用驅動的多例項來模擬;

Ø  非驅動相關組:

·          建立核心執行緒,作為核心中的server,用於,通訊可採用netlink socket或其它可用的方式;

·          使用netlink介面來配置echo處理轉換方式;

使用者態netlink socket原始碼

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <linux/netlink.h>
#include <signal.h>

#define NETLINK_TEST    17
#define MSG_LEN         125
#define BUF_LEN         125 
#define TIME            210

int skfd;
struct sockaddr_nl local;
struct sockaddr_nl dest;
struct nlmsghdr *message;

struct msg_to_kernel
{
    struct nlmsghdr hdr;
    char data[MSG_LEN];
};
struct u_packet_info
{
    struct nlmsghdr hdr;
    char msg[MSG_LEN];
};

static void sig_pipe(int sign)
{
    printf("Catch a SIGPIPE signal\n");
    close(skfd);
    kill(local.nl_pid,SIGUSR1);
    exit(-1);
}

int init_netlink(void)
{
    //char *send_data = "aaaaaaaaaaaaBBBBBBBBBBBBBBBBBBaaaaaaaaaaaaaaaaBBBBBBBBBBBB11111112222";
    char send_data[BUF_LEN];
    message = (struct nlmsghdr *)malloc(1);       
    skfd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
    
    if(skfd < 0){
        printf("can not create a netlink socket\n");
        return -1;
    }    
    
    memset(&local, 0, sizeof(local));
    local.nl_family = AF_NETLINK;
    local.nl_pid = getpid();    
    local.nl_groups = 0;
    if(bind(skfd, (struct sockaddr *)&local, sizeof(local)) != 0){
        printf("bind() error\n");
        return -1;
    }
    memset(&dest, 0, sizeof(dest));
    dest.nl_family = AF_NETLINK;
    dest.nl_pid = 0;
    dest.nl_groups = 0;

    memset(message, '\0', sizeof(struct nlmsghdr));
    message->nlmsg_len = NLMSG_SPACE(MSG_LEN);
    message->nlmsg_flags = 0;
    message->nlmsg_type = 0;
    message->nlmsg_seq = 0;
    message->nlmsg_pid = local.nl_pid;    
    
    while(1) {
        printf("input  the  data: ");
        fgets(send_data, MSG_LEN, stdin);
        if(0 == (strlen(send_data)-1)) 
            continue;
        else
            break;
    }
    memcpy(NLMSG_DATA(message), send_data, strlen(send_data)-1);
    printf("send  to  kernel: %s,  send_id: %d   send_len: %d\n", \
        (char *)NLMSG_DATA(message),local.nl_pid, strlen(send_data)-1);
    return 0;
}

/**
 * NAME: ngsa_test_init 
 *
 * DESCRIPTION:
 *      ngsa test model 初始化
 * @*psdhdr   
 * @*addr     
 * @size      
 * 
 * RETURN: 
 */
int main(int argc, char* argv[]) 
{    
    int ret,len; 
    socklen_t destlen = sizeof(struct sockaddr_nl);    
    struct u_packet_info info; 
    fd_set fd_sets;
    struct timeval select_time;
    
    signal(SIGINT, sig_pipe);
    signal(SIGINT, sig_pipe);
    
    ret = init_netlink();
    if(ret<0) {
        close(skfd);
        perror("netlink failure!");
        exit(-1);
    }
    
    FD_ZERO( &fd_sets );
    FD_SET( skfd, &fd_sets);
    
    len = sendto(skfd, message, message->nlmsg_len, 0,(struct sockaddr *)&dest, sizeof(dest));
    if(!len){
        perror("send pid:");
        exit(-1);
    }

    select_time.tv_sec= TIME;
    select_time.tv_usec = 0;
    ret = select(skfd+1,&fd_sets,NULL,NULL,&select_time);

    if(ret > 0){
        /* 接受核心態確認資訊 */        
        len = recvfrom(skfd, &info, sizeof(struct u_packet_info),0, (struct sockaddr*)&dest, &destlen);        
        printf("recv from kernel: %s,  recv_len: %d\n",(char *)info.msg, strlen(info.msg));
    }else if(ret < 0) {
        perror("\n error! \n");
        exit(-1);
    }else {
        printf("\n kernel server disconncet! \n");
        kill(local.nl_pid, SIGUSR1);
    }
        
    /* 核心和使用者關閉通訊 */
    close(skfd);
    return 0;
}

#########################################################################

linux核心原始碼(module.c)

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>

#define MEMDEV_MAJOR    255   /* 預設的mem的主裝置號 */
#define MEMDEV_NR_DEVS  1     /* 裝置數 */
#define MEMDEV_SIZE     1024  /* 分配記憶體的大小 */
#define NETLINK_TEST    17
#define UP_TO_LOW       0
#define LOW_TO_UP       1
#define MAX_PID_COUNT   100
#define MSG_LEN         125 

#ifndef SLEEP_MILLI_SEC
#define SLEEP_MILLI_SEC(nMillisec) \
    do { \
        long timeout = (nMillisec) * HZ /1000; \
        while (timeout > 0) { \
            timeout = schedule_timeout(timeout); \
        } \
    }while(0);
#endif

static int echo_major = MEMDEV_MAJOR;
module_param(echo_major, int, S_IRUGO);
struct echo_dev *echo_devp;                   /*裝置結構體指標*/
struct cdev cdev;
char *echo_dev = "echodev";
static struct sock *netlinkfd = NULL;
static struct task_struct *task_test[MAX_PID_COUNT];
static int pid_index = 0;
static int char_num = 0;
static int char_cnvt_flag = 0;

/* mem裝置描述結構體 */
struct echo_dev                                     
{                                                        
  char *data;          /* 分配到的記憶體的起始地址 */                    
  unsigned long size;  /* 記憶體的大小 */
};

struct{
    __u32 pid;
}user_process;


/* netlink */
struct echo_netlink                                     
{  
  __u32 pid;            /* netlink pid */
  char  buf[MSG_LEN];   /* data */  
  int   length;         /* buf len  */
};

struct echo_netlink client_netlink[MAX_PID_COUNT];

static int echo_open(struct inode *inode, struct file *filp);
static ssize_t echo_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos);
static long echo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);

static const struct file_operations echo_fops =
{
    .owner = THIS_MODULE,
    .open = echo_open,
    .read = echo_read,
    .unlocked_ioctl = echo_ioctl,
};

static int echo_open(struct inode *inode, struct file *filp)
{
    /*獲取次裝置號*/
    printk(KERN_DEBUG"[kernel space] open char device!!\n");
    return 0;
}

static ssize_t echo_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
    printk(KERN_DEBUG"[kernel space] test_netlink_exit!!\n");
    return char_num;
}

static long echo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int result = 0;
    
    switch(cmd) {
        case UP_TO_LOW:
            char_cnvt_flag = 0;
            break;
        case LOW_TO_UP:
            char_cnvt_flag = 1;
            break;
        default       :
            result = -1;
            break;
    }
    printk(KERN_DEBUG"[kernel space] ioctl cmd: %d\n",char_cnvt_flag);
    return result;
}

int init_char_device(void)
{
    int i,result;
    dev_t devno = MKDEV(echo_major, 0);
        
    if (echo_major)
        /* 靜態申請裝置號*/
        result = register_chrdev_region(devno, 2, "echodev");
    else {
        /* 動態分配裝置號 */
        result = alloc_chrdev_region(&devno, 0, 2, "echodev");
        echo_major = MAJOR(devno);
    }

    if ( result<0 )
        return result;

    /* 初始化cdev結構 */
    cdev_init(&cdev, &echo_fops);
    cdev.owner = THIS_MODULE;
    cdev.ops = &echo_fops;

    /* 註冊字元裝置 */    
    cdev_add(&cdev, MKDEV(echo_major, 0), MEMDEV_NR_DEVS);

    /* 為裝置描述結構分配記憶體 */
    echo_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct echo_dev), GFP_KERNEL);

    /* 申請失敗 */
    if (!echo_devp)
    {
        result = -1;
        goto fail_malloc;
    }

    memset(echo_devp, 0, sizeof(struct echo_dev));

    /* 為裝置分配記憶體 */
    for(i= 0; i < MEMDEV_NR_DEVS; i++) {
        echo_devp[i].size = MEMDEV_SIZE;
        echo_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
        memset(echo_devp[i].data, 0, MEMDEV_SIZE);
    }
    
    printk(KERN_ERR"[kernel space] create char device successfuly!\n");
    return 0;
    
    fail_malloc: 
        unregister_chrdev_region(devno, 1);
    return result;
}

void delete_device(void)
{
    /* 登出裝置 */
    cdev_del(&cdev);
    
    /* 釋放裝置號 */
    unregister_chrdev_region(MKDEV(echo_major, 0), 2);
    printk(KERN_DEBUG"[kernel space] echo_cdev_del!!\n");
}

static int kernel_send_thread(void *index)

    int threadindex = *((int *)index);
    int size;
    struct sk_buff *skb;
    unsigned char *old_tail;
    struct nlmsghdr *nlh;  //報文頭    
    int retval;     
    int i=0;     
    
    size = NLMSG_SPACE(client_netlink[threadindex].length);

    /* 分配一個新的套接字快取,使用GFP_ATOMIC標誌程序不會被置為睡眠 */
    skb = alloc_skb(size, GFP_ATOMIC); 

    /* 初始化一個netlink訊息首部 */
    nlh = nlmsg_put(skb, 0, 0, 0, NLMSG_SPACE(client_netlink[threadindex].length)-sizeof(struct nlmsghdr), 0);
    old_tail = skb->tail;    

    //memcpy(NLMSG_DATA(nlh), client_netlink[i].buf, client_netlink[i].length);  //填充資料區
    strcpy(NLMSG_DATA(nlh), client_netlink[threadindex].buf);  //填充資料區
    nlh->nlmsg_len = skb->tail - old_tail;  //設定訊息長度
    
    /* 設定控制欄位 */
    NETLINK_CB(skb).pid = 0;
    NETLINK_CB(skb).dst_group = 0;

    printk(KERN_DEBUG "[kernel space] send  to  user: %s,  send_pid: %d,  send_len: %d\n", \
        (char *)NLMSG_DATA((struct nlmsghdr *)skb->data), client_netlink[threadindex].pid, \
        client_netlink[threadindex].length);    

    /* 傳送資料 */
    retval = netlink_unicast(netlinkfd, skb, client_netlink[threadindex].pid, MSG_DONTWAIT);

    if (retval<0) {
        printk(KERN_DEBUG "[kernel space] client closed: \n");        
    }
    
    while(!(i = kthread_should_stop())) {
        printk(KERN_DEBUG "[kernel space] kthread_should_stop: %d\n", i);
        SLEEP_MILLI_SEC(1000*10);             
    }
    
    return 0;
}

void char_convert(int id) 
{    
    int len = client_netlink[id].length;
    int i = 0;

    client_netlink[id].buf[len] = '\0';
    if( UP_TO_LOW == char_cnvt_flag ) {
        printk(KERN_DEBUG "[kernel space] UP_TO_LOW\n");
        while(client_netlink[id].buf[i] != '\0') {           
            if(client_netlink[id].buf[i] >= 'A' && client_netlink[id].buf[i] <= 'Z') {
                client_netlink[id].buf[i] = client_netlink[id].buf[i] + 0x20;
                mdelay(200);
            }            
            i++;
        }
    }
    else if( LOW_TO_UP == char_cnvt_flag ) {
        printk(KERN_DEBUG "[kernel space] LOW_TO_UP\n");
        while(client_netlink[id].buf[i] != '\0') {            
            if(client_netlink[id].buf[i]  >= 'a' && client_netlink[id].buf[i]  <= 'z') {
                client_netlink[id].buf[i] = client_netlink[id].buf[i]  - 0x20;
                mdelay(200);
            }            
            i++;
        }
    }    
    char_num += len;    
}

void run_netlink_thread(int thread_index)
{
    int err;
    char process_name[64] = {0};
 
    void* data = kmalloc(sizeof(int), GFP_ATOMIC);
    *(int *)data = thread_index;
    snprintf(process_name, 63, "child_thread-%d", thread_index);

    task_test[thread_index] = kthread_create(kernel_send_thread, data, process_name);

    if(IS_ERR(task_test[thread_index])) {
        err = PTR_ERR(task_test[thread_index]);
        printk(KERN_DEBUG "[kernel space] creat child thread failure \n");
    } else {
        printk(KERN_DEBUG "[kernel space] creat child_thread-%d \n", thread_index);    
        wake_up_process(task_test[thread_index]);
    }      
}

void buf_deal(int id)
{  
    char_convert(id);
    
    /* 喚醒執行緒 */
    run_netlink_thread(id);     
}

void kernel_recv_thread(struct sk_buff *__skb)
{
    struct sk_buff *skb;
    struct nlmsghdr *nlh = NULL;
    char *recv_data = NULL;
    int pid_id = 0;
    printk(KERN_DEBUG "[kernel space] begin kernel_recv\n");
    skb = skb_get(__skb);

    if(skb->len >= NLMSG_SPACE(0)) {
        nlh = nlmsg_hdr(skb);

        if(pid_index < MAX_PID_COUNT) {
            client_netlink[pid_index].pid = nlh->nlmsg_pid;
            recv_data = NLMSG_DATA(nlh);            
            strcpy(client_netlink[pid_index].buf,recv_data);
            client_netlink[pid_index].length = strlen(recv_data);
            printk(KERN_DEBUG "[kernel space] recv from user: %s,  recv_pid: %d,  recv_len: %d\n", \
            (char *)NLMSG_DATA(nlh), client_netlink[pid_index].pid, strlen(recv_data));            
            pid_id = pid_index;
            pid_index++;
            buf_deal(pid_id);
        } else {
            printk(KERN_DEBUG "[kernel space] out of pid\n");
        }
        kfree_skb(skb);
    }   
}

int init_netlink(void)
{    
    netlinkfd = netlink_kernel_create(&init_net,NETLINK_TEST,0,kernel_recv_thread,NULL,THIS_MODULE);
    if(!netlinkfd )
        return -1;
    else {
        printk(KERN_ERR"[kernel space] create netlink successfuly!\n");
        return 0;
    }        
}

void netlink_release(void)
{
    printk(KERN_DEBUG"[kernel space] echo_netlink_exit!\n");
    if(netlinkfd != NULL)
        sock_release(netlinkfd->sk_socket); 
}

void stop_kthread(void)
{
    int i;
    printk(KERN_ERR"[kernel space] stop kthread!\n");
    for(i=0; i != pid_index; i++) {
        if(task_test[i] != NULL) {            
            kthread_stop(task_test[i]);            
            task_test[i] = NULL;
        }
    }    
}

void  init_client(void)
{
    int i = 0;
    for(i=0; i<MAX_PID_COUNT; i++) {        
        client_netlink[i].pid = 0;        
        task_test[i] = NULL;
    }
}

/**
 * NAME: init_echo_module 
 *
 * DESCRIPTION:
 *      模組載入函式
 * @*psdhdr   
 * @*addr     
 * @size      
 * 
 * RETURN: 
 */
int __init init_echo_module(void)
{
    int result = 0;

    init_client();   

    result = init_char_device();
    if ( result<0 ) {
        printk(KERN_ERR"[kernel space] cannot create a netlinksocket!\n");
        return result;
    }
    
    result = init_netlink();
    if ( result<0 ) {
        printk(KERN_ERR"[kernel space] cannot create a netlinksocket!\n");
        return result;
    }        
    
    return result;
}

/**
 * NAME: exit_echo_module 
 *
 * DESCRIPTION:
 *      模組解除安裝函式
 * @*psdhdr   
 * @*addr     
 * @size      
 * 
 * RETURN: 
 */
void __exit exit_echo_module(void)
{    
    netlink_release();
    stop_kthread();
    delete_device();    
}

module_init(init_echo_module);
module_exit(exit_echo_module);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhang");

MODULE_VERSION("V1.0");

#####################使用者態檔案操作原始碼####################

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/fs.h>
#include <stdlib.h>  
#include <string.h> 
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>

#define MAX_SIZE 1024
char buf[MAX_SIZE]; //緩衝區
char *dir = "/dev/echodev";
int fd = 0;

static void stop(int sign)
{
    printf("Catch a signal\n");
    close(fd);
    exit(0);
}

int main()
{    
    int arg = 0;
    int cmd = 0;    
    int ret = 0;
    int len = 0;
    
    /*開啟裝置檔案*/
    fd = open(dir, O_RDWR | O_NONBLOCK);

    if(fd == -1) {
        printf("open failure: %s\n",dir);
        close(fd);
        return -1;
    }    
    signal(SIGINT, stop);
    signal(SIGTSTP, stop);
    char opt[50];
    while(1){
        printf("please input operation (read/ioctl): ");
        scanf("%s",opt);
        
        if(0 == strcmp(opt,"read")) {
            /* 讀取資料 */
            len = read(fd, buf, sizeof(buf));
            printf(": %d\n", len);
        } else if(0 == strcmp(opt,"ioctl")) {
            printf("select 0(A-a)/1(a-A): ");
            while(scanf("%d",&cmd) != EOF) {
                ret = ioctl(fd,cmd,&arg);
                if (ret<0)                    
                    continue;
                else
                    break;
            }
        }else {
            printf("input error!\n");
            continue;
        }
    } 
    
    /* 關閉裝置 */
    close(fd);
    return 0;

}

linux核心模組具體載入流程,詳見:

linux核心模組Makefile編寫格式,詳見:

gcc的Makefile編寫格式,詳見: