在 linux學習 2.4 版以後版本的內核中,幾乎全部的中斷過程與用戶態進程的通信都是使用 netlink 套接字實現的,同時還使用 netlink 實現了 ip queue 工具,但 ip queue 的使用有其侷限性,不能自由地用於各種中斷過程。內核的幫助文檔和其他一些 linux學習 相關文章都沒有對 netlink 套接字在中斷過程和用戶空間通信的應用上作詳細的說明,使得很多用戶對此只有一個模糊的概念。
Unicast Communication between Kernel and Application
在下面的例子中,一個用戶空間進程發送一個netlink消息給內核模塊,內核模塊應答一個消息給發送進程,這裏是用戶空間的代碼:
#include <linux學習/netlink.h>
#define MAX_PAYLOAD 1024 /* maximum payload size*/
struct sockaddr_nl src_addr, dest_addr;
struct msghdr msg;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
int sock_fd;
void main() ...{
sock_fd = socket(PF_NETLINK, SOCK_RAW,NETLINK_TEST);
memset(&src_addr, 0, sizeof(src_addr));
src__addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid(); /**//* self pid */
src_addr.nl_groups = 0; /**//* not in mcast groups */
bind(sock_fd, (struct sockaddr*)&src_addr,
sizeof(src_addr));
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; /**//* For Linux Kernel */
dest_addr.nl_groups = 0; /**//* unicast */
nlh=(struct nlmsghdr *)malloc(
NLMSG_SPACE(MAX_PAYLOAD));
/**//* Fill the netlink message header */
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = getpid(); /**//* self pid */
nlh->nlmsg_flags = 0;
/**//* Fill in the netlink message payload */
strcpy(NLMSG_DATA(nlh), "Hello you!");
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
sendmsg(fd, &msg, 0);
/**//* Read message from kernel */
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
recvmsg(fd, &msg, 0);
printf(" Received message payload: %s ",
NLMSG_DATA(nlh));
/**//* Close Netlink Socket */
close(sock_fd);
}
這裏是內核代碼:
void nl_data_ready (struct sock *sk, int len)
...{
wake_up_interruptible(sk->sleep);
}
void netlink_test() ...{
struct sk_buff *skb = NULL;
struct nlmsghdr *nlh = NULL;
int err;
u32 pid;
nl_sk = netlink_kernel_create(NETLINK_TEST,
nl_data_ready);
/**//* wait for message coming down from user-space */
skb = skb_recv_datagram(nl_sk, 0, 0, &err);
nlh = (struct nlmsghdr *)skb->data;
printk("%s: received netlink message payload:%s ",
__FUNCTION__, NLMSG_DATA(nlh));
pid = nlh->nlmsg_pid; /**//*pid of sending process */
NETLINK_CB(skb).groups = 0; /**//* not in mcast group */
NETLINK_CB(skb).pid = 0; /**//* from kernel */
NETLINK_CB(skb).dst_pid = pid;
NETLINK_CB(skb).dst_groups = 0; /**//* unicast */
netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
sock_release(nl_sk->socket);
}
在內核模塊被加載到內核,當我們運行用戶程序,我們將看到下面的信息:
Received message payload: Hello you!
然後用dmesg我們可以看到內核輸出:
netlink_test: received netlink message payload:
Hello you!
Multicast Communication between Kernel and Applications
這個例子中,兩個應用程序在監聽同一個netlink廣播組.內核模塊發送一個netlink消息給這個廣播組,所用的應用程序都收到它,如下是用戶程序代碼:
#include <linux學習/netlink.h>
#define MAX_PAYLOAD 1024 /* maximum payload size*/
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
int sock_fd;
void main() ...{
sock_fd=socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
memset(&src_addr, 0, sizeof(local_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid(); /**//* self pid */
/**//* interested in group 1<<0 */
src_addr.nl_groups = 1;
bind(sock_fd, (struct sockaddr*)&src_addr,
sizeof(src_addr));
memset(&dest_addr, 0, sizeof(dest_addr));
nlh = (struct nlmsghdr *)malloc(
NLMSG_SPACE(MAX_PAYLOAD));
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
iov.iov_base = (void *)nlh;
iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
printf("Waiting for message from kernel ");
/**//* Read message from kernel */
recvmsg(fd, &msg, 0);
printf(" Received message payload: %s ",
NLMSG_DATA(nlh));
close(sock_fd);
}
內核代碼:
struct sock *nl_sk = NULL;
void netlink_test() ...{
sturct sk_buff *skb = NULL;
struct nlmsghdr *nlh;
int err;
nl_sk = netlink_kernel_create(NETLINK_TEST,
nl_data_ready);
skb=alloc_skb(NLMSG_SPACE(MAX_PAYLOAD),GFP_KERNEL);
nlh = (struct nlmsghdr *)skb->data;
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = 0; /**//* from kernel */
nlh->nlmsg_flags = 0;
strcpy(NLMSG_DATA(nlh), "Greeting from kernel!");
/**//* sender is in group 1<<0 */
NETLINK_CB(skb).groups = 1;
NETLINK_CB(skb).pid = 0; /**//* from kernel */
NETLINK_CB(skb).dst_pid = 0; /**//* multicast */
/**//* to mcast group 1<<0 */
NETLINK_CB(skb).dst_groups = 1;
/**//*multicast the message to all listening processes*/
netlink_broadcast(nl_sk, skb, 0, 1, GFP_KERNEL);
sock_release(nl_sk->socket);
}
我們運行用戶程序:
./nl_recv &
Waiting for message from kernel
./nl_recv &
Waiting for message from kernel
然後我們加載內核模塊到內核空間,會看到如下信息::
Received message payload: Greeting from kernel!
Received message payload: Greeting from kernel!
以下是一個簡單的測試內核事件的應用程序:
struct sockaddr_nl src_addr, dest_addr;
char *KernelMsg = NULL;
struct iovec iov;
int sock_fd;
struct msghdr msg;
int msglen;
#define DEVICE_ADD "add"
#define DEVICE_REMOVE "remove"
#define DEVICE_NAME "event0"
#define DEVICE_NAMELEN 6
void * DeviceManagement(void *arg)
...{
sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
int msgle;
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = pthread_self() << 16 | getpid();
src_addr.nl_groups = 1;
bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr));
memset(&dest_addr, 0, sizeof(dest_addr));
KernelMsg = (struct nlmsghdr *)malloc(MAX_PAYLOAD);
memset(KernelMsg, 0, MAX_PAYLOAD);
iov.iov_base = (void *)KernelMsg;
iov.iov_len = MAX_PAYLOAD;
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
while(1) ...{
//printf("Waiting for message from kernel ");
recvmsg(sock_fd, &msg, 0);
//printf("Receved message payload: %s ", KernelMsg);
msglen = strlen(KernelMsg);
//printf("Device: %s ", KernelMsg+msglen-DEVICE_NAMELEN);
if(!strncmp(DEVICE_NAME, KernelMsg+msglen-DEVICE_NAMELEN, DEVICE_NAMELEN)) ...{
if(!strncmp(DEVICE_ADD, KernelMsg, strlen(DEVICE_ADD)))
...{
printf("Add event0 device ");
USBKeyboardReady = 1;
}
else if(!strncmp(DEVICE_REMOVE, KernelMsg, strlen(DEVICE_REMOVE))) ...{
printf("Remove event0 device ");
USBKeyboardReady = 0;
}
}
}
close(sock_fd);
}
示例:一個用戶進程發送數據到內核,然後通過內核發送給另一個用戶進程。
內核進程:netlink-exam-kern.c
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux學習/config.h>
#include <linux學習/module.h>
#include <linux學習/netlink.h>
#include <linux學習/sched.h>
#include <net/sock.h>
#include <linux學習/proc_fs.h>
#define BUF_SIZE 16384
#define NL 30
static struct sock *netlink_exam_sock;
static unsigned char buffer[BUF_SIZE];
static unsigned int buffer_tail = 0;
static int exit_flag = 0;
static DECLARE_COMPLETION(exit_completion);
static void recv_handler(struct sock * sk, int length)
...{
wake_up(sk->sk_sleep);
}
static int process_message_thread(void * data)
...{
struct sk_buff * skb = NULL;
struct nlmsghdr * nlhdr = NULL;
int len;
DEFINE_WAIT(wait);
daemonize("mynetlink");
while (exit_flag == 0) ...{
prepare_to_wait(netlink_exam_sock->sk_sleep, &wait, TASK_INTERRUPTIBLE);
schedule();
finish_wait(netlink_exam_sock->sk_sleep, &wait);
while ((skb = skb_dequeue(&netlink_exam_sock->sk_receive_queue))
!= NULL) ...{
nlhdr = (struct nlmsghdr *)skb->data;
if (nlhdr->nlmsg_len < sizeof(struct nlmsghdr)) ...{
printk("Corrupt netlink message. ");
continue;
}
len = nlhdr->nlmsg_len - NLMSG_LENGTH(0);
if (len + buffer_tail > BUF_SIZE) ...{
printk("netlink buffer is full. ");
}
else ...{
memcpy(buffer + buffer_tail, NLMSG_DATA(nlhdr), len);
buffer_tail += len;
}
nlhdr->nlmsg_pid = 0;
nlhdr->nlmsg_flags = 0;
NETLINK_CB(skb).pid = 0;
NETLINK_CB(skb).dst_pid = 0;
NETLINK_CB(skb).dst_group = 1;
netlink_broadcast(netlink_exam_sock, skb, 0, 1, GFP_KERNEL);
}
}
complete(&exit_completion);
return 0;
}
static int netlink_exam_readproc(char *page, char **start, off_t off,
int count, int *eof, void *data)
...{
int len;
if (off >= buffer_tail) ...{
* eof = 1;
return 0;
}
else ...{
len = count;
if (count > PAGE_SIZE) ...{
len = PAGE_SIZE;
}
if (len > buffer_tail - off) ...{
len = buffer_tail - off;
}
memcpy(page, buffer + off, len);
*start = page;
return len;
}
}
static int __init netlink_exam_init(void)
...{
netlink_exam_sock = netlink_kernel_create(NL, 0, recv_handler, THIS_MODULE);
if (!netlink_exam_sock) ...{
printk("Fail to create netlink socket. ");
return 1;
}
kernel_thread(process_message_thread, NULL, CLONE_KERNEL);
create_proc_read_entry("netlink_exam_buffer", 0444, NULL, netlink_exam_readproc, 0);
return 0;
}
static void __exit netlink_exam_exit(void)
...{
exit_flag = 1;
wake_up(netlink_exam_sock->sk_sleep);
wait_for_completion(&exit_completion);
sock_release(netlink_exam_sock->sk_socket);
}
module_init(netlink_exam_init);
module_exit(netlink_exam_exit);
MODULE_LICENSE("GPL");
編譯成模塊:
debug-objs := netlink-exam-kern.o
obj-m := netlink-exam-kern1.o
CFLAGS += -w -Wimplicit-function-declaration
else
PWD := $(shell pwd)
KVER ?= $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
endif
用戶發送進程:netlink-exam-user-send.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux學習/netlink.h>
#define MAX_MSGSIZE 1024
int main(int argc, char * argv[])
...{
FILE * fp;
struct sockaddr_nl saddr, daddr;
struct nlmsghdr *nlhdr = NULL;
struct msghdr msg;
struct iovec iov;
int sd;
char text_line[MAX_MSGSIZE];
int ret = -1;
if (argc < 2) ...{
printf("Usage: %s atextfilename ", argv[0]);
exit(1);
}
if ((fp = fopen(argv[1], "r")) == NULL) ...{
printf("File %s dosen't exist. ");
exit(1);
}
sd = socket(AF_NETLINK, SOCK_RAW, 30);
memset(&saddr, 0, sizeof(saddr));
memset(&daddr, 0, sizeof(daddr));
saddr.nl_family = AF_NETLINK;
saddr.nl_pid = getpid();
saddr.nl_groups = 0;
bind(sd, (struct sockaddr*)&saddr, sizeof(saddr));
daddr.nl_family = AF_NETLINK;
daddr.nl_pid = 0;
daddr.nl_groups = 0;
nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));
while (fgets(text_line, MAX_MSGSIZE, fp)) ...{
memcpy(NLMSG_DATA(nlhdr), text_line, strlen(text_line));
memset(&msg, 0 ,sizeof(struct msghdr));
nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(text_line));
nlhdr->nlmsg_pid = getpid(); /**//* self pid */
nlhdr->nlmsg_flags = 0;
iov.iov_base = (void *)nlhdr;
iov.iov_len = nlhdr->nlmsg_len;
msg.msg_name = (void *)&daddr;
msg.msg_namelen = sizeof(daddr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
ret = sendmsg(sd, &msg, 0);
if (ret == -1) ...{
perror("sendmsg error:");
}
}
close(sd);
return 0;
}
用戶接收進程:netlink-exam-user-recv.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux學習/netlink.h>
#define MAX_MSGSIZE 1024
int main(int argc, char * argv[])
...{
FILE * fp;
struct sockaddr_nl saddr, daddr;
struct nlmsghdr *nlhdr = NULL;
struct msghdr msg;
struct iovec iov;
int sd;
char text_line[MAX_MSGSIZE];
int ret = -1;
if (argc < 2) ...{
printf("Usage: %s atextfilename ", argv[0]);
exit(1);
}
if ((fp = fopen(argv[1], "r")) == NULL) ...{
printf("File %s dosen't exist. ");
exit(1);
}
sd = socket(AF_NETLINK, SOCK_RAW, 30);
memset(&saddr, 0, sizeof(saddr));
memset(&daddr, 0, sizeof(daddr));
saddr.nl_family = AF_NETLINK;
saddr.nl_pid = getpid();
saddr.nl_groups = 0;
bind(sd, (struct sockaddr*)&saddr, sizeof(saddr));
daddr.nl_family = AF_NETLINK;
daddr.nl_pid = 0;
daddr.nl_groups = 0;
nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));
while (fgets(text_line, MAX_MSGSIZE, fp)) ...{
memcpy(NLMSG_DATA(nlhdr), text_line, strlen(text_line));
memset(&msg, 0 ,sizeof(struct msghdr));
nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(text_line));
nlhdr->nlmsg_pid = getpid(); /**//* self pid */
nlhdr->nlmsg_flags = 0;
iov.iov_base = (void *)nlhdr;
iov.iov_len = nlhdr->nlmsg_len;
msg.msg_name = (void *)&daddr;
msg.msg_namelen = sizeof(daddr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
ret = sendmsg(sd, &msg, 0);
if (ret == -1) ...{
perror("sendmsg error:");
}
}
close(sd);
return 0;
}