# 深入理解Linux内核与用户态通信:Netlink机制实战
深入理解Linux内核与用户态通信:Netlink机制实战
摘要:本文深入探讨了Linux系统中内核态与用户态之间的通信机制Netlink,通过理论讲解与实战代码相结合的方式,带你全面掌握这一强大的IPC通信方式。文章包含完整的示例代码和测试结果分析,适合Linux系统编程进阶学习。
📖 目录
文章目录
- 深入理解Linux内核与用户态通信:Netlink机制实战
- 📖 目录
- @[toc]
- 一、前言
- 二、Netlink通信机制概述
- 2.1 什么是Netlink
- 2.2 Netlink的核心优势
- ✅ **1. 简单易用**
- ✅ **2. 异步通信**
- ✅ **3. 模块化设计**
- ✅ **4. 支持多播**
- ✅ **5. 双向通信**
- 2.3 Netlink的应用场景
- 三、Netlink核心数据结构详解
- 3.1 网络命名空间:struct net
- 3.2 网络层套接字:struct sock
- 3.3 网络数据包:struct sk_buff
- 3.4 Netlink消息头:struct nlmsghdr
- 消息类型(nlmsg_type)
- 消息标志(nlmsg_flags)
- 3.5 Netlink地址结构:struct sockaddr_nl
- 3.6 内核配置结构:struct netlink_kernel_cfg
- 四、Netlink API函数详解
- 4.1 内核态API
- 创建和销毁Socket
- 发送消息
- 消息处理辅助函数
- 4.2 用户态API
- 五、实战项目:构建完整的Netlink通信系统
- 5.1 项目架构设计
- 5.2 内核模块实现
- 5.3 用户空间程序实现
- 5.4 编译和测试
- 编译内核模块
- 编译用户程序
- 加载模块并测试
- 预期输出
- 六、性能测试与分析
- 6.1 性能测试代码
- 6.2 测试结果
- 七、开发中的注意事项
- 7.1 内存管理
- 7.2 端口号选择
- 7.3 协议类型定义
- 7.4 线程安全
- 7.5 调试技巧
- 八、进阶应用场景
- 8.1 实现内核事件通知系统
- 8.2 构建用户态网络工具
- 8.3 内核模块间通信
- 九、总结与展望
- 十、参考资料
文章目录
- 深入理解Linux内核与用户态通信:Netlink机制实战
- 📖 目录
- @[toc]
- 一、前言
- 二、Netlink通信机制概述
- 2.1 什么是Netlink
- 2.2 Netlink的核心优势
- ✅ **1. 简单易用**
- ✅ **2. 异步通信**
- ✅ **3. 模块化设计**
- ✅ **4. 支持多播**
- ✅ **5. 双向通信**
- 2.3 Netlink的应用场景
- 三、Netlink核心数据结构详解
- 3.1 网络命名空间:struct net
- 3.2 网络层套接字:struct sock
- 3.3 网络数据包:struct sk_buff
- 3.4 Netlink消息头:struct nlmsghdr
- 消息类型(nlmsg_type)
- 消息标志(nlmsg_flags)
- 3.5 Netlink地址结构:struct sockaddr_nl
- 3.6 内核配置结构:struct netlink_kernel_cfg
- 四、Netlink API函数详解
- 4.1 内核态API
- 创建和销毁Socket
- 发送消息
- 消息处理辅助函数
- 4.2 用户态API
- 五、实战项目:构建完整的Netlink通信系统
- 5.1 项目架构设计
- 5.2 内核模块实现
- 5.3 用户空间程序实现
- 5.4 编译和测试
- 编译内核模块
- 编译用户程序
- 加载模块并测试
- 预期输出
- 六、性能测试与分析
- 6.1 性能测试代码
- 6.2 测试结果
- 七、开发中的注意事项
- 7.1 内存管理
- 7.2 端口号选择
- 7.3 协议类型定义
- 7.4 线程安全
- 7.5 调试技巧
- 八、进阶应用场景
- 8.1 实现内核事件通知系统
- 8.2 构建用户态网络工具
- 8.3 内核模块间通信
- 九、总结与展望
- 十、参考资料
一、前言
在Linux系统开发中,我们经常需要实现内核态与用户态之间的数据交互。传统的方式包括系统调用、ioctl、/proc文件系统等,但这些方法都存在一定的局限性。今天,我想和大家分享一个更加优雅和强大的解决方案——Netlink套接字通信机制。
经过一段时间的学习和实践,我发现Netlink不仅使用简单,而且功能强大。它广泛应用于Linux内核的各个子系统中,包括路由管理、防火墙、netfilter等核心模块。本文将结合我的实际开发经验,系统地介绍Netlink的原理和使用方法。
二、Netlink通信机制概述
2.1 什么是Netlink
Netlink是Linux特有的一种用于内核与用户进程之间通信的特殊IPC(进程间通信)机制。它基于标准的Socket API实现,但提供了比传统Socket更强大的功能。
可以将Netlink理解为一个"特殊的Socket"——用户态程序通过标准Socket接口就能使用,而内核态则需要使用专门的内核API来操作。
2.2 Netlink的核心优势
通过实际使用对比,我总结了Netlink相比其他通信方式的几个显著优点:
✅ 1. 简单易用
只需在include/linux/netlink.h
中定义一个新的协议类型(例如#define NETLINK_TEST 20
),内核和用户态就可以立即开始通信,无需复杂的配置。
✅ 2. 异步通信
消息传递采用异步机制,发送方只需将消息放入接收方的Socket缓冲队列即可返回,不必等待对方处理完成。这对高并发场景特别有利。
✅ 3. 模块化设计
内核部分可以采用内核模块(LKM)方式实现,与用户空间程序没有编译时依赖关系,极大地提高了灵活性。
✅ 4. 支持多播
这是Netlink的一大亮点!内核或应用可以将消息多播给一个Netlink组,组内所有成员都能接收到。Linux的内核事件通知(udev)就是利用了这一特性。
✅ 5. 双向通信
与传统的系统调用不同,Netlink允许内核主动向用户空间发起会话,实现真正的双向通信。
2.3 Netlink的应用场景
目前Linux内核中使用Netlink的典型场景包括:
- NETLINK_ROUTE:路由子系统
- NETLINK_FIREWALL:防火墙管理
- NETLINK_NETFILTER:网络过滤框架
- NETLINK_KOBJECT_UEVENT:内核对象事件通知
- NETLINK_GENERIC:通用Netlink接口
- 自定义协议:开发者可以自定义协议类型
三、Netlink核心数据结构详解
在实际编程之前,我们需要理解Netlink涉及的几个关键数据结构。这些结构构成了整个通信框架的基础。
3.1 网络命名空间:struct net
struct net {refcount_t passive; // 决定何时释放网络命名空间atomic_t count; // 决定何时关闭网络命名空间spinlock_t rules_mod_lock;atomic64_t cookie_gen;// ... 其他字段
} __randomize_layout;
这个结构代表网络命名空间,通常我们使用全局的init_net
。
3.2 网络层套接字:struct sock
struct sock {struct sock_common __sk_common;socket_lock_t sk_lock;atomic_t sk_drops;int sk_rcvlowat;struct sk_buff_head sk_error_queue;struct sk_buff_head sk_receive_queue;// ... 更多字段
};
这是套接字在网络层的表示,所有的网络操作都围绕这个结构展开。
3.3 网络数据包:struct sk_buff
struct sk_buff {struct sock *sk; // 关联的socketsk_buff_data_t tail;sk_buff_data_t end;unsigned char *head, *data; // 数据指针unsigned int truesize;refcount_t users;
};
sk_buff是Linux网络栈中最重要的数据结构之一,用于管理和控制收发数据包。
3.4 Netlink消息头:struct nlmsghdr
这是我们在实际编程中接触最多的结构:
struct nlmsghdr {__u32 nlmsg_len; // 消息总长度(包括头部)__u16 nlmsg_type; // 消息类型__u16 nlmsg_flags; // 消息标志__u32 nlmsg_seq; // 消息序列号__u32 nlmsg_pid; // 发送进程的端口ID(内核为0)
};
消息类型(nlmsg_type)
系统预定义了几种通用消息类型:
#define NLMSG_NOOP 0x1 // 空操作,丢弃该消息
#define NLMSG_ERROR 0x2 // 错误消息
#define NLMSG_DONE 0x3 // 多段消息结束标志
#define NLMSG_OVERRUN 0x4 // 缓冲区溢出,数据丢失
消息标志(nlmsg_flags)
#define NLM_F_REQUEST 0x01 // 请求消息
#define NLM_F_MULTI 0x02 // 多段消息
#define NLM_F_ACK 0x04 // 需要应答
#define NLM_F_ECHO 0x08 // 回显请求// GET请求修饰符
#define NLM_F_ROOT 0x100 // 返回整棵树
#define NLM_F_MATCH 0x200 // 返回所有匹配项
#define NLM_F_ATOMIC 0x400 // 原子操作
#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH)// NEW请求修饰符
#define NLM_F_REPLACE 0x100 // 替换已存在项
#define NLM_F_EXCL 0x200 // 不存在时才创建
#define NLM_F_CREATE 0x400 // 不存在则创建
#define NLM_F_APPEND 0x800 // 添加到列表末尾
3.5 Netlink地址结构:struct sockaddr_nl
struct sockaddr_nl {__kernel_sa_family_t nl_family; // 地址族(AF_NETLINK)unsigned short nl_pad; // 填充字段(设为0)__u32 nl_pid; // 端口ID__u32 nl_groups; // 多播组掩码
};
3.6 内核配置结构:struct netlink_kernel_cfg
struct netlink_kernel_cfg {unsigned int groups;unsigned int flags;void (*input)(struct sk_buff *skb); // 接收回调函数struct mutex *cb_mutex;int (*bind)(struct net *net, int group);void (*unbind)(struct net *net, int group);bool (*compare)(struct net *net, struct sock *sk);
};
这个结构用于配置内核端的Netlink套接字,其中最重要的是input
回调函数。
四、Netlink API函数详解
4.1 内核态API
创建和销毁Socket
// 创建Netlink socket
static inline struct sock *netlink_kernel_create(struct net *net, // 网络命名空间(通常用&init_net)int unit, // 协议类型struct netlink_kernel_cfg *cfg // 配置参数
);// 释放Netlink socket
void netlink_kernel_release(struct sock *sk);
发送消息
// 单播消息
int netlink_unicast(struct sock *ssk, // Netlink socketstruct sk_buff *skb, // 数据包u32 portid, // 目标端口IDint nonblock // 是否非阻塞(1=非阻塞,0=阻塞)
);// 多播消息
int netlink_broadcast(struct sock *ssk, // Netlink socketstruct sk_buff *skb, // 数据包u32 portid, // 源端口IDu32 group, // 目标多播组掩码gfp_t allocation // 内存分配标志(GFP_ATOMIC或GFP_KERNEL)
);
消息处理辅助函数
// 从sk_buff获取netlink消息头
static inline struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)
{return (struct nlmsghdr *)skb->data;
}// 创建指定大小的sk_buff
static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
{return alloc_skb(nlmsg_total_size(payload), flags);
}// 向sk_buff添加netlink消息
static inline struct nlmsghdr *nlmsg_put(struct sk_buff *skb,u32 portid,u32 seq,int type,int payload,int flags
);// 释放sk_buff
static inline void nlmsg_free(struct sk_buff *skb)
{kfree_skb(skb);
}// 获取消息数据部分(payload)
static inline void *nlmsg_data(const struct nlmsghdr *nlh)
{return (unsigned char *) nlh + NLMSG_HDRLEN;
}// 获取下一条消息
static inline struct nlmsghdr *nlmsg_next(const struct nlmsghdr *nlh,int *remaining
);
4.2 用户态API
用户空间使用标准的Socket API:
// 创建socket
int socket(int domain, // AF_NETLINKint type, // SOCK_RAWint protocol // 自定义协议类型
);// 绑定地址
int bind(int socket,const struct sockaddr *address,size_t address_len
);// 发送数据
int sendto(int sockfd,void *buffer,size_t len,int flags,struct sockaddr *to,socklen_t tolen
);// 接收数据
int recvfrom(int sockfd,void *buffer,size_t len,int flags,struct sockaddr *src_from,socklen_t *src_len
);
五、实战项目:构建完整的Netlink通信系统
理论知识了解得再多,不如动手实践一次。接下来,我将带大家从零开始构建一个完整的Netlink通信示例,包括内核模块和用户空间程序。
5.1 项目架构设计
我们的项目包含两部分:
- 内核模块:接收用户消息并响应
- 用户程序:发送消息到内核并接收回复
通信流程如下:
用户程序 --[发送消息]--> 内核模块^ || |+-------[返回响应]--------+
5.2 内核模块实现
创建文件netlink_kernel.c
:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>#define NETLINK_TEST 30 // 自定义协议类型
#define USER_PORT 100 // 用户端口号// 全局变量
int netlink_count = 0; // 消息计数器
char netlink_kmsg[30]; // 内核消息缓冲
struct sock *nlsk = NULL; // Netlink socket指针
extern struct net init_net; // 网络命名空间/*** 发送消息到用户空间* @param pbuf: 消息内容* @param len: 消息长度* @return: 成功返回发送字节数,失败返回-1*/
int send_usrmsg(char *pbuf, uint16_t len)
{struct sk_buff *nl_skb;struct nlmsghdr *nlh;int ret;// 1. 分配sk_buffnl_skb = nlmsg_new(len, GFP_ATOMIC);if (!nl_skb) {printk("netlink alloc failure\n");return -1;}// 2. 填充netlink消息头nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);if (nlh == NULL) {printk("nlmsg_put failure\n");nlmsg_free(nl_skb);return -1;}// 3. 拷贝数据到消息体memcpy(nlmsg_data(nlh), pbuf, len);// 4. 通过netlink单播发送ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);return ret;
}/*** 接收用户空间消息的回调函数* @param skb: 接收到的数据包*/
static void netlink_rcv_msg(struct sk_buff *skb)
{struct nlmsghdr *nlh = NULL;char *umsg = NULL;char *kmsg;// 检查数据包长度if (skb->len >= nlmsg_total_size(0)) {// 更新计数器并生成响应消息netlink_count++;snprintf(netlink_kmsg, sizeof(netlink_kmsg), "hello users count=%d", netlink_count);kmsg = netlink_kmsg;// 获取消息头和数据nlh = nlmsg_hdr(skb);umsg = NLMSG_DATA(nlh);if (umsg) {printk("kernel recv from user: %s\n", umsg);// 发送响应send_usrmsg(kmsg, strlen(kmsg));}}
}// 配置结构体
struct netlink_kernel_cfg cfg = {.input = netlink_rcv_msg, // 设置接收回调
};/*** 模块初始化函数*/
static int __init netlink_test_init(void)
{// 创建netlink socketnlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);if (nlsk == NULL) {printk("netlink_kernel_create error!\n");return -1;}printk("netlink_test_init success\n");return 0;
}/*** 模块退出函数*/
static void __exit netlink_test_exit(void)
{if (nlsk) {netlink_kernel_release(nlsk);nlsk = NULL;}printk("netlink_test_exit!\n");
}module_init(netlink_test_init);
module_exit(netlink_test_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Netlink communication demo");
Makefile文件:
MODULE_NAME := netlink_kernel
obj-m := $(MODULE_NAME).oKERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)all:$(MAKE) -C $(KERNELDIR) M=$(PWD)clean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
5.3 用户空间程序实现
创建文件netlink_user.c
:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>#define NETLINK_TEST 30 // 与内核定义一致
#define USER_PORT 100 // 端口号
#define MAX_PLOAD 125 // 最大负载
#define MSG_LEN 125 // 消息长度// 用户消息结构
typedef struct _user_msg_info {struct nlmsghdr hdr;char msg[MSG_LEN];
} user_msg_info;int main(int argc, char **argv)
{int skfd;int ret;user_msg_info u_info;socklen_t len;struct nlmsghdr *nlh = NULL;struct sockaddr_nl saddr, daddr;char *umsg = "hello netlink!!";int loop_count = 0;// 1. 创建Netlink socketskfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);if (skfd == -1) {perror("create socket error");return -1;}// 2. 配置本地地址(源地址)memset(&saddr, 0, sizeof(saddr));saddr.nl_family = AF_NETLINK;saddr.nl_pid = USER_PORT; // 设置本地端口saddr.nl_groups = 0;// 3. 绑定socketif (bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) {perror("bind() error");close(skfd);return -1;}// 4. 配置目标地址(内核)memset(&daddr, 0, sizeof(daddr));daddr.nl_family = AF_NETLINK;daddr.nl_pid = 0; // 目标是内核daddr.nl_groups = 0;// 5. 构造netlink消息nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));memset(nlh, 0, sizeof(struct nlmsghdr));nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);nlh->nlmsg_flags = 0;nlh->nlmsg_type = 0;nlh->nlmsg_seq = 0;nlh->nlmsg_pid = saddr.nl_pid;// 拷贝消息内容memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));// 6. 循环发送和接收while (loop_count < 11) {printf("sendto kernel: %s\n", umsg);// 发送消息到内核ret = sendto(skfd, nlh, nlh->nlmsg_len, 0,(struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));if (!ret) {perror("sendto error");close(skfd);exit(-1);}// 接收内核响应memset(&u_info, 0, sizeof(u_info));len = sizeof(struct sockaddr_nl);ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0,(struct sockaddr *)&daddr, &len);if (!ret) {perror("recv from kernel error");close(skfd);exit(-1);}printf("from kernel: %s\n", u_info.msg);loop_count++;}// 7. 清理资源close(skfd);free((void *)nlh);return 0;
}
5.4 编译和测试
编译内核模块
# 进入内核模块目录
cd /path/to/kernel/module# 编译
make# 查看生成的.ko文件
ls -l netlink_kernel.ko
编译用户程序
gcc netlink_user.c -o netlink_user
加载模块并测试
# 加载内核模块
sudo insmod netlink_kernel.ko# 查看模块是否加载成功
lsmod | grep netlink_kernel# 运行用户程序
./netlink_user
预期输出
用户空间输出:
sendto kernel: hello netlink!!
from kernel: hello users count=1
sendto kernel: hello netlink!!
from kernel: hello users count=2
sendto kernel: hello netlink!!
from kernel: hello users count=3
...
sendto kernel: hello netlink!!
from kernel: hello users count=11
内核日志(通过dmesg
查看):
dmesg | tail -20
[12345.678901] netlink_test_init success
[12348.234567] kernel recv from user: hello netlink!!
[12348.234589] kernel recv from user: hello netlink!!
[12348.234601] kernel recv from user: hello netlink!!
...
六、性能测试与分析
6.1 性能测试代码
为了测试Netlink的通信性能,我修改了用户程序,增加了时间统计:
#include <time.h>// 在main函数中添加
struct timespec time1, time2;
unsigned long int duration;clock_gettime(CLOCK_REALTIME, &time1);// 将循环次数改为10000
while (loop_count < 10000) {// ... 发送和接收代码loop_count++;
}clock_gettime(CLOCK_REALTIME, &time2);
duration = (time2.tv_sec - time1.tv_sec) * 1000000000 + (time2.tv_nsec - time1.tv_nsec);printf("Total time: %ld.%ld seconds\n", duration / 1000000000, duration % 1000000000);
printf("Average latency: %.2f us\n", (double)duration / loop_count / 1000);
6.2 测试结果
在我的测试环境中(Intel i5处理器,内核版本5.10),进行10000次往返通信的结果:
- 总耗时:约262ms
- 单次往返平均延迟:26微秒
这个性能表现非常出色!对比其他通信方式:
通信方式 | 平均延迟 | 优缺点 |
---|---|---|
Netlink | ~26μs | 性能好,双向通信,支持多播 |
ioctl | ~15μs | 性能最好,但单向,不支持异步 |
/proc | ~50μs | 简单,但性能较差 |
system call | ~10μs | 性能好,但只能用户调内核 |
可以看出,Netlink在保持良好性能的同时,提供了更强大和灵活的功能。
七、开发中的注意事项
7.1 内存管理
-
sk_buff的生命周期:使用
nlmsg_new()
创建的sk_buff,在netlink_unicast()
或netlink_broadcast()
成功后会自动释放,失败时需要手动调用nlmsg_free()
。 -
避免内存泄漏:在错误处理路径中,务必检查是否正确释放了资源。
7.2 端口号选择
- 用户空间的
nl_pid
通常使用进程PID,但也可以自定义 - 内核空间的
nl_pid
始终为0 - 避免端口号冲突,特别是在多进程环境中
7.3 协议类型定义
自定义协议类型时,建议使用大于16的数值(0-15被系统预留)。当前系统已定义的协议类型包括:
#define NETLINK_ROUTE 0 // 路由
#define NETLINK_UNUSED 1 // 未使用
#define NETLINK_USERSOCK 2 // 用户态socket
#define NETLINK_FIREWALL 3 // 防火墙
// ... 等等
7.4 线程安全
在内核模块中,如果多个线程可能同时访问Netlink socket,需要添加适当的锁保护。
7.5 调试技巧
- 使用
printk()
输出调试信息到内核日志 - 通过
dmesg -w
实时查看内核日志 - 使用
strace
跟踪用户程序的系统调用 - 利用
wireshark
抓包分析(Netlink也可以抓包!)
八、进阶应用场景
8.1 实现内核事件通知系统
可以利用Netlink的多播功能,实现类似udev的事件通知机制:
// 内核端发送多播消息
netlink_broadcast(nlsk, skb, 0, group_mask, GFP_KERNEL);// 用户端加入多播组
setsockopt(skfd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group));
8.2 构建用户态网络工具
许多网络管理工具都基于Netlink实现,例如:
ip
命令(iproute2工具集)tc
流量控制工具- 自定义网络监控工具
8.3 内核模块间通信
虽然不常见,但Netlink也可用于不同内核模块之间的通信。
九、总结与展望
通过本文的学习,我们系统地掌握了Linux Netlink通信机制,包括:
✔ Netlink的基本原理和优势
✔ 核心数据结构的详细解析
✔ 内核态和用户态API的使用方法
✔ 完整的实战项目开发
✔ 性能测试和优化建议
Netlink作为Linux内核与用户空间通信的重要桥梁,在系统编程中扮演着关键角色。随着对Linux内核的深入学习,相信你会发现更多Netlink的应用场景。
下一步学习建议:
- 研究Linux内核中Netlink的实际应用(如rtnetlink)
- 学习Generic Netlink框架
- 探索Netlink与其他IPC机制的组合使用
- 尝试开发自己的内核模块项目
十、参考资料
- Linux内核源码:
net/netlink/
目录 - 《Linux设备驱动程序》第三版
- 《深入Linux内核架构》
- Linux man手册:
man 7 netlink
- 内核文档:
Documentation/networking/
作者注:本文是我在学习Linux内核通信机制过程中的总结和实践记录。如果文章对你有帮助,欢迎点赞收藏!如有问题或建议,欢迎在评论区交流讨论。
💡 提示:文章中的示例代码已在Ubuntu 20.04、内核版本5.10上测试通过。不同内核版本的API可能略有差异,请根据实际情况调整。
关键词:Linux内核通信、Netlink、内核模块开发、IPC机制、Socket编程、用户态内核态通信
标签:#Linux
#内核开发
#Netlink
#系统编程
#C语言