Linux内核空间到用户空间的共享内存映射

 

当内核空间和用户空间存在大量数据交互时, 共享内存映射就成了这种情况下的不二选择; 它能够最大限度的降低内核空间和用户空间之间的数据拷贝, 从而大大提高系统的性能.

 

以下是创建从内核空间到用户空间的共享内存映射的模板代码(在内核2.6.18和2.6.32上测试通过):

1.内核空间分配内存:

#include <linux/types.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>

int mmap_alloc(int require_buf_size)
{
  struct page *page;
 
  mmap_size = PAGE_ALIGN(require_buf_size);

#if USE_KMALLOC //for kmalloc
  mmap_buf = kzalloc(mmap_size, GFP_KERNEL);
  if (!mmap_buf) {
    return -1;
  }
  for (page = virt_to_page(mmap_buf ); page < virt_to_page(mmap_buf + mmap_size); page++) {
    SetPageReserved(page);
  }
#else //for vmalloc
  mmap_buf  = vmalloc(mmap_size);
  if (!mmap_buf ) {
    return -1;
  }
  for (i = 0; i < mmap_size; i += PAGE_SIZE) {
    SetPageReserved(vmalloc_to_page((void *)(((unsigned long)mmap_buf) + i)));
  }
#endif

  return 0;
}

2.用户空间映射内存

int test_mmap()
{
  mmap_fd = open("/dev/mmap_dev", O_RDWR);
  mmap_ptr = mmap(NULL, mmap_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, mmap_fd, 0);
  if (mmap_ptr == MAP_FAILED) {
    return -1;
  }
  return 0;
}

 

3.内核空间映射内存: 实现file_operations的mmap函数
static int mmap_mmap(struct file *filp, struct vm_area_struct *vma)
{
  int ret;
  unsigned long pfn;
  unsigned long start = vma->vm_start;
  unsigned long size = PAGE_ALIGN(vma->vm_end - vma->vm_start);

  if (size > mmap_size || !mmap_buf) {
    return -EINVAL;
  }

#if USE_KMALLOC
  return remap_pfn_range(vma, start, (virt_to_phys(mmap_buf) >> PAGE_SHIFT), size, PAGE_SHARED);
#else
  /* loop over all pages, map it page individually */
  while (size > 0) {
          pfn = vmalloc_to_pfn(mmap_buf);
          if ((ret = remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED)) < 0) {
            return ret;
          }
          start += PAGE_SIZE;
          mmap_buf += PAGE_SIZE;
          size -= PAGE_SIZE;
  }
#endif
  return 0;
}

static const struct file_operations mmap_fops = {
  .owner = THIS_MODULE,
  .ioctl = mmap_ioctl,
  .open = mmap_open,
  .mmap = mmap_mmap,
  .release = mmap_release,
};

4.用户空间撤销内存映射

void test_munmap()
{   
  munmap(mmap_ptr, mmap_size);
  close(mmap_fd);
}


5.内核空间释放内存; 必须在用户空间执行munmap系统调用后才能释放

void mmap_free()
{
#if USE_KMALLOC
  struct page *page;
  for (page = virt_to_page(mmap_buf); page < virt_to_page(mmap_buf + mmap_size); page++) {
    ClearPageReserved(page);
  }
  kfree(mmap_buf);
#else
  int i;
  for (i = 0; i < mmap_size; i += PAGE_SIZE) {
    ClearPageReserved(vmalloc_to_page((void *)(((unsigned long)mmap_buf) + i)));
  }
  vfree(mmap_buf);
#endif
  mmap_buf = NULL;
}

 

 

 

 

 

 

 

 

基于NETLINK的内核与用户空间共享内存的实现

author:bripengandre Email:bripengandre@126.com

一、前言
    前些日子,开发中用到了netlink来实现内核与用户空间共享内存,写点笔记与大家分享。因为我对这块也不了解,写出来的东西一定存在很多错误,请大家批评指正~
    内核与用户空间共享内存的关键是,用户空间必须得知共享内存的起始地址,这就要求内核空间应该有一种通信机制来通知用户空间。已经有Godbach版主等人用proc文件系统实现了(可以google '共享内存 内核 用户空间'),很显然任何内核空间与用户空间的通信方法都可资利用。本文主要讲基于NETLINK机制的实现。

二、NETLINK简介
    netlink在linux的内核与用户空间通信中用得很多(但具体例子我举不出,因为我不清楚~~请google之),其最大优势是接口与网络编程中的socket相似,且内核要主动发信息给用户空间很方便。
   但通过实践,我发现netlink通信机制的最大弊病在于其在各内核版本中接口变化太大,让人难以适从(可从后文列出的源码中的kernel_receive的声明窥一斑)。
  既然涉及到内核与用户空间两个空间,就应该在两个空间各有一套接口。用户空间的接口很简单,与一般的socket接口相似,内核空间则稍先复杂,但简单的应用只需简单地了解即可:首先也是建立描述符,建立描述符时会注册一个回调函数(源码中的kernel_receive即是),然后当用户空间有消息发过来时,我们的函数将被调用,显然在这个函数里我们可做相应的处理;当内核要主动发消息给用户进程时,直接调用一个类send函数即可(netlink_unicast系列函数)。当然这个过程中,有很多结构体变量需要填充。具体用法请google,我差不多忘光了~。

三、基于netlink的共享内存   
   这里的共享内存是指内核与用户空间,而不是通常的用户进程间的。
   大概流程如下。
  内核:__get_free__pages分配连续的物理内存页(貌似返回的其实是虚拟地址)-->SetPageReserved每一页(每一页都需这个操作,参见源码)-->如果用户空间通过netlink要求获取共享内存的起始物理地址,将__get_free__pages返回的地址__pa下发给用户空间。
   用户空间:open "/dev/shm"(一个读写物理内存的设备,具体请google"linux读写物理内存")-->发netlink消息给内核,得到共享内存的起始物理地址-->mmap上步得到的物理地址。

四、源码说明
   正如二中提到的,netlink接口在各版本中接口变化很大,本人懒惰及时间紧,只实验了比较新的内核2.6.25,源码如需移植到老版本上,需要一些改动,敬请原谅。
   另外,由于本源码是从一个比较大的程序里抠出来的,所以命名什么的可能有点怪异~
  源码包括shm_k.c(内核模块)和用户空间程序(shm_u.c)两部分。shm_k.c模块的工作是:分配8KB内存供共享,并把前几十个字节置为“hello, use share memory withnetlink"字样。shm_u.c工作是:读取共享内存的前几十个字节,将内容输出在stdout上。
    特别说明:该程序只适用于2.6.25左右的新版本!用__get_free_pages分配连续内存时,宜先用get_order获取页数,然后需将各页都SetPageReserved下,同样地,释放内存时,需要对每一页调用ClearPageReserved。
    我成功用该程序分配了4MB共享内存,运行还比较稳定。因为linux内核的默认设置,一般情况下用get_free_pages只能分配到4MB内存左右,如需增大,可能需改相应的参数并重新编译内核

五、运行结果
    我是在mpc8377上运行的,全过程如下(有少许编辑,如dmesg信息只列出新增的)。
-sh-2.05b# insmod shm_k.ko
-sh-2.05b# dmesg
SHM_TEST: linux version:00020619
SHM_TEST: init_netlink ok.
SHM_TEST: size=00002000, page_cnt=2
SHM_TEST: __get_free_pages ok.
SHM_TEST: init_mem_pool ok.
-sh-2.05b# ./shm_u
SHM_TEST: 24, errno=0.Success, 0defe000, 00002000
the first 30 bytes of shm are: hello, use share memory with netlink
-sh-2.05b# dmesg
SHM_TEST: begin kernel_receive
SHM_TEST: receiv TA_GET_SHM_INFO
SHM_TEST: nlk_get_mem_addr ok
-sh-2.05b# rmmod shm_k
-sh-2.05b# dmesg
SHM_TEST: ta_exit ok.
-sh-2.05b#




六、内核源码

   1、common.h(内核与用户空间都用到的头文件)

  1. #ifndef _COMMON_H_
  2. #define _COMMON_H_
  3. /* protocol type */
  4. #define SHM_NETLINK 30
  5. /* message type */
  6. #define SHM_GET_SHM_INFO 1
  7. /* you can add othe message type here */
  8. #define SHM_WITH_NETLINK "hello, use share memory with netlink"

  9. typedef struct _nlk_msg
  10. {
  11.     union _data
  12.     {
  13.         struct _shm_info
  14.         {
  15.             uint32_t mem_addr;
  16.             uint32_t mem_size;
  17.         }shm_info;
  18.         
  19.         /* you can add other content here */
  20.     }data;
  21. }nlk_msg_t;

  22. #endif /* _COMMON_H_ */
复制代码

2、shm_k.c(内核模块)

  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/version.h>
  4. #include <linux/types.h>
  5. #include <linux/skbuff.h>
  6. #include <linux/netlink.h>
  7. #include <net/sock.h>
  8. #include <linux/spinlock.h>
  9. #include "common.h"
  10. #define SHM_TEST_DEBUG
  11. #ifdef SHM_TEST_DEBUG
  12. #define SHM_DBG(args...) printk(KERN_DEBUG "SHM_TEST: " args)
  13. #else
  14. #define SHM_DBG(args...)
  15. #endif
  16. #define SHM_ERR(args...) printk(KERN_ERR "SHM_TEST: " args)

  17. static struct _glb_para
  18. {
  19.     struct _shm_para
  20.     {
  21.         uint32_t mem_addr; /* memory starting address */
  22.         uint32_t mem_size; /* memory size */
  23.         uint32_t page_cnt; /* memory page count*/
  24.         uint16_t order;
  25.         uint8_t mem_init_flag; /* 0, init failed; 1, init successful */
  26.     }shm_para;
  27.    
  28.     struct sock *nlfd; /* netlink descriptor */
  29.     uint32_t pid; /* user-space process's pid */
  30.     rwlock_t lock;
  31. }glb_para;

  32. static void init_glb_para(void);
  33. static int init_netlink(void);
  34. static void kernel_receive(struct sk_buff* __skb);
  35. static int nlk_get_mem_addr(struct nlmsghdr *pnhdr);
  36. static void clean_netlink(void);
  37. static int init_shm(void);
  38. static void clean_shm(void);
  39. static int  __init init_shm_test(void);
  40. static void clean_shm_test(void);

  41. static void init_glb_para(void)
  42. {
  43.     memset(&glb_para, 0, sizeof(glb_para));
  44. }

  45. static int init_netlink(void)
  46. {
  47.     rwlock_init(&glb_para.lock);
  48.     SHM_DBG("linux version:%08x\n", LINUX_VERSION_CODE);
  49. #if(LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18))
  50.     glb_para.nlfd = netlink_kernel_create(SHM_NETLINK, kernel_receive);
  51. #elif(LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24))
  52.     glb_para.nlfd = netlink_kernel_create(SHM_NETLINK, 0, kernel_receive, THIS_MODULE));
  53. #else
  54.     glb_para.nlfd = netlink_kernel_create(&init_net, SHM_NETLINK, 0, kernel_receive, NULL, THIS_MODULE);
  55. #endif
  56.    
  57.     if(glb_para.nlfd == NULL)
  58.     {
  59.         SHM_ERR("init_netlink::netlink_kernel_create error\n";);
  60.         return (-1);
  61.     }
  62.    
  63.     return (0);
  64. }

  65. static void kernel_receive(struct sk_buff* __skb)
  66. {
  67. struct sk_buff *skb;
  68.     struct nlmsghdr *nlh = NULL;
  69.     int invalid;
  70.    
  71. SHM_DBG("begin kernel_receive\n";);
  72. skb = skb_get(__skb);
  73. invalid = 0;
  74. if(skb->len >= sizeof(struct nlmsghdr))
  75.     {
  76.         nlh = (struct nlmsghdr *)skb->data;
  77.         if((nlh->nlmsg_len >= sizeof(struct nlmsghdr))
  78.             && (skb->len >= nlh->nlmsg_len))
  79.         {
  80.             switch(nlh->nlmsg_type)
  81.             {
  82.                 case SHM_GET_SHM_INFO:
  83.                     SHM_DBG("receiv TA_GET_SHM_INFO\n";);
  84.               nlk_get_mem_addr(nlh);
  85.                     break;
  86.                 default:
  87.                     break;
  88.             }
  89.      }
  90. }
  91.     kfree_skb(skb);
  92. }

  93. static int nlk_get_mem_addr(struct nlmsghdr *pnhdr)
  94. {
  95.     int ret, size;
  96.     unsigned char *old_tail;
  97.     struct sk_buff *skb;
  98.     struct nlmsghdr *nlh;
  99.     struct _nlk_msg *p;
  100.    
  101.    
  102.     glb_para.pid = pnhdr->nlmsg_pid; /* get the user-space process's pid */
  103.    
  104.     size = NLMSG_SPACE(sizeof(struct _nlk_msg)); /* compute the needed memory size */
  105.     if( (skb = alloc_skb(size, GFP_ATOMIC)) == NULL) /* allocate memory */
  106.     {
  107.         SHM_DBG("nlk_hello_test::alloc_skb error.\n";);
  108.         return (-1);
  109.     }
  110.    
  111.     old_tail = skb->tail;
  112.     nlh = NLMSG_PUT(skb, 0, 0, SHM_GET_SHM_INFO, size-sizeof(struct nlmsghdr)); /* put netlink message structure into memory */
  113.    
  114.     p = NLMSG_DATA(nlh); /* get netlink message body pointer */
  115.     p->data.shm_info.mem_addr = __pa(glb_para.shm_para.mem_addr); /*__pa:convert virtual address to physical address, which needed by/dev/mem */
  116.     p->data.shm_info.mem_size = glb_para.shm_para.mem_size;
  117.    
  118.     nlh->nlmsg_len = skb->tail - old_tail;
  119.     NETLINK_CB(skb).pid = 0;      /* from kernel */
  120.     NETLINK_CB(skb).dst_group = 0;
  121.     read_lock_bh(&glb_para.lock);
  122.     ret = netlink_unicast(glb_para.nlfd, skb, glb_para.pid, MSG_DONTWAIT); /* send message to user-space process */
  123.     read_unlock_bh(&glb_para.lock);
  124.     SHM_DBG("nlk_get_mem_addr ok.\n";);
  125.     return (ret);
  126.    
  127. nlmsg_failure:
  128.     SHM_DBG("nlmsg_failure\n";);
  129.     if(skb)
  130.     {
  131.         kfree_skb(skb);
  132.     }
  133.     return (-1);
  134. }

  135. static void clean_netlink(void)
  136. {
  137.     if(glb_para.nlfd != NULL)
  138.     {
  139.         sock_release(glb_para.nlfd->sk_socket);
  140.     }
  141. }

  142. static int init_shm(void)
  143. {
  144.     int i;
  145.     char *p;
  146.     uint32_t page_addr;
  147.    
  148.     glb_para.shm_para.order = get_order(1024*8); /* allocate 8kB */
  149.     glb_para.shm_para.mem_addr = __get_free_pages(GFP_KERNEL, glb_para.shm_para.order);
  150.     if(glb_para.shm_para.mem_addr == 0)
  151.     {
  152.         SHM_ERR("init_mem_pool::__get_free_pages error.\n";);
  153.         glb_para.shm_para.mem_init_flag = 0;
  154.         return (-1);
  155.     }
  156.     else
  157.     {
  158.         glb_para.shm_para.page_cnt = (1<<glb_para.shm_para.order);
  159.         glb_para.shm_para.mem_size = glb_para.shm_para.page_cnt*PAGE_SIZE;
  160.         glb_para.shm_para.mem_init_flag = 1;
  161.         page_addr = glb_para.shm_para.mem_addr;
  162.         SHM_DBG("size=%08x, page_cnt=%d\n", glb_para.shm_para.mem_size, glb_para.shm_para.page_cnt);
  163.         for(i = 0; i <  glb_para.shm_para.page_cnt; i++)
  164.         {
  165.             SetPageReserved(virt_to_page(page_addr)); /* reserved for used */
  166.             page_addr += PAGE_SIZE;
  167.         }
  168.         
  169.         p = (char *)glb_para.shm_para.mem_addr;
  170.         strcpy(p, SHM_WITH_NETLINK); /* write */
  171.         SHM_DBG("__get_free_pages ok.\n";);
  172.     }
  173.    
  174.     return (0);
  175. }

  176. static void clean_shm(void)
  177. {
  178.     int i;
  179.     uint32_t page_addr;
  180.    
  181.     if(glb_para.shm_para.mem_init_flag == 1)
  182.     {
  183.         page_addr = glb_para.shm_para.mem_addr;
  184.         for(i = 0; i < glb_para.shm_para.page_cnt; i++)
  185.         {
  186.             ClearPageReserved(virt_to_page(page_addr));
  187.             page_addr += PAGE_SIZE;
  188.         }
  189.         free_pages(glb_para.shm_para.mem_addr, glb_para.shm_para.order);
  190.     }
  191. }

  192. static int  __init init_shm_test(void)
  193. {
  194.     init_glb_para();
  195.     if(init_netlink() < 0)
  196.     {
  197.         SHM_ERR("init_shm_test::init_netlink error.\n";);
  198.         return (-1);
  199.     }
  200.     SHM_DBG("init_netlink ok.\n";);
  201.    
  202.     if(init_shm() < 0)
  203.     {
  204.         SHM_ERR("init_shm_test::init_mem_pool error.\n");
  205.         clean_shm_test();
  206.         return (-1);
  207.     }
  208.     SHM_DBG("init_mem_pool ok.\n");
  209.    
  210.     return (0);
  211. }

  212. static void clean_shm_test(void)
  213. {
  214.     clean_shm();
  215.     clean_netlink();
  216.    
  217.     SHM_DBG("ta_exit ok.\n");
  218. }
  219. module_init(init_shm_test);
  220. module_exit(clean_shm_test);
  221. MODULE_LICENSE("GPL");
  222. MODULE_AUTHOR("bripengandre (bripengandre@126.com)");
  223. MODULE_DESCRIPTION("Memory Share between user-space and kernel-space with netlink.");
复制代码

3、shm_u.c(用户进程)

  1. #include <stdio.h>
  2. #include <sys/socket.h>
  3. #include <arpa/inet.h>
  4. #include <linux/netlink.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <errno.h>
  8. #include <sys/types.h>
  9. #include <unistd.h>
  10. #include <fcntl.h>
  11. #include <sys/mman.h>
  12. #include "common.h"

  13. /* netlink */
  14. #define MAX_SEND_BUF_SIZE 2500
  15. #define MAX_RECV_BUF_SIZE 2500
  16. #define SHM_TEST_DEBUG
  17. #ifdef SHM_TEST_DEBUG
  18. #define SHM_DBG(args...) fprintf(stderr, "SHM_TEST: " args)
  19. #else
  20. #define SHM_DBG(args...)
  21. #endif
  22. #define SHM_ERR(args...) fprintf(stderr, "SHM_TEST: " args)
  23. struct _glb_para
  24. {
  25.     struct _shm_para
  26.     {
  27.         uint32_t mem_addr;
  28.         uint32_t mem_size;
  29.     }shm_para;
  30.    
  31.     int nlk_fd;
  32.     char send_buf[MAX_SEND_BUF_SIZE];
  33.     char recv_buf[MAX_RECV_BUF_SIZE];
  34. }glb_para;


  35. static void init_glb_para(void);
  36. static  int create_nlk_connect(void);
  37. static int nlk_get_shm_info(void);
  38. static int init_mem_pool(void);

  39. int main(int argc ,char *argv[])
  40. {  
  41.     char *p;
  42.    
  43.     init_glb_para();
  44.     if(create_nlk_connect() < 0)
  45.     {
  46.         SHM_ERR("main::create_nlk_connect error.\n");
  47.         return (1);
  48.     }
  49.    
  50.     if(nlk_get_shm_info() < 0)
  51.     {
  52.         SHM_ERR("main::nlk_get_shm_info error.\n");
  53.         return (1);
  54.     }
  55.    
  56.     init_mem_pool();
  57.     /* printf the first 30 bytes */
  58.     p = (char *)glb_para.shm_para.mem_addr;
  59.     p[strlen(SHM_WITH_NETLINK)] = '\0';
  60.     printf("the first 30 bytes of shm are: %s\n", p);
  61.    
  62.     return (0);
  63. }

  64. static void init_glb_para(void)
  65. {
  66.     memset(&glb_para, 0, sizeof(glb_para));
  67. }

  68. static  int create_nlk_connect(void)
  69. {
  70.     int sockfd;
  71.     struct sockaddr_nl local;
  72.    
  73.     sockfd = socket(PF_NETLINK, SOCK_RAW, SHM_NETLINK);
  74.     if(sockfd < 0)
  75.     {
  76.         SHM_ERR("create_nlk_connect::socket error:%s\n", strerror(errno));
  77.         return (-1);
  78.     }
  79.     memset(&local, 0, sizeof(local));
  80.     local.nl_family = AF_NETLINK;
  81.     local.nl_pid = getpid();
  82.     local.nl_groups = 0;
  83.     if(bind(sockfd, (struct sockaddr*)&local, sizeof(local)) != 0)
  84.     {
  85.         SHM_ERR("create_nlk_connect::bind error: %s\n", strerror(errno));
  86.         return -1;
  87.     }
  88.    
  89.     glb_para.nlk_fd = sockfd;
  90.    
  91.     return (sockfd);
  92. }

  93. static int nlk_get_shm_info(void)
  94. {
  95.     struct nlmsghdr *nlh;
  96.     struct _nlk_msg *p;
  97.     struct sockaddr_nl kpeer;
  98.     int recv_len, kpeerlen;
  99.    
  100.     memset(&kpeer, 0, sizeof(kpeer));
  101.     kpeer.nl_family = AF_NETLINK;
  102.     kpeer.nl_pid = 0;
  103.     kpeer.nl_groups = 0;
  104.    
  105.     memset(glb_para.send_buf, 0, sizeof(glb_para.send_buf));
  106.     nlh = (struct nlmsghdr *)glb_para.send_buf;
  107.     nlh->nlmsg_len = NLMSG_SPACE(0);
  108.     nlh->nlmsg_flags = 0;
  109.     nlh->nlmsg_type = SHM_GET_SHM_INFO;
  110.     nlh->nlmsg_pid = getpid();
  111.     sendto(glb_para.nlk_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr*)&kpeer, sizeof(kpeer));
  112.     memset(glb_para.send_buf, 0, sizeof(glb_para.send_buf));
  113.     kpeerlen = sizeof(struct sockaddr_nl);
  114. recv_len = recvfrom(glb_para.nlk_fd, glb_para.recv_buf,sizeof(glb_para.recv_buf), 0, (struct sockaddr*)&kpeer,&kpeerlen);
  115. p = NLMSG_DATA((struct nlmsghdr *) glb_para.recv_buf);
  116. SHM_DBG("%d, errno=%d.%s, %08x, %08x\n", recv_len, errno,strerror(errno),p->data.shm_info.mem_addr,  p->data.shm_info.mem_size);
  117. glb_para.shm_para.mem_addr = p->data.shm_info.mem_addr;
  118. glb_para.shm_para.mem_size = p->data.shm_info.mem_size;

  119. return (0);
  120. }


  121. static int init_mem_pool(void)
  122. {
  123.     int map_fd;
  124.     void *map_addr;
  125.    
  126.     map_fd = open("/dev/mem", O_RDWR);
  127.     if(map_fd < 0)
  128.     {
  129.         SHM_ERR("init_mem_pool::open %s error: %s\n", "/dev/mem", strerror(errno));
  130.         return (-1);
  131.     }
  132.       
  133.     map_addr = mmap(0, glb_para.shm_para.mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, glb_para.shm_para.mem_addr);
  134.     if(map_addr == NULL)
  135.     {
  136.         SHM_ERR("init_mem_pool::mmap error: %s\n", strerror(errno));
  137.         return (-1);
  138.     }
  139.     glb_para.shm_para.mem_addr = (uint32_t)map_addr;
  140.     return (0);
  141. }
复制代码



4、Makefile

#PREFIX = powerpc-e300c3-linux-gnu-
CC  ?= $(PREFIX)gcc

KERNELDIR ?= /lib/modules/`uname -r`/build

all: modules app
obj-m:= shm_k.o
module-objs := shm_k.c

modules:
make -C $(KERNELDIR) M=`pwd` modules

app: shm_u.o
$(CC) -o shm_u shm_u.c

clean:
rm -rf *.o Module.symvers modules.order shm_u shm_k.ko shm_k.mod.c .tmp_versions .shm_k.*


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值