-
Linux内核态与用户态进程通信方法-用户上下文
2014-02-19 19:53:09运行在用户上下文环境中的代码是可以阻塞的,这样,便可以使用消息队列和 UNIX 域套接字来实现内核态与用户态的通信。但这些方法的数据传输效率较低,Linux 内核提供 copy_from_user()/copy_to_user() 函数来实现...
运行在用户上下文环境中的代码是可以阻塞的,这样,便可以使用消息队列和 UNIX 域套接字来实现内核态与用户态的通信。但这些方法的数据传输效率较低,Linux 内核提供 copy_from_user()/copy_to_user() 函数来实现内核态与用户态数据的拷贝,但这两个函数会引发阻塞,所以不能用在硬、软中断中。一般将这两个特殊拷贝函数用在类似于系统调用一类的函数中,此类函数在使用中往往"穿梭"于内核态与用户态。此类方法的工作原理路如图【1】。其中相关的系统调用是需要用户自行编写并载入内核。
内核模块注册了一组设置套接字选项的函数使得用户空间进程可以调用此组函数对内核态数据进行读写。源码包含三个文件,imp1.h 是通用头文件,定义了用户态和内核态都要用到的宏。imp1_k.c 是内核模块的源代码。imp1_u.c 是用户态进程的源代码。整个示例演示了由一个用户态进程向用户上下文环境发送一个字符串,内容为"a message from userspace\n"。然后再由用户上下文环境向用户态进程发送一个字符串,内容为"a message from kernel\n"。
1 内核代码:
/*imp1_k.c*/ 2 #ifndef __KERNEL__ 3 #define __KERNEL__ 4 #endif 5 6 #ifndef MODULE 7 #define MODULE 8 #endif 9 10 #include <linux/module.h> 11 #include <linux/kernel.h> 12 #include <linux/types.h> 13 #include <linux/string.h> 14 #include <linux/netfilter_ipv4.h> 15 #include <linux/init.h> 16 #include <asm/uaccess.h> 17 #include "imp1.h" 18 19 #define KMSG "a message from kernel\n" 20 #define KMSG_LEN sizeof("a message from kernel\n") 21 MODULE_LICENSE("Dual BSD/GPL"); 22 static int data_to_kernel(struct sock *sk, int cmd, void *user, 23 unsigned int len) 24 { 25 switch(cmd) 26 { 27 case IMP1_SET: 28 { 29 char umsg[64]; 30 memset(umsg, 0, sizeof(char)*64); 31 copy_from_user(umsg, user, sizeof(char)*64); 32 printk("umsg: %s", umsg); 33 } 34 break; 35 } 36 return 0; 37 } 38 39 static int data_from_kernel(struct sock *sk, int cmd, void *user, int *len) 40 { 41 switch(cmd) 42 { 43 case IMP1_GET: 44 { 45 copy_to_user(user, KMSG, KMSG_LEN); 46 } 47 break; 48 } 49 return 0; 50 } 51 52 static struct nf_sockopt_ops imp1_sockops = 53 { 54 .pf = PF_INET, 55 .set_optmin = IMP1_SET, 56 .set_optmax = IMP1_MAX, 57 .set = data_to_kernel, 58 .get_optmin = IMP1_GET, 59 .get_optmax = IMP1_MAX, 60 .get = data_from_kernel, 61 }; 62 63 static int __init init(void) 64 { 65 return nf_register_sockopt(&imp1_sockops); 66 } 67 68 static void __exit fini(void) 69 { 70 nf_unregister_sockopt(&imp1_sockops); 71 } 72 73 module_init(init); 74 module_exit(fini); 75
二 应用层代码:/*imp1_u.c*/ 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <sys/socket.h> 5 #include <linux/in.h> 6 #include "imp1.h" 7 8 #define UMSG "a message from userspace\n" 9 #define UMSG_LEN sizeof("a message from userspace\n") 10 11 char kmsg[64]; 12 13 int main(void) 14 { 15 int sockfd; 16 int len; 17 18 sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); 19 if(sockfd < 0) 20 { 21 printf("can not create a socket\n"); 22 return -1; 23 } 24 25 /*call function data_to_kernel()*/ 26 setsockopt(sockfd, IPPROTO_IP, IMP1_SET, UMSG, UMSG_LEN); 27 28 len = sizeof(char)*64; 29 30 /*call function data_from_kernel()*/ 31 getsockopt(sockfd, IPPROTO_IP, IMP1_GET, kmsg, &len); 32 printf("kmsg: %s", kmsg); 33 34 close(sockfd); 35 return 0; 36 }
三头文件:1 /*imp1.h*/ 2 #ifndef __IMP1_H__ 3 #define __IMP1_H__ 4 5 #define IMP1_OPS_BASIC 128 6 #define IMP1_SET IMP1_OPS_BASIC 7 #define IMP1_GET IMP1_OPS_BASIC 8 #define IMP1_MAX IMP1_OPS_BASIC+1 9 10 #endif ~
四 编译后运行结果:内核打印:[541380.295993] umsg: a message from userspace
[541390.819256] umsg: a message from userspace
[541392.515414] umsg: a message from userspace
[541393.374753] umsg: a message from userspace
[541393.967123] umsg: a message from userspace应用程序打印:
/imp1$ sudo ./imp1_u
kmsg: a message from kernel -
Linux 用户态与内核态的交互――netlink 篇
2009-10-16 22:44:48netlink 套接字实现的,例如iprote2网络管理工具,它与内核的交互就全部使用了netlink,著名的内核包过滤框架Netfilter在与用户空间的通读,也在最新版本中改变为netlink,无疑,它将是Linux用户态与内核态交流的... -
嵌入式linux消息队列应用
2020-09-27 07:23:47文章目录消息队列机制发送消息三步走接收消息两步走 消息队列机制 在内核空间开辟一片区域,一个进程将数据传到另一个进程,需要完成2次数据复制。第一次,将一个进程的数据复制到内核中,即从用户态转到内核态。第...消息队列机制
在内核空间开辟一片区域,一个进程将数据传到另一个进程,需要完成2次数据复制。第一次,将一个进程的数据复制到内核中,即从用户态转到内核态。第二次,将内核的数据复制到另一个进程,即从内核态转到用户态。与管道相比,简化了对文件的操作。
发送消息三步走
第一步:创建一个对象,msgget。需要定义队列的键值,相当于IPC通信对象中的ID号,需要与其他IPC对象的key不一样,接着还需设置权限、模式。
第二步:复制“消息”到缓存区,则先定义一个结构体
#define MAX_TEXT 512 struct msg_st { long int msg_type; char text[MAX_TEXT]; };
利用stmncmp函数,将数据复制到msg_st的text中。
第三步:发送消息,msgsnd。这个函数的倒数第二个参数一般设置为0,则表示按照队列的先进先出规则来发送消息。而设置为 等于 msg_st中msg_type的值,则只发送与msg_type匹配的消息。
发送消息例程
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/msg.h> #include <errno.h> #define MAX_TEXT 512 struct msg_st{ long int msg_type; char text[MAX_TEXT]; }; int main() { int running = 1; struct msg_st data; char buffer[BUFSIZ]; int msgid = -1; msgid = msgget((key_t)321, 0666 | IPC_CREAT); if (msgid == -1){ fprintf(stderr, "msgget failed with error: %d\n", errno); exit(EXIT_FAILURE); } while(running) { printf("Enter some text: "); fgets(buffer, BUFSIZ, stdin); data.msg_type = 1; strcpy(data.text, buffer); if (msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1) { fprintf(stderr, "msgsnd failed\n"); exit(EXIT_FAILURE); } if (strncmp(buffer, "end", 3) == 0) running = 0; sleep(1); } exit(EXIT_FAILURE); }
接收消息两步走
第一步:创建对象,msgget。主要用来获取相同key值的ID号。
第二步:接收消息,msgrcv。参数用法与msgsnd类似。
接收例程
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <sys/msg.h> struct msg_st { long int msg_type; char text[BUFSIZ]; }; int main() { int running = 1; int msgid = -1; struct msg_st data; long int msgtype = 0; msgid = msgget((key_t)321, 0666 | IPC_CREAT); if (msgid == -1) { fprintf(stderr, "msgrcv failed with errno: %d\n", errno); exit(EXIT_FAILURE); } while(running) { if (msgrcv(msgid, (void*)&data, BUFSIZ, 2, 0) == -1) { fprintf(stderr, "msgrcv failed with errno: %d\n", errno); exit(EXIT_FAILURE); } printf("You wrote: %s\n", data.text); if (strncmp(data.text, "end", 3) == 0) running = 0; } if (msgctl(msgid, IPC_RMID, 0) == -1) { fprintf(stderr, "msgctl(IPC_RMID) failed\n"); exit(EXIT_FAILURE); } exit(EXIT_FAILURE); }
-
Linux、内核态vhost-net
2019-06-13 10:51:24数据直接从tap,通过vhost-net模块吧报文拷贝到虚拟队列的数据区,客户机接收报文 消息通路是当报文从tap设备到达vhost-net时,通知kvm项客户机发中断,通知客户机接收报文 可见中间省略了qemu,主要时因为qemu...数据直接从tap,通过vhost-net模块吧报文拷贝到虚拟队列的数据区,客户机接收报文
消息通路是当报文从tap设备到达vhost-net时,通知kvm项客户机发中断,通知客户机接收报文
可见中间省略了qemu,主要时因为qemu共享了它的内存空间(一些,不说了,),就解放了它
-
内核态和用户态通信(二)--实现
2018-01-21 10:56:44Linux内核态和用户态进程通信方法的提出和实现 用户上下文环境 运行在用户上下文环境中的代码是可以阻塞的,这样,便可以使用消息队列和Unix域套接字来实现内核态和用户态的通信。但这些的数据传输效率较低,linux...本文主要使用netlink套接字实现中断环境与用户态进程通信。
系统环境:基于linux 2.6.32.27 和 linux 3.16.36Linux内核态和用户态进程通信方法的提出和实现
用户上下文环境
运行在用户上下文环境中的代码是可以阻塞的,这样,便可以使用消息队列和Unix域套接字来实现内核态和用户态的通信。但这些的数据传输效率较低,linux内核提供copy_from_user() 和 copy_to_user() 函数来实现内核态与用户态数据的拷贝,但这两个函数会引发阻塞,所以不能用在硬、软中断中。一般将这两个特殊拷贝函数用在类似系统调用一类的函数中,如图,
其中相关的系统调用是需要用户自行编写并载入内核。硬、软中断环境
-
硬中断和软中断环境与用户态进程无丝毫关系,而且运行过程不能阻塞。
软中断、硬中断有一套同步机制 — 自旋锁(spinlock),可以通过自旋锁来实现中断环境和中断环境,中断环境与内核线程的同步,而内核线程是运行在有进程上下文环境中的,这样便可以在内核线程中使用套接字或消息队列来取得用户空间的数据,然后再将数据通过临界区传递给中断过程,如图
因为中断过程不可能无休止地等待用户态进程发送数据,所以要通过一个内核线程来接收用户空间的数据,再通过临界区传给中断过程。中断过程向用户空间的数据发送必须是无阻塞的。这样的通信模型并不令人满意,因为内核线程是和其他用户态进程竞争cpu接收数据的,效率很低,这样中断过程便不能实时地接收来自用户空间的数据。 -
netlink套接字的通信依据是一个对应于进程的标识,一般定义为该进程的ID。当通信的一端处于中断过程时,该标识为0。当使用 netlink 套接字进行通信,通信的双方都是用户态进程,则使用方法类似于消息队列。但通信双方有一端是中断过程,使用方法则不同。netlink 套接字的最大特点是对中断过程的支持,它在内核空间接收用户空间数据时不再需要用户自行启动一个内核线程,而是通过另一个软中断调用用户事先指定的接收函数。工作原理如图
很明显,这里使用了软中断而不是内核线程来接收数据,这样就可以保证数据接收的实时性。
当 netlink 套接字用于内核空间与用户空间的通信时,在用户空间的创建方法和一般套接字使用类似,但内核空间的创建方法则不同。如图
/** *imp2.h */ #ifndef __IMP2_H__ #define __IMP2_H__ #define IMP2_OPS_BASIC 128 #define IMP2_SET IMP2_OPS_BASIC #define IMP2_GET IMP2_OPS_BASIC #define IMP2_MAX (IMP2_OPS_BASIC + 1) #define IMP2_U_PID 0 #define IMP2_K_MSG 1 #define IMP2_CLOSE 2 #define NL_IMP2 31 struct packet_info { __u32 src; __u32 dest; }; #endif
/** *imp2_u.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <signal.h> #include <arpa/inet.h> #include <linux/types.h> #include <linux/netlink.h> #include "imp2.h" struct msg_to_kernel { struct nlmsghdr hdr; }; struct u_packet_info { struct nlmsghdr hdr; struct packet_info icmp_info; }; static int skfd; static void sig_int(int signo) { struct sockaddr_nl kpeer; struct msg_to_kernel message; memset(&kpeer,0,sizeof(kpeer)); kpeer.nl_family = AF_NETLINK; kpeer.nl_pid = 0; kpeer.nl_groups = 0; memset(&message,0,sizeof(message)); message.hdr.nlmsg_len = NLMSG_LENGTH(0); message.hdr.nlmsg_flags = 0; message.hdr.nlmsg_type = IMP2_CLOSE; message.hdr.nlmsg_pid = getpid(); sendto(skfd,&message,message.hdr.nlmsg_len,0,(struct sockaddr *)(&kpeer),sizeof(kpeer)); close(skfd); exit(0); } int main(void) { /* 本地的 */ struct sockaddr_nl local; /* 连线kernel的 */ struct sockaddr_nl kpeer; int kpeerlen; struct msg_to_kernel message; struct u_packet_info info; int sendlen = 0; int rcvlen = 0; struct in_addr addr; skfd = socket(AF_NETLINK,SOCK_RAW,NL_IMP2); if(skfd < 0) { printf("cannot create a netlink socket\n"); exit(0); } memset(&local,0,sizeof(local)); local.nl_family = AF_NETLINK; local.nl_pid = getpid(); local.nl_groups = 0; if(bind(skfd,(struct sockaddr *)&local,sizeof(local)) != 0) { printf("bind() error\n"); return -1; } signal(SIGINT,sig_int); memset(&kpeer,0,sizeof(kpeer)); kpeer.nl_family = AF_NETLINK; kpeer.nl_pid = 0; kpeer.nl_groups = 0; memset(&message,0,sizeof(message)); message.hdr.nlmsg_len = NLMSG_LENGTH(0); message.hdr.nlmsg_flags = 0; message.hdr.nlmsg_type = IMP2_U_PID; message.hdr.nlmsg_pid = local.nl_pid; sendto(skfd,&message,message.hdr.nlmsg_len,0,(struct sockaddr *)&kpeer,sizeof(kpeer)); while(1) { kpeerlen = sizeof(struct sockaddr_nl); rcvlen = recvfrom(skfd,&info,sizeof(struct u_packet_info),0,(struct sockaddr *)&kpeer,&kpeerlen); addr.s_addr = info.icmp_info.src; printf("src:%s,",inet_ntoa(addr)); addr.s_addr = info.icmp_info.dest; printf("dest:%s\n",inet_ntoa(addr)); } return 0; }
/** * imp2_k.c //兼容linux 2.6.32 和 linux 3.16.36 */ #ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/types.h> #include <linux/netdevice.h> #include <linux/skbuff.h> #include <linux/version.h> #include <linux/netfilter_ipv4.h> #include <linux/inet.h> #include <linux/in.h> #include <linux/ip.h> #include <linux/netlink.h> #include <linux/spinlock.h> #include <linux/semaphore.h> #include <linux/semaphore.h> #include <net/sock.h> #include "imp2.h" #define NF_IP_PRE_ROUTING 0 /* 实现从netfilter的NF_IP_PRE_ROUTING点截获的ICMP数据包, 再将数据包的相关信息传递到一个用户态进程 */ static struct sock *nlfd; //是竞争的资源 struct { __u32 pid; rwlock_t lock; }user_proc; //相当于np_log static int send_to_user(struct packet_info *info) { int ret = 0; int size = 0; unsigned char *old_tail = NULL; struct sk_buff *skb = NULL; struct nlmsghdr *nlh = NULL; struct packet_info *packet = NULL; printk("%s,begin ....\n",__FUNCTION__); size = NLMSG_SPACE(sizeof(*info)); //分配一块skb(数据缓存区和skb描述符),大小,GFP自动分配 skb = alloc_skb(size,GFP_ATOMIC); old_tail = skb->tail; nlh = nlmsg_put(skb,0,0,IMP2_K_MSG,(size-sizeof(*nlh)),0); packet = NLMSG_DATA(nlh); memset(packet , 0 ,sizeof(struct packet_info)); packet->src = info->src; packet->dest = info->dest; nlh->nlmsg_len = skb->tail - old_tail; NETLINK_CB(skb).dst_group = 0; read_lock_bh(&user_proc.lock); ret = netlink_unicast(nlfd,skb,user_proc.pid,MSG_DONTWAIT); read_unlock_bh(&user_proc.lock); printk("%s,end....\n",__FUNCTION__); return ret; nlmsg_failure: if(skb) { kfree_skb(skb); } return -1; } static unsigned int get_icmp(unsigned int hooknum,struct sk_buff *skb, const struct net_device *in,const struct net_device *out, int(*okfn)(struct sk_buff *)) { //struct iphdr *iph = (*pskb)->nh.iph; struct iphdr *iph = ip_hdr(skb); //2.6.24开始使用,因为struct sk_buff struct packet_info info; if(iph->protocol == IPPROTO_ICMP) { read_lock_bh(&user_proc.lock); if(user_proc.pid != 0) { info.src = iph->saddr; info.dest= iph->daddr; //printk("%s,src = %u.%u,%u.%u,dst = %u,%u,%u,%u\n",__FUNCTION__,NIPQUAD(info.src), NIPQUAD(info.dest)); read_unlock_bh(&user_proc.lock); send_to_user(&info); } else { //printk("%s, no user process runing....\n",__FUNCTION__); read_unlock_bh(&user_proc.lock); } } return NF_ACCEPT; } static struct nf_hook_ops imp2_ops = { .hook = get_icmp, .pf = PF_INET, .hooknum = NF_IP_PRE_ROUTING, .priority = NF_IP_PRI_FILTER - 1, .owner = THIS_MODULE, }; static void kernel_receive(struct sk_buff *skb) { struct nlmsghdr *nlh = NULL; int len = 0; nlh = nlmsg_hdr(skb); len = skb->len; while(NLMSG_OK(nlh,len)) { write_lock_bh(&user_proc.lock); if(nlh->nlmsg_type == IMP2_U_PID) { user_proc.pid = nlh->nlmsg_pid; } else if(nlh->nlmsg_type == IMP2_CLOSE && nlh->nlmsg_pid == user_proc.pid) { user_proc.pid = 0; } write_unlock_bh(&user_proc.lock); netlink_ack(skb,nlh,0); nlh = NLMSG_NEXT(nlh,len); } } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) struct netlink_kernel_cfg cfg = { .input = kernel_receive, }; #endif static int __init init(void) { rwlock_init(&user_proc.lock); //这里的版本问题需要解决 /*在内核创建一个netlink socket , 协议 NL_IMP2是自定义的,并指示由 kernel_receive接收数据*/ { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) nlfd = netlink_kernel_create(&init_net,NL_IMP2,&cfg); #elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) nlfd = netlink_kernel_create(&init_net ,NL_IMP2 , 0, kernel_receive, NULL, THIS_MODULE); #else nlfd = NULL; #endif } if(!nlfd) { printk("cannot create a netlink socket\n"); return -1; } return nf_register_hook(&imp2_ops); } static void __exit fini(void) { if(nlfd) { netlink_kernel_release(nlfd); } nf_unregister_hook(&imp2_ops); } module_init(init); module_exit(fini); MODULE_LICENSE("GPL");
#Makefile MODULE_NAME := imp2_k obj-m := $(MODULE_NAME).o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean: rm -fr *.ko rm -fr *.o rm -fr *.cmd sender $(MODULE_NAME).mod.c .PHONY:clean aLL
netlink具体结构分析,可参考其他博文
http://blog.csdn.net/luckyapple1028/article/details/50839395http://blog.csdn.net/luckyapple1028/article/details/50936563
转自:http://blog.csdn.net/my_zou/article/details/53840840
-
-
Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存
2012-04-19 22:43:28Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存参考:《linux编程从入门到精通》,《Linux C程序设计大全》,《unix环境高级编程》参考:C和指针学习 说明:本文非常的长,也是为了便于查找和比较,... -
Linux进程间通信方式--信号,管道,消息队列,信号量,共享内存
2015-10-10 18:09:281、概述 ...消息队列 在硬、软中断中无法无阻塞地接收数据。 信号量 无法介于内核态和用户态使用。 内存共享 需要信号量辅助,而信号量又无法使用。 套接字 在硬、软中断中无法无阻 -
操作系统学习:Linux内核并发机制
2018-02-14 15:13:39Linux包含了在其他UNIX系统中出现的所有并发机制,其中包括管道、消息队列、共享内存和信号,除此之外,Linux2.6还包含了一套丰富的并发机制,这套机制是特别为内核态线程准备的。换言之,它们是内核中的并发机制,... -
LINUX/进程通信:共享内存/消息队列/信号量
2020-04-10 19:42:40管道通信:这种通信方式,涉及两次用户态与内核态之间的拷贝,将数据写入管道,再从管道中读取数据。 共享内存:直接通过虚拟地址访问物理内存实现对共享内存中数据的操作,相比与管道通信少了两次用户态与内... -
深入理解LInux内核-进程通信
2015-08-22 18:15:003、消息:允许进程在预定义的消息队列中读和写消息来交换消息(小块数据)。Linux提供两种不同的消息版本:System V IPC;POSIX消息4、共享内存区:允许进程通过共享内存块来交换信息。在必须共享大量数据的应用... -
Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存,匿名管道...
2017-05-29 20:08:00参考:《linux编程从入门到精通》,《Linux C程序设计大全》,《unix环境高级编程》 ...Linux 传统的进程间通信有很多,如各类管道、消息队列、内存共享、信号量等等。但它们都无法介于内核态与用户态使用... -
linux用户与内核通信方式
2019-09-26 11:14:29消息队列、socket:copy_from_user()/copy_to_user() 以上同步通信方式,由用户态主动发起向内核态的通信,内核无法主动发起通信 netlink:异步全双工的通信方式,它支持由内核态主动发起通信,内核为Netlink通信... -
【Linux学习笔记】----进程间通信(管道(命名管道,匿名管道)、共享内存、消息队列、信号量机制)
2020-07-23 00:41:42通信过程需要用户态到内核态,再到用户态的情况。 特性: (1).半双工通信方式,(可双向传递数据,同一时刻只能单向) (2).生命周期随进程,进程退出,管道释放。 (3).以只读方式打开会阻塞,直到文件以写的...
-
A data hiding approach for the self-security of iris recognition
-
叠栅条纹信号细分误差的一种动态补偿方法
-
python 使用 enumerate简化遍历
-
如何绘制平台框架的设计图:使用UML工具
-
盘古开源分析:Filecoin价格暴涨之后,接下来该如何走?
-
Control of soliton interactions by use of super-Gaussian sliding-frequency filters
-
PPTP_NNN 服务生产环境实战教程
-
【Java基础】Java中的异常技术详解
-
就如何提高燃气蒸汽发生器热效率,厂家给出的几点建议
-
Bootstrap中的工具提示
-
【硬核】一线Python程序员实战经验分享(1)
-
WLAN的三极化和多频带天线
-
Mysql 索引的设计原则
-
Liunx 优化思路与实操步骤
-
朱老师c++课程第3部分-3.5STL的其他容器讲解
-
分布式事务之解决方案(XA和2PC)
-
MongoDB基本使用教程
-
小型化差分馈送双频可植入天线:设计,实现和体外测试
-
嵌套块元素垂直外边距的塌陷 嵌套块元素子元素使用margin垂直移动
-
Java Api(应用程序接口)的使用