Linux 커널은 프로세스 관리, 메모리 할당, 장치 드라이버, 네트워크 프로토콜 등과 같은 많은 기능과 서비스를 제공하는 복잡하고 강력한 시스템입니다. 하지만 사용자 공간 애플리케이션이 커널과 상호 작용하도록 하려면 어떻게 해야 할까요? 전통적인 방법에는 시스템 호출, 신호, 파이프, 소켓 등이 포함되지만 모두 몇 가지 제한 사항과 단점이 있습니다. 예를 들어 시스템 호출은 미리 정의된 작업만 수행할 수 있고, 신호는 간단한 정보만 전달할 수 있으며, 파이프와 소켓에는 추가 버퍼와 복사본이 필요합니다. 더 효율적이고 유연하며 확장 가능한 방법이 있습니까? 대답은 Linux Netlink입니다.
넷링크란 무엇인가요? Netlink는 커널과 사용자 모드 프로세스 간의 통신을 위해 Linux에서 제공하는 통신 방법입니다. 그러나 Netlink는 주로 사용자 공간과 커널 공간 간의 통신에 사용되지만 사용자 공간의 두 프로세스 간의 통신에도 사용될 수 있습니다. 프로세스 간에 통신하는 다른 방법은 많이 있으며 Netlink는 일반적으로 사용되지 않습니다. Netlink의 브로드캐스트 기능을 사용해야 하는 경우가 아니라면 말이죠.
그렇다면 넷링크의 장점은 무엇인가요? 일반적으로 사용자 공간과 커널 공간 사이에는 /proc, ioctl 및 Netlink의 세 가지 통신 방법이 있습니다. 처음 두 개는 단방향이지만 Netlink는 이중 통신을 달성할 수 있습니다.
Netlink 프로토콜은 32비트 포트 번호 주소 지정(이전 PID라고 함)을 사용하는 BSDsocket 및 AF_NETLINK 주소 계열(addressfamily)을 기반으로 합니다. 각 Netlink 프로토콜(또는 매뉴얼에서는 netlinkfamily라고 하는 버스)은 일반적으로 하나 또는 그룹과 연결됩니다. 라우팅 및 링크 정보를 얻고 설정하기 위한 NETLINK_ROUTE, 사용자 공간의 udev 프로세스에 알림을 보내는 커널에 대한 NETLINK_KOBJECT_UEVENT 등 커널 서비스/구성 요소가 연관되어 있습니다. netlink에는 다음과 같은 기능이 있습니다:
①전이중 및 비동기식 통신을 지원합니다. (물론 동기식도 지원됩니다.)
②사용자 공간은 표준 BSDsocket 인터페이스를 사용할 수 있습니다(그러나 netlink는 프로토콜 패키지의 구성 및 구문 분석 프로세스를 차단하지 않으므로 libnl과 같은 타사 라이브러리를 사용하는 것이 좋습니다)
3커널 공간에서 전용 커널 API 인터페이스를 사용하세요
4멀티캐스트 지원(따라서 "버스" 통신을 지원하고 메시지 구독 활성화)
⑤커널 측에서는 프로세스 컨텍스트와 인터럽트 컨텍스트에 사용될 수 있습니다
Netlink를 배우는 방법은 무엇입니까? 가장 좋은 방법은 Netlink와 UDPsocket을 비교하는 것입니다. 왜냐하면 그들은 장소가 정말 비슷하기 때문입니다. AF_NETLINK는 AF_INET에 해당하며 프로토콜 계열이고, NETLINK_ROUTE 및 NETLINK_GENERIC은 프로토콜이며 UDP에 해당합니다.
그런 다음 주로 Netlink와 UDPsocket의 차이점에 중점을 둡니다. 가장 중요한 점은 UDPsocket을 사용하여 데이터 패킷을 보낼 때 사용자가 UDP 데이터 패킷의 헤더를 구성할 필요가 없다는 것입니다. 원본 주소와 대상 주소(sockaddr_in). 하지만 Netlink에서는 헤더를 직접 생성해야 합니다(이 헤더의 사용에 대해서는 나중에 설명하겠습니다).
일반적으로 Netlink를 사용할 때는 프로토콜을 지정해야 합니다. 커널에 의해 예약된 NETLINK_GENERIC(linux/netlink.h에 정의됨)을 사용하거나 실제로 프로토콜을 정의하는 자체 사용자 정의 프로토콜을 사용할 수 있습니다. 커널 번호가 차지하지 않았습니다. 아래에서는 예제를 작성하기 위해 정의한 프로토콜로 NETLINK_TEST를 사용합니다(참고: 사용자 모드와 커널 모드 코드가 모두 정의를 찾을 수 있는 한 사용자 정의 프로토콜을 linux/netlink.h에 추가할 필요는 없습니다). UDP를 사용하여 메시지를 보내는 방법에는 sendto와 sendmsg가 있다는 것을 알고 있습니다. Netlink도 이 두 가지 방법을 지원합니다. 먼저 sendmsg 사용법을 살펴보겠습니다.
먼저 몇 가지 중요한 데이터 구조 간의 관계를 살펴보겠습니다.
msghdr 구조는 소켓 생성에 사용되며 Netlink에만 국한되지 않습니다. 여기서는 자세히 설명하지 않겠습니다. 이 구조의 기능을 더 잘 이해하는 방법을 설명하십시오. 우리는 소켓 메시지의 송신 및 수신 기능이 일반적으로 다음 쌍을 갖는다는 것을 알고 있습니다: recv/send, readv/writev, recvfrom/sendto. 물론, recvmsg/sendmsg도 있습니다. 처음 세 쌍의 함수에는 각각 고유한 기능이 있으며, recvmsg/sendmsg는 처음 세 쌍의 모든 기능을 포함하며 물론 고유한 특수 용도도 있습니다. msghdr의 처음 두 멤버는 recvfrom/sendto의 기능을 충족하고, 중간 두 멤버 msg_iov 및 msg_iovlen은 readv/writev의 기능을 충족하며, 마지막 msg_flags는 recv/send의 플래그 기능을 충족합니다. 나머지 msg_control 및 msg_controllen은 recvmsg/sendmsg의 고유한 기능을 만족합니다.
Structsockaddr_ln은 일반적인 소켓 프로그래밍의 sockaddr_in과 동일한 기능을 갖는 Netlink의 주소입니다.
structsockaddr_nl{}의 자세한 정의와 설명은 다음과 같습니다.
으아아아(1)nl_pid: Netlink 사양에서 PID의 전체 이름은 Port-ID(32비트)입니다. 주요 기능은 netlink 기반 소켓 채널을 고유하게 식별하는 것입니다. 일반적으로 nl_pid는 현재 프로세스의 프로세스 ID로 설정됩니다. 우리는 이전에 Netlink가 사용자-커널 공간 통신을 실현할 수 있을 뿐만 아니라 사용자 공간의 두 프로세스 간 또는 커널 공간의 두 프로세스 간 통신도 가능하게 한다고 말했습니다. 이 속성이 0이면 일반적으로 커널을 나타냅니다.
(2)nl_groups: 사용자 공간 프로세스가 멀티캐스트 그룹에 참여하려는 경우 바인딩() 시스템 호출을 실행해야 합니다. 이 필드는 호출자가 참여하기를 원하는 멀티캐스트 그룹 번호의 마스크를 지정합니다(그룹 번호가 아니라는 점에 유의하십시오. 이 필드에 대해서는 나중에 자세히 설명하겠습니다). 이 필드가 0이면 발신자가 멀티캐스트 그룹에 참여하기를 원하지 않는다는 의미입니다. Netlink 프로토콜 도메인에 속하는 각 프로토콜에 대해 최대 32개의 멀티캐스트 그룹을 지원할 수 있으며(nl_groups의 길이는 32비트이므로) 각 멀티캐스트 그룹은 1비트로 표시됩니다.
Netlink 메시지는 메시지 헤더와 메시지 본문으로 구성되며 structnlmsghdr은 메시지 헤더입니다. 메시지 헤더는 파일에 정의되어 있으며 nlmsghdr 구조로 표시됩니다.
으아아아메시지 헤더에 있는 각 멤버의 속성에 대한 설명 및 설명:
(1)nlmsg_len: 전체 메시지의 길이(바이트 단위로 계산됨) Netlink 메시지 헤더 자체를 포함합니다.
(2)nlmsg_type: 메시지 유형, 즉 데이터 메시지인지 제어 메시지인지입니다. 현재(커널 버전 2.6.21) Netlink는 다음과 같은 네 가지 유형의 제어 메시지만 지원합니다.
a)NLMSG_NOOP - 빈 메시지, 아무것도 하지 않음
b)NLMSG_ERROR - 메시지에 오류가 있음을 나타냅니다.
c) NLMSG_DONE - 커널이 Netlink 대기열을 통해 여러 메시지를 반환하는 경우 대기열의 마지막 메시지 유형은 NLMSG_DONE이며 나머지 모든 메시지의 nlmsg_flags 속성에는 NLM_F_MULTI 비트가 유효하도록 설정되어 있습니다.d)NLMSG_OVERRUN-아직 사용되지 않았습니다.
(3)nlmsg_flags: 위에서 언급한 NLM_F_MULTI와 같이 메시지에 첨부된 추가 설명 정보입니다.
메시지 본문을 어떻게 설정하나요? NLMSG_DATA를 사용할 수 있습니다. 자세한 내용은 아래 예를 참조하세요.
#include #include #include #include #include #include #include #include #include #include #include #define MAX_PAYLOAD 1024 // maximum payload size #define NETLINK_TEST 25 //自定义的协议 int main(int argc, char* argv[]) { int state; struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; //Netlink数据包头 struct iovec iov; struct msghdr msg; int sock_fd, retval; int state_smg = 0; // Create a socket sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); if(sock_fd == -1){ printf("error getting socket: %s", strerror(errno)); return -1; } // To prepare binding memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = 100; //A:设置源端端口号 src_addr.nl_groups = 0; //Bind retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)); if(retval printf("bind failed: %s", strerror(errno)); close(sock_fd); return -1; } // To orepare create mssage nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); if(!nlh){ printf("malloc nlmsghdr error!\n"); close(sock_fd); return -1; } memset(&dest_addr,0,sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; //B:设置目的端口号 dest_addr.nl_groups = 0; nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); nlh->nlmsg_pid = 100; //C:设置源端口 nlh->nlmsg_flags = 0; strcpy(NLMSG_DATA(nlh),"Hello you!"); //设置消息体 iov.iov_base = (void *)nlh; iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD); //Create mssage 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; //send message printf("state_smg\n"); state_smg = sendmsg(sock_fd,&msg,0); if(state_smg == -1) { printf("get error sendmsg = %s\n",strerror(errno)); } memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD)); //receive message printf("waiting received!\n"); while(1){ printf("In while recvmsg\n"); state = recvmsg(sock_fd, &msg, 0); if(stateprintf("state); } printf("Received message: %s\n",(char *) NLMSG_DATA(nlh)); } close(sock_fd); return 0; }
上面程序首先向内核发送一条消息;“Helloyou”,然后进入循环一直等待读取内核的回复,并将收到的回复打印出来。如果看上面程序感觉很吃力,那么应该首先复习一下UDP中使用sendmsg的用法,特别时structmsghdr的结构要清楚,这里再赘述。下面主要分析与UDP发送数据包的不同点:
1.socket地址结构不同,UDP为sockaddr_in,Netlink为structsockaddr_nl;
2.与UDP发送数据相比,Netlink多了一个消息头结构structnlmsghdr需要我们构造。
注意代码注释中的A、B、C三处分别设置了pid。首先解释一下什么是pid,网上很多文章把这个字段说成是进程的pid,其实这完全是望文生义。这里的pid和进程pid没有什么关系,仅仅相当于UDP的port。对于UDP来说port和ip标示一个地址,那对我们的NETLINK_TEST协议(注意Netlink本身不是一个协议)来说,pid就唯一标示了一个地址。所以你如果用进程pid做为标示当然也是可以的。当然同样的pid对于NETLINK_TEST协议和内核定义的其他使用Netlink的协议是不冲突的(就像TCP的80端口和UDP的80端口)。
下面分析这三处设置pid分别有什么作用,首先A和B位置的比较好理解,这是在地址(sockaddr_nl)上进行的设置,就是相当于设置源地址和目的地址(其实是端口),只是注意B处设置pid为0,0就代表是内核,可以理解为内核专用的pid,那么用户进程就不能用0做为自己的pid吗?这个只能说如果你非要用也是可以的,只是会产生一些问题,后面在分析。接下来看为什么C处的消息头仍然需要设置pid呢?这里首先要知道一个前提:内核不会像UDP一样根据我们设置的原、目的地址为我们构造消息头,所以我们不在包头写入我们自己的地址(pid),那内核怎么知道是谁发来的报文呢?当然如果内核只是处理消息不需要回复进程的话舍不设置这个消息头pid都可以。
所以每个pid的设置功能不同:A处的设置是要设置发送者的源地址,有人会说既然源地址又不会自动填充到报文中,我们为什么还要设置这个,因为你还可能要接收回复啊。就像寄信,你连“门牌号”都没有,即使你在写信时候写上你的地址是100号,对方回信目的地址也是100号,但是邮局发现根本没有这个地址怎么可能把信送到你手里呢?所以A的主要作用是注册源地址,保证可以收到回复,如果不需要回复当然可以简单将pid设置为0;B处自然就是收信人的地址,pid为0代表内核的地址,假如有一个进程在101号上注册了地址,并调用了recvmsg,如果你将B处的pid设置为101,那数据包就发给了另一个进程,这就实现了使用Netlink进行进程间通信;C相当于你在信封上写的源地址,通常情况下这个应该和你的真实地址(A)处注册的源地址相同,当然你要是不想收到回信,又想恶搞一下或者有特殊需求,你可以写成其他进程注册的pid(比如101)。这和我们现实中寄信是一样的,你给你朋友写封情书,把写信人写成你的另一个好基友,然后后果你懂得……
好了,有了这个例子我们就大概知道用户态怎么使用Netlink了,至于我们没有用到的nl_groups等其他信息后面讲到再说,下面看下内核是怎么处理Netlink的。
struct sock *netlink_kernel_create(struct net *net, int unit,unsigned int groups, void (*input)(struct sk_buff *skb), struct mutex *cb_mutex,struct module *module);
参数说明:
(1)net:是一个网络名字空间namespace,在不同的名字空间里面可以有自己的转发信息库,有自己的一套net_device等等。默认情况下都是使用init_net这个全局变量。
(2)unit:表示netlink协议类型,如NETLINK_TEST、NETLINK_SELINUX。
(3)groups:多播地址。
(4)input:为内核模块定义的netlink消息处理函数,当有消息到达这个netlinksocket时,该input函数指针就会被引用,且只有此函数返回时,调用者的sendmsg才能返回。
(5)cb_mutex:为访问数据时的互斥信号量。
(6)module:一般为THIS_MODULE。
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)
参数说明:
(1)ssk:为函数netlink_kernel_create()返回的socket。
(2)skb:存放消息,它的data字段指向要发送的netlink消息结构,而skb的控制块保存了消息的地址信息,宏NETLINK_CB(skb)就用于方便设置该控制块。
(3)pid:为接收此消息进程的pid,即目标地址,如果目标为组或内核,它设置为0。
(4)nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回;而如果为0,该函数在没有接收缓存可利用定时睡眠。
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)
前面的三个参数与netlink_unicast相同,参数group为接收消息的多播组,该参数的每一个位代表一个多播组,因此如果发送给多个多播组,就把该参数设置为多个多播组组ID的位或。参数allocation为内核内存分配类型,一般地为GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文。
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)
#include #include #include #include #include #include #include #define NETLINK_TEST 25 #define MAX_MSGSIZE 1024 int stringlength(char *s); int err; struct sock *nl_sk = NULL; int flag = 0; //向用户态进程回发消息 void sendnlmsg(char *message, int pid) { struct sk_buff *skb_1; struct nlmsghdr *nlh; int len = NLMSG_SPACE(MAX_MSGSIZE); int slen = 0; if(!message || !nl_sk) { return ; } printk(KERN_ERR "pid:%d\n",pid); skb_1 = alloc_skb(len,GFP_KERNEL); if(!skb_1) { printk(KERN_ERR "my_net_link:alloc_skb error\n"); } slen = stringlength(message); nlh = nlmsg_put(skb_1,0,0,0,MAX_MSGSIZE,0); NETLINK_CB(skb_1).pid = 0; NETLINK_CB(skb_1).dst_group = 0; message[slen]= '\0'; memcpy(NLMSG_DATA(nlh),message,slen+1); printk("my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh)); netlink_unicast(nl_sk,skb_1,pid,MSG_DONTWAIT); } int stringlength(char *s) { int slen = 0; for(; *s; s++) { slen++; } return slen; } //接收用户态发来的消息 void nl_data_ready(struct sk_buff *__skb) { struct sk_buff *skb; struct nlmsghdr *nlh; char str[100]; struct completion cmpl; printk("begin data_ready\n"); int i=10; int pid; skb = skb_get (__skb); if(skb->len >= NLMSG_SPACE(0)) { nlh = nlmsg_hdr(skb); memcpy(str, NLMSG_DATA(nlh), sizeof(str)); printk("Message received:%s\n",str) ; pid = nlh->nlmsg_pid; while(i--) {//我们使用completion做延时,每3秒钟向用户态回发一个消息 init_completion(&cmpl); wait_for_completion_timeout(&cmpl,3 * HZ); sendnlmsg("I am from kernel!",pid); } flag = 1; kfree_skb(skb); } } // Initialize netlink int netlink_init(void) { nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 1, nl_data_ready, NULL, THIS_MODULE); if(!nl_sk){ printk(KERN_ERR "my_net_link: create netlink socket error.\n"); return 1; } printk("my_net_link_4: create netlink socket ok.\n"); return 0; } static void netlink_exit(void) { if(nl_sk != NULL){ sock_release(nl_sk->sk_socket); } printk("my_net_link: self module exited\n"); } module_init(netlink_init); module_exit(netlink_exit); MODULE_AUTHOR("yilong"); MODULE_LICENSE("GPL");
附上内核代码的Makefile文件:
ifneq ($(KERNELRELEASE),) obj-m :=netl.o else KERNELDIR ?=/lib/modules/$(shell uname -r)/build PWD :=$(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif
我们将内核模块insmod后,运行用户态程序,结果如下:
这个结果复合我们的预期,但是运行过程中打印出“state_smg”卡了好久才输出了后面的结果。这时候查看客户进程是处于D状态的(不了解D状态的同学可以google一下)。这是为什么呢?因为进程使用Netlink向内核发数据是同步,内核向进程发数据是异步。什么意思呢?也就是用户进程调用sendmsg发送消息后,内核会调用相应的接收函数,但是一定到这个接收函数执行完用户态的sendmsg才能够返回。我们在内核态的接收函数中调用了10次回发函数,每次都等待3秒钟,所以内核接收函数30秒后才返回,所以我们用户态程序的sendmsg也要等30秒后才返回。相反,内核回发的数据不用等待用户程序接收,这是因为内核所发的数据会暂时存放在一个队列中。
再来回到之前的一个问题,用户态程序的源地址(pid)可以用0吗?我把上面的用户程序的A和C处pid都改为了0,结果一运行就死机了。为什么呢?我们看一下内核代码的逻辑,收到用户消息后,根据消息中的pid发送回去,而pid为0,内核并不认为这是用户程序,认为是自身,所有又将回发的10个消息发给了自己(内核),这样就陷入了一个死循环,而用户态这时候进程一直处于D。
另外一个问题,如果同时启动两个用户进程会是什么情况?答案是再调用bind时出错:“Addressalreadyinuse”,这个同UDP一样,同一个地址同一个port如果没有设置SO_REUSEADDR两次bind就会出错,之后我用同样的方式再Netlink的socket上设置了SO_REUSEADDR,但是并没有什么效果。
之前我们说过UDP可以使用sendmsg/recvmsg也可以使用sendto/recvfrom,那么Netlink同样也可以使用sendto/recvfrom。具体实现如下:
#include #include #include #include #include #include #include #include #include #include #include #define MAX_PAYLOAD 1024 // maximum payload size #define NETLINK_TEST 25 int main(int argc, char* argv[]) { struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; int sock_fd, retval; int state,state_smg = 0; // Create a socket sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); if(sock_fd == -1){ printf("error getting socket: %s", strerror(errno)); return -1; } // To prepare binding memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = 100; src_addr.nl_groups = 0; //Bind retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)); if(retval printf("bind failed: %s", strerror(errno)); close(sock_fd); return -1; } // To orepare create mssage head nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); if(!nlh){ printf("malloc nlmsghdr error!\n"); close(sock_fd); return -1; } memset(&dest_addr,0,sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; dest_addr.nl_groups = 0; nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); nlh->nlmsg_pid = 100; nlh->nlmsg_flags = 0; strcpy(NLMSG_DATA(nlh),"Hello you!"); //send message printf("state_smg\n"); sendto(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,(struct sockaddr*)(&dest_addr),sizeof(dest_addr)); if(state_smg == -1) { printf("get error sendmsg = %s\n",strerror(errno)); } memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD)); //receive message printf("waiting received!\n"); while(1){ printf("In while recvmsg\n"); state=recvfrom(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,NULL,NULL); if(stateprintf("state); } printf("Received message: %s\n",(char *) NLMSG_DATA(nlh)); memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD)); } close(sock_fd); return 0; }
熟悉UDP编程的同学看到这个程序一定很熟悉,除了多了一个Netlink消息头的设置。但是我们发现程序中调用了bind函数,这个函数再UDP编程中的客户端不是必须的,因为我们不需要把UDPsocket与某个地址关联,同时再发送UDP数据包时内核会为我们分配一个随即的端口。但是对于Netlink必须要有这一步bind,因为Netlink内核可不会为我们分配一个pid。再强调一遍消息头(nlmsghdr)中的pid是告诉内核接收端要回复的地址,但是这个地址存不存在内核并不关心,这个地址只有用户端调用了bind后才存在。
再说一个体外话,我们看到这两个例子都是用户态首先发起的,那Netlink是否支持内核态主动发起的情况呢?当然是可以的,只是内核一般需要事件触发,这里,只要和用户态约定号一个地址(pid),内核直接调用netlink_unicast就可以了。
Linux Netlink是一种特殊的套接字类型,它允许内核与用户空间进行双向的异步消息传递。Netlink支持多种协议族,每个协议族负责处理不同的主题,如路由、防火墙、设备监控等。Netlink还提供了一些高级特性,如多播、分组、序列号、确认等。Netlink是一种非常强大和灵活的通信机制,它可以让用户空间的应用程序更方便地访问和控制内核的状态和行为。本文介绍了Netlink的基本概念和使用方法,希望对你有所帮助。
위 내용은 Linux Netlink: 효율적이고 유연한 커널-사용자 공간 통신 메커니즘의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!