文章:用户空间与内核通信(一)介绍了系统调用(System Call),内核模块参数和sysfs,sysctl函数方式进行用户空间和内核空间的访问。本章节我将介绍使用netlink套接字和proc文件系统实现用户空间对内核空间的访问。
netlink套接字
netlink是一种基于socket的通信机制,用于在用户空间与内核空间之间进行小量数据的及时交互。netlink套接字允许用户空间程序与内核空间程序建立连接,并通过发送和接收消息来进行通信。
内核模块:
c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netlink.h>
#include <net/netlink.h>
#define NETLINK_USER 31
#define MSG_LEN 1024
struct sock *nl_sock = NULL;
// 接收用户空间消息的回调函数
static void nl_recv_msg(struct sk_buff *skb) {
struct nlmsghdr *nlh;
char msg[MSG_LEN];
// 从skb中获取消息头
nlh = nlmsg_hdr(skb);
if (nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&
netlink_skb_valid(skb, nlh->nlmsg_len)) {
// 拷贝消息数据到msg缓冲区
strncpy(msg, NLMSG_DATA(nlh), MSG_LEN);
// 打印接收到的消息
printk(KERN_INFO "Received message from user space: %s\n", msg);
}
}
// 模块初始化函数
static int __init init_netlink(void) {
// 创建Netlink套接字
nl_sock = netlink_kernel_create(&init_net, NETLINK_USER, 0, nl_recv_msg, NULL, THIS_MODULE);
if (!nl_sock) {
printk(KERN_ERR "Failed to create netlink socket\n");
return -1;
}
printk(KERN_INFO "Netlink socket created\n");
return 0;
}
// 模块退出函数
static void __exit exit_netlink(void) {
if (nl_sock)
netlink_kernel_release(nl_sock);
printk(KERN_INFO "Netlink socket released\n");
}
module_init(init_netlink);
module_exit(exit_netlink);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
用户空间应用程序:
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#define NETLINK_USER 31
#define MSG_LEN 1024
int main() {
int sockfd;
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
struct msghdr msg;
sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_USER);
if (sockfd < 0) {
perror("socket");
return -1;
}
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid(); // This is the source's port number
bind(sockfd, (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; // To kernel
dest_addr.nl_groups = 0;
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MSG_LEN));
memset(nlh, 0, NLMSG_SPACE(MSG_LEN));
nlh->nlmsg_len = NLMSG_SPACE(MSG_LEN);
nlh->nlmsg_pid = getpid();
nlh->nlmsg_flags = 0;
strcpy(NLMSG_DATA(nlh), "Hello from user space");
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;
sendmsg(sockfd, &msg, 0);
close(sockfd);
free(nlh);
return 0;
}
编译内核模块:
bash
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
加载内核模块:
bash
sudo insmod your_module.ko
编译用户空间程序:
bash
gcc user_program.c -o user_program
运行用户空间程序:
bash
sudo ./user_program
proc文件系统
proc是一个虚拟文件系统,用于导出内核和进程的状态信息。用户空间程序可以通过读取proc文件系统中的文件来获取内核和进程的信息,也可以通过写入proc文件来向内核发送指令或修改配置。
这些机制为用户空间与内核空间之间的通信提供了灵活和多样化的方式,使得用户程序能够与操作系统内核进行交互,获取系统服务并完成各种任务。
下面的示例,演示了如何使用proc文件系统在用户空间和内核空间之间进行通信。在此示例中,我们将创建一个proc文件,并使用它来向内核发送和接收数据。
首先,让我们创建一个名为"proc_example"的内核模块,该模块将在/proc目录下创建一个名为"proc_example"的文件。用户可以通过读取和写入此文件来与内核通信。
c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#define PROC_ENTRY_NAME "proc_example"
#define BUFFER_SIZE 1024
static struct proc_dir_entry *proc_entry;
static char proc_buffer[BUFFER_SIZE];
// 读取proc文件的回调函数
static ssize_t proc_read(struct file *file, char __user *buffer, size_t count, loff_t *offset) {
ssize_t len = strlen(proc_buffer);
if (*offset >= len) {
return 0; // 已经读取完毕
}
if (count > len - *offset) {
count = len - *offset; // 如果请求的数据超过了剩余的数据量,则只读取剩余的数据量
}
if (copy_to_user(buffer, proc_buffer + *offset, count) != 0) {
return -EFAULT; // 复制数据失败
}
*offset += count; // 更新偏移量
return count;
}
// 写入proc文件的回调函数
static ssize_t proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *offset) {
if (count >= BUFFER_SIZE) {
return -EINVAL; // 写入的数据过大
}
if (copy_from_user(proc_buffer, buffer, count) != 0) {
return -EFAULT; // 复制数据失败
}
proc_buffer[count] = '\0'; // 添加字符串结束符
return count;
}
// 模块初始化函数
static int __init init_proc_example(void) {
proc_entry = proc_create(PROC_ENTRY_NAME, 0666, NULL, &proc_fops);
if (!proc_entry) {
printk(KERN_ERR "Failed to create /proc/%s\n", PROC_ENTRY_NAME);
return -ENOMEM;
}
printk(KERN_INFO "/proc/%s created\n", PROC_ENTRY_NAME);
return 0;
}
// 模块退出函数
static void __exit exit_proc_example(void) {
if (proc_entry) {
remove_proc_entry(PROC_ENTRY_NAME, NULL);
printk(KERN_INFO "/proc/%s removed\n", PROC_ENTRY_NAME);
}
}
// 定义proc文件操作结构体
static const struct file_operations proc_fops = {
.read = proc_read,
.write = proc_write,
};
module_init(init_proc_example);
module_exit(exit_proc_example);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
编译和加载模块后,你可以在/sys/kernel/debug/proc_example中读取和写入数据。例如,你可以使用cat和echo命令:
bash
$ echo "Hello from user space" > /proc/proc_example
$ cat /proc/proc_example
Hello from user space