一、netlink 通信的优点
使用netlink方法而不是系统调用、ioctls或proc文件系统来实现用户程序和内核程序之间的通信,有以下几个原因:
-
灵活性和扩展性:netlink提供了一种更灵活和可扩展的机制来进行用户程序和内核程序之间的通信。通过使用netlink套接字,可以定义自定义的消息格式和协议,以满足特定的通信需求。这使得添加新的特性或修改通信协议更加容易。
-
异步通信:netlink支持异步通信,允许用户程序和内核程序之间进行非阻塞的消息传递。这对于需要进行长时间或复杂操作的通信非常有用,避免了在系统调用或ioctls中可能出现的阻塞情况。
-
多路复用:netlink套接字可以与其他套接字一起使用,通过使用select、poll或epoll等多路复用机制,可以同时处理多个通信通道。这样可以更好地管理和处理多个用户程序和内核程序之间的通信。
相比之下,添加新的系统调用、ioctls或proc文件系统来实现通信可能更加困难:
-
系统调用:添加新的系统调用需要修改内核源代码,并进行编译和重新部署内核。这需要对内核的深入了解,并且可能会引入潜在的兼容性和稳定性问题。
-
ioctls:ioctls是一种用于设备驱动程序的特定控制操作。如果要将其用于用户程序和内核程序之间的通信,需要修改相应的设备驱动程序,并定义新的ioctls命令。这可能需要对设备驱动程序和内核进行修改,并且可能不适用于所有类型的通信需求。
-
proc文件系统:proc文件系统提供了一种通过文件系统接口与内核进行通信的机制。但是,proc文件系统主要用于获取内核状态和信息,并不适合进行实时的双向通信。添加新的文件和接口可能需要修改内核源代码,并且可能会引入复杂性和风险。
综上所述,使用netlink方法可以提供更灵活、可扩展和异步的用户程序和内核程序通信机制,避免了修改内核和引入潜在问题的困难。这使得netlink成为了在Linux系统中广泛使用的通信机制之一。
二、为什么不用其他几种上层和内核层通信方式
为什么以上的功能在实现用户程序和内核程序通讯时,都使用netlink方法而不是系统调用,ioctls
或者proc文件系统呢?原因在于:为新的特性添加一个新的系统调用,ioctls或者一个proc文件的做法并不是很容易的一件事情,因为我们要冒着污染内核代码并且可能破坏系统稳定性的风险去完成这件事情。
然而,netlink socket却是如此的简单,你只需要在文件netlink.h中添加一个常量来标识你的协议类型,然后,内核模块和用户程序就可以立刻使用socket风格的API进行通讯了!
Netlink提供了一种异步通讯方式,与其他socket API一样,它提供了一个socket队列来缓冲或者平滑
瞬时的消息高峰。发送netlink消息的系统调用在把消息加入到接收者的消息对列后,会触发接收者的接收处理函数。接收者在接收处理函数上下文中,可以决定立即处理消息还是把消息放在队列中,在以后其它上下文去处理它(因为我们希望接收处理函数执行的尽可能快)。系统调用与netlink不同,它需要一个同步的处理,因此,当我们使用一个系统调用来从用户态传递消息到内核时,如果处理这个消息的时间很长的话,内核调度的粒度就会受到影响。
内核中实现系统调用的代码都是在编译时静态链接到内核的,因此,在动态加载模块中去包含一个系统调用的做法是不合适的,那是大多数设备驱动的做法。使用netlink socket时,动态加载模块中的netlink程序不会和linux内核中的netlink部分产生任何编译时依赖关系。
Netlink优于系统调用,ioctls和proc文件系统的另外一个特点就是它支持多点传送。一个进程可以把消息传输给一个netlink组地址,然后任意多个进程都可以监听那个组地址(并且接收消息)。这种机制为内核到用户态的事件分发提供了一种近乎完美的解决方案。
系统调用和ioctl都属于单工方式的IPC,也就是说,这种IPC会话的发起者只能是用户态程序。但是,如果内核有一个紧急的消息想要通知给用户态程序时,该怎么办呢?如果直接使用这些IPC的话,是没办法做到这点的。通常情况下,应用程序会周期性的轮询内核以获取状态的改变,然而,高频度的轮询势必会增加系统的负载。Netlink 通过允许内核初始化会话的方式完美的解决了此问题,我们称之为netlink socket的双工特性。
最后,netlink socket提供了一组开发者熟悉的BSD风格的API函数,因此,相对于使用神秘的系统调用API或者ioctl而言,netlink开发培训的费用会更低些。
三、代码示例
上层代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/genetlink.h>
#include <fcntl.h>
#include <errno.h>#define SIGUSR3 40
#define NETLINK_USER 31
#define MY_FAMILY 17
#define MAX_PAYLOAD 1024int nos_notify_kernel(int data)
{
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh;
struct iovec iov;
struct msghdr msg;
int sock_fd, ret;// 创建 Netlink socket
sock_fd = socket(AF_NETLINK, SOCK_RAW, 17);
if (sock_fd < 0) {
perror("socket");
exit(1);
}memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid(); // 设置源地址为当前进程的 PID// 绑定源地址到 socket
ret = bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr));
if (ret < 0) {
perror("bind");
close(sock_fd);
exit(1);
}memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // 发送到内核// 创建 Netlink 消息头部
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = data; // 设置消息的源地址为当前进程的 PID
nlh->nlmsg_flags = 0;// 填充消息内容
strcpy(NLMSG_DATA(nlh), "Hello, kernel!");// 创建 I/O 向量
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;// 创建消息
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;// 发送消息
ret = sendmsg(sock_fd, &msg, 0);
if (ret < 0) {
perror("sendmsg");
close(sock_fd);
exit(1);
}printf("Message sent to kernel\n");
// 关闭 socket
close(sock_fd);
return 0;
}void signalHandler(int signum, siginfo_t *info, void *context) {
//
int data = info->si_value.sival_int;
nos_notify_kernel(data);
printf("Received signal %d with data: %d\n", signum, info->si_value.sival_int);
}int register_signal_func(void)
{
struct sigaction sa;
sa.sa_sigaction = signalHandler;
sa.sa_flags = SA_SIGINFO;// 注册信号处理函数
sigaction(SIGUSR3, &sa, NULL);}
int main() {
pid_t em_pid;// em_pid = fork();
register_signal_func();
while(1);
return 0;}
内核层代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <net/netlink.h>
#define MY_FAMILY 17
static struct sock *nl_sk = NULL;void receive_message(struct sk_buff *skb) {
struct nlmsghdr *nlh;
struct task_struct *task;
struct sk_buff *skb_out;
int msg_size;
char *msg = "Hello from kernel space!";
int res;
int pid;
struct pid *pid_struct;
msg_size = strlen(msg);nlh = (struct nlmsghdr *)skb->data;
pid = nlh->nlmsg_pid;printk(KERN_INFO "em pid: %d \n",pid);
pid_struct = find_get_pid(pid);task = pid_task(pid_struct, PIDTYPE_PID);
// task = pid_task(find_vpid(pid), PIDTYPE_PID);
pr_info("Parent process: %s (PID: %d)\n", task->comm, task->pid);if (task != NULL) {
printk(KERN_INFO "Traversing child processes...\n");
traverse_children(task);
} else {
printk(KERN_INFO "Failed to find the target process\n");
}
#if 0
skb_out = nlmsg_new(msg_size, 0);
if (!skb_out) {
printk(KERN_ERR "Failed to allocate new skb\n");
return;
}nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0);
NETLINK_CB(skb_out).dst_group = 0; /* ä¸<8d>使ç<94>¨å¤<9a>æ<92>ç»<84> */strncpy(nlmsg_data(nlh), msg, msg_size);
res = nlmsg_unicast(nl_sk, skb_out, pid);
if (res < 0)
printk(KERN_INFO "Error while sending back to user\n");
#endif
}
static int __init child_processes_init(void)
{
struct netlink_kernel_cfg cfg = {
.input = receive_message,
};nl_sk = netlink_kernel_create(&init_net, MY_FAMILY, &cfg);
if (!nl_sk) {
printk(KERN_ALERT "Error creating Netlink socket\n");
return -1;
}
cgroup_sys_mkdir();
printk(KERN_INFO "Netlink socket created\n");
return 0;
}static void __exit child_processes_exit(void)
{
printk(KERN_INFO "Exiting child_processes module\n");
if (nl_sk) {
netlink_kernel_release(nl_sk);
printk(KERN_INFO "Netlink socket closed\n");
}}
module_init(child_processes_init);
module_exit(child_processes_exit);
MODULE_LICENSE("GPL");