Linux内核IPC源码——共享内存

介绍

我看的是linux-4.2.3的源码。参考了《边干边学——Linux内核指导》(鬼畜的书名)第16章内容,他们用的是2.6.15的内核源码。

现在linux中可以使用共享内存的方式有两种

  1. POSIX的shm_open()/dev/shm/下打开一个文件,用mmap()映射到进程自己的内存地址

  2. System V的shmget()得到一个共享内存对象的id,用shmat()映射到进程自己的内存地址

POSIX的实现是基于tmpfs的,函数都写在libc里,没什么好说的,主要还是看System V的实现方式。在System V中共享内存属于IPC子系统。所谓ipc,就是InterProcess Communication即进程间通信的意思,System V比前面的Unix增加了3中进程间通信的方式,共享内存、消息队列、信号量,统称IPC。主要代码在以下文件中

  • ipc/shm.c

  • include/linux/shm.h

  • ipc/util.c

  • ipc/util.h

  • include/linux/ipc.h

同一块共享内存在内核中至少有3个标识符

  1. IPC对象id(IPC对象是保存IPC信息的数据结构)

  2. 进程虚拟内存中文件的inode,即每个进程中的共享内存也是以文件的方式存在的,但并不是显式的。可以通过某个vm_area_struct->vm_file->f_dentry->d_inode->i_ino表示

  3. IPC对象的key。如果在shmget()中传入同一个key可以获取到同一块共享内存。但由于key是用户指定的,可能重复,而且也很少程序写之前会约定一个key,所以这种方法不是很常用。通常System V这种共享内存的方式是用于有父子关系的进程的。或者用ftok()函数用路径名来生成一个key。

首先看一下在内核中表示一块共享内存的数据结构,在include/linux/shm.h

/* */是内核源码的注释,// 是我的注释

 
  
  1. struct shmid_kernel /* private to the kernel */

  2. {

  3. struct kern_ipc_perm shm_perm; // 权限,这个结构体中还有一些重要的内容,后面会提到

  4. struct file *shm_file; // 表示这块共享内存的内核文件,文件内容即共享内存的内容

  5. unsigned long shm_nattch; // 连接到这块共享内存的进程数

  6. unsigned long shm_segsz; // 大小,字节为单位

  7. time_t shm_atim; // 最后一次连接时间

  8. time_t shm_dtim; // 最后一次断开时间

  9. time_t shm_ctim; // 最后一次更改信息的时间

  10. pid_t shm_cprid; // 创建者进程id

  11. pid_t shm_lprid; // 最后操作者进程id

  12. struct user_struct *mlock_user;

  13. /* The task created the shm object. NULL if the task is dead. */

  14. struct task_struct *shm_creator;

  15. struct list_head shm_clist; /* list by creator */

  16. };

再看一下struct shmid_kernel中存储权限信息的shm_perm,在include/linux/ipc.h

 
  
  1. /* used by in-kernel data structures */

  2. struct kern_ipc_perm

  3. {

  4. spinlock_t lock;

  5. bool deleted;

  6. int id; // IPC对象id

  7. key_t key; // IPC对象键值,即创建共享内存时用户指定的

  8. kuid_t uid; // IPC对象拥有者id

  9. kgid_t gid; // 组id

  10. kuid_t cuid; // 创建者id

  11. kgid_t cgid;

  12. umode_t mode;

  13. unsigned long seq;

  14. void *security;

  15. };

为啥有这样一个struct呢?因为这些权限、id、key是IPC对象都有的属性,所以比如表示semaphore的结构struct semid_kernel中也有一个这样的struct kern_ipc_perm。然后在传递IPC对象的时候,传的也是struct kern_ipc_perm的指针,再用container_of这样的宏获得外面的struct,这样就能用同一个函数操作3种IPC对象,达到较好的代码重用。

接下来我们看一下共享内存相关函数。首先它们都是系统调用,对应的用户API在libc里面,参数是相同的,只是libc中的API做了一些调用系统调用需要的日常工作(保护现场、恢复现场之类的),所以就直接看这个系统调用了。

声明在include/linux/syscalls.h

 
  
  1. asmlinkage long sys_shmat(int shmid, char __user *shmaddr, int shmflg);

  2. asmlinkage long sys_shmget(key_t key, size_t size, int flag);

  3. asmlinkage long sys_shmdt(char __user *shmaddr);

  4. asmlinkage long sys_shmctl(int shmid, int cmd, struct shmid_ds __user *buf);

定义在ipc/shm.c

shmget

 
  
  1. SYSCALL_DEFINE3(shmget, key_t, key, size_t, size, int, shmflg)

  2. {

  3. struct ipc_namespace *ns;

  4. static const struct ipc_ops shm_ops = {

  5. .getnew = newseg,

  6. .associate = shm_security,

  7. .more_checks = shm_more_checks,

  8. };

  9. struct ipc_params shm_params;

  10. ns = current->nsproxy->ipc_ns;

  11. shm_params.key = key;

  12. shm_params.flg = shmflg;

  13. shm_params.u.size = size;

  14. return ipcget(ns, &shm_ids(ns), &shm_ops, &shm_params);

  15. }

首先看到这个函数定义可能会很奇怪,不过这个SYSCALL_DEFINE3的宏展开来最后形式肯定和.h文件中声明的一样,即还是long sys_shmget(key_t key, size_t size, int flag)这个宏是为了修一个bug,纯粹黑科技,这里不提它。

然后这里实际调用的函数是ipcget()。为了统一一个ipc的接口也是煞费苦心,共享内存、信号量、消息队列三种对象创建的时候都会调用这个函数,但其实创建的逻辑并不在这里。而在shm_ops中的三个函数里。

namespace

顺便提一下其中的current->nsproxy->ipc_ns。这个的类型是struct ipc_namespace。它是啥呢?我们知道,共享内存这些进程间通信的数据结构是全局的,但有时候需要把他们隔离开,即某一组进程并不知道另外的进程的共享内存,它们只希望在组内共用这些东西,这样就不会与其他进程冲突。于是就煞费苦心在内核中加了一个namespace。只要在clone()函数中加入CLONE_NEWIPC标志就能创建一个新的IPC namespace。

那么这个IPC namespace和我们的共享内存的数据结构有什么关系呢,可以看一下结构体

 
  
  1. struct ipc_ids {

  2. int in_use;

  3. unsigned short seq;

  4. struct rw_semaphore rwsem;

  5. struct idr ipcs_idr;

  6. int next_id;

  7. };

  8. struct ipc_namespace {

  9. atomic_t count;

  10. struct ipc_ids ids[3];

  11. ...

  12. };

比较重要的是其中的ids,它存的是所用IPC对象的id,其中共享内存都存在ids[2]中。而在ids[2]中真正负责管理数据的是ipcs_idr,它也是内核中一个煞费苦心弄出来的id管理机制,一个id可以对应任意唯一确定的对象。把它理解成一个数组就好。它们之间的关系大概如下图所示。

 
  
  1. [0] struct kern_ipc_perm <==> struct shmid_kernel

  2. struct ipc_namespace => struct ipc_ids => struct idr => [1] struct kern_ipc_perm <==> struct shmid_kernel

  3. [2] struct kern_ipc_perm <==> struct shmid_kernel

回到shmget

好的,我们回头来看看shmget()究竟干了啥,首先看一下ipcget()

 
  
  1. int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids,

  2. const struct ipc_ops *ops, struct ipc_params *params)

  3. {

  4. if (params->key == IPC_PRIVATE)

  5. return ipcget_new(ns, ids, ops, params);

  6. else

  7. return ipcget_public(ns, ids, ops, params);

  8. }

如果传进来的参数是IPC_PRIVATE(这个宏的值是0)的话,无论是什么mode,都会创建一块新的共享内存。如果非0,则会去已有的共享内存中找有没有这个key的,有就返回,没有就新建。

首先看一下新建的函数newseg()

 
  
  1. static int newseg(struct ipc_namespace *ns, struct ipc_params *params)

  2. {

  3. key_t key = params->key;

  4. int shmflg = params->flg;

  5. size_t size = params->u.size;

  6. int error;

  7. struct shmid_kernel *shp;

  8. size_t numpages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;

  9. struct file *file;

  10. char name[13];

  11. int id;

  12. vm_flags_t acctflag = 0;

  13. if (size < SHMMIN || size > ns->shm_ctlmax)

  14. return -EINVAL;

  15. if (numpages << PAGE_SHIFT < size)

  16. return -ENOSPC;

  17. if (ns->shm_tot + numpages < ns->shm_tot ||

  18. ns->shm_tot + numpages > ns->shm_ctlall)

  19. return -ENOSPC;

  20. shp = ipc_rcu_alloc(sizeof(*shp));

  21. if (!shp)

  22. return -ENOMEM;

  23. shp->shm_perm.key = key;

  24. shp->shm_perm.mode = (shmflg & S_IRWXUGO);

  25. shp->mlock_user = NULL;

  26. shp->shm_perm.security = NULL;

  27. error = security_shm_alloc(shp);

  28. if (error) {

  29. ipc_rcu_putref(shp, ipc_rcu_free);

  30. return error;

  31. }

  32. sprintf(name, "SYSV%08x", key);

  33. if (shmflg & SHM_HUGETLB) {

  34. struct hstate *hs;

  35. size_t hugesize;

  36. hs = hstate_sizelog((shmflg >> SHM_HUGE_SHIFT) & SHM_HUGE_MASK);

  37. if (!hs) {

  38. error = -EINVAL;

  39. goto no_file;

  40. }

  41. hugesize = ALIGN(size, huge_page_size(hs));

  42. /* hugetlb_file_setup applies strict accounting */

  43. if (shmflg & SHM_NORESERVE)

  44. acctflag = VM_NORESERVE;

  45. file = hugetlb_file_setup(name, hugesize, acctflag,

  46. &shp->mlock_user, HUGETLB_SHMFS_INODE,

  47. (shmflg >> SHM_HUGE_SHIFT) & SHM_HUGE_MASK);

  48. } else {

  49. /*

  50. * Do not allow no accounting for OVERCOMMIT_NEVER, even

  51. * if it's asked for.

  52. */

  53. if ((shmflg & SHM_NORESERVE) &&

  54. sysctl_overcommit_memory != OVERCOMMIT_NEVER)

  55. acctflag = VM_NORESERVE;

  56. file = shmem_kernel_file_setup(name, size, acctflag);

  57. }

  58. error = PTR_ERR(file);

  59. if (IS_ERR(file))

  60. goto no_file;

  61. id = ipc_addid(&shm_ids(ns), &shp->shm_perm, ns->shm_ctlmni);

  62. if (id < 0) {

  63. error = id;

  64. goto no_id;

  65. }

  66. shp->shm_cprid = task_tgid_vnr(current);

  67. shp->shm_lprid = 0;

  68. shp->shm_atim = shp->shm_dtim = 0;

  69. shp->shm_ctim = get_seconds();

  70. shp->shm_segsz = size;

  71. shp->shm_nattch = 0;

  72. shp->shm_file = file;

  73. shp->shm_creator = current;

  74. list_add(&shp->shm_clist, ¤t->sysvshm.shm_clist);

  75. /*

  76. * shmid gets reported as "inode#" in /proc/pid/maps.

  77. * proc-ps tools use this. Changing this will break them.

  78. */

  79. file_inode(file)->i_ino = shp->shm_perm.id;

  80. ns->shm_tot += numpages;

  81. error = shp->shm_perm.id;

  82. ipc_unlock_object(&shp->shm_perm);

  83. rcu_read_unlock();

  84. return error;

  85. no_id:

  86. if (is_file_hugepages(file) && shp->mlock_user)

  87. user_shm_unlock(size, shp->mlock_user);

  88. fput(file);

  89. no_file:

  90. ipc_rcu_putref(shp, shm_rcu_free);

  91. return error;

  92. }

这个函数首先几个if检查size是不是合法的参数,并且检查有没有足够的pages。然后调用ipc_rcu_alloc()函数给共享内存数据结构shp分配空间。然后把一些参数写到shp的shm_perm成员中。然后sprintf下面那个大的if-else是为表示共享内存内容的file分配空间。再然后ipc_addid()是一个比较重要的函数,它把刚才新建的这个共享内存的数据结构的指针加入到namespace的ids里,即可以想象成加入到数组里,并获得一个可以找到它的id。这里的id并不完全是数组的下标,因为要避免重复,所以这里有一个简单的机制来保证生成的id几乎是unique的,即ids里面有个seq变量,每次新加入共享内存对象时都会加1,而真正的id是这样生成的SEQ_MULTIPLIER * seq + id。然后初始化一些成员,再把这个数据结构的指针加到当前进程的一个list里。这个函数的工作就基本完成了。

接下来我们再看一下如果创建时传入一个已有的key,即ipcget_public()的逻辑

 
  
  1. static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids,

  2. const struct ipc_ops *ops, struct ipc_params *params)

  3. {

  4. struct kern_ipc_perm *ipcp;

  5. int flg = params->flg;

  6. int err;

  7. /*

  8. * Take the lock as a writer since we are potentially going to add

  9. * a new entry + read locks are not "upgradable"

  10. */

  11. down_write(&ids->rwsem);

  12. ipcp = ipc_findkey(ids, params->key);

  13. if (ipcp == NULL) {

  14. /* key not used */

  15. if (!(flg & IPC_CREAT))

  16. err = -ENOENT;

  17. else

  18. err = ops->getnew(ns, params);

  19. } else {

  20. /* ipc object has been locked by ipc_findkey() */

  21. if (flg & IPC_CREAT && flg & IPC_EXCL)

  22. err = -EEXIST;

  23. else {

  24. err = 0;

  25. if (ops->more_checks)

  26. err = ops->more_checks(ipcp, params);

  27. if (!err)

  28. /*

  29. * ipc_check_perms returns the IPC id on

  30. * success

  31. */

  32. err = ipc_check_perms(ns, ipcp, ops, params);

  33. }

  34. ipc_unlock(ipcp);

  35. }

  36. up_write(&ids->rwsem);

  37. return err;

  38. }

逻辑非常简单,先去找有没有这个key。没有的话还是创建一个新的,注意ops->getnew()对应的就是刚才的newseg()函数。如果找到了就判断一下权限有没有问题,没有问题就直接返回IPC id。

可以再看下ipc_findkey()这个函数

 
  
  1. static struct kern_ipc_perm *ipc_findkey(struct ipc_ids *ids, key_t key)

  2. {

  3. struct kern_ipc_perm *ipc;

  4. int next_id;

  5. int total;

  6. for (total = 0, next_id = 0; total < ids->in_use; next_id++) {

  7. ipc = idr_find(&ids->ipcs_idr, next_id);

  8. if (ipc == NULL)

  9. continue;

  10. if (ipc->key != key) {

  11. total++;

  12. continue;

  13. }

  14. rcu_read_lock();

  15. ipc_lock_object(ipc);

  16. return ipc;

  17. }

  18. return NULL;

  19. }

逻辑也很简单,注意到ids->ipcs_idr就是之前提到的Interger ID Managenent机制,里面存的就是shmid和对象一一对应的关系。然后这里可以看到ids->in_use表示的是共享内存的个数,由于中间的有些可能删掉了,所以total在找到一个不为空的共享内存的时候才++。然后我们也可以看到,这里对重复的key并没有做任何处理。所以我们在编程的时候也应该避免直接约定用某一个数字当key。

shmat

接下来我们看一下shmat(),它的逻辑全在do_shmat()中,所以我们直接看这个函数。

 
  
  1. long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr,

  2. unsigned long shmlba)

  3. {

  4. struct shmid_kernel *shp;

  5. unsigned long addr;

  6. unsigned long size;

  7. struct file *file;

  8. int err;

  9. unsigned long flags;

  10. unsigned long prot;

  11. int acc_mode;

  12. struct ipc_namespace *ns;

  13. struct shm_file_data *sfd;

  14. struct path path;

  15. fmode_t f_mode;

  16. unsigned long populate = 0;

  17. err = -EINVAL;

  18. if (shmid < 0)

  19. goto out;

  20. else if ((addr = (ulong)shmaddr)) {

  21. if (addr & (shmlba - 1)) {

  22. if (shmflg & SHM_RND)

  23. addr &= ~(shmlba - 1); /* round down */

  24. else

  25. #ifndef __ARCH_FORCE_SHMLBA

  26. if (addr & ~PAGE_MASK)

  27. #endif

  28. goto out;

  29. }

  30. flags = MAP_SHARED | MAP_FIXED;

  31. } else {

  32. if ((shmflg & SHM_REMAP))

  33. goto out;

  34. flags = MAP_SHARED;

  35. }

  36. if (shmflg & SHM_RDONLY) {

  37. prot = PROT_READ;

  38. acc_mode = S_IRUGO;

  39. f_mode = FMODE_READ;

  40. } else {

  41. prot = PROT_READ | PROT_WRITE;

  42. acc_mode = S_IRUGO | S_IWUGO;

  43. f_mode = FMODE_READ | FMODE_WRITE;

  44. }

  45. if (shmflg & SHM_EXEC) {

  46. prot |= PROT_EXEC;

  47. acc_mode |= S_IXUGO;

  48. }

  49. /*

  50. * We cannot rely on the fs check since SYSV IPC does have an

  51. * additional creator id...

  52. */

  53. ns = current->nsproxy->ipc_ns;

  54. rcu_read_lock();

  55. shp = shm_obtain_object_check(ns, shmid);

  56. if (IS_ERR(shp)) {

  57. err = PTR_ERR(shp);

  58. goto out_unlock;

  59. }

  60. err = -EACCES;

  61. if (ipcperms(ns, &shp->shm_perm, acc_mode))

  62. goto out_unlock;

  63. err = security_shm_shmat(shp, shmaddr, shmflg);

  64. if (err)

  65. goto out_unlock;

  66. ipc_lock_object(&shp->shm_perm);

  67. /* check if shm_destroy() is tearing down shp */

  68. if (!ipc_valid_object(&shp->shm_perm)) {

  69. ipc_unlock_object(&shp->shm_perm);

  70. err = -EIDRM;

  71. goto out_unlock;

  72. }

  73. path = shp->shm_file->f_path;

  74. path_get(&path);

  75. shp->shm_nattch++;

  76. size = i_size_read(d_inode(path.dentry));

  77. ipc_unlock_object(&shp->shm_perm);

  78. rcu_read_unlock();

  79. err = -ENOMEM;

  80. sfd = kzalloc(sizeof(*sfd), GFP_KERNEL);

  81. if (!sfd) {

  82. path_put(&path);

  83. goto out_nattch;

  84. }

  85. file = alloc_file(&path, f_mode,

  86. is_file_hugepages(shp->shm_file) ?

  87. &shm_file_operations_huge :

  88. &shm_file_operations);

  89. err = PTR_ERR(file);

  90. if (IS_ERR(file)) {

  91. kfree(sfd);

  92. path_put(&path);

  93. goto out_nattch;

  94. }

  95. file->private_data = sfd;

  96. file->f_mapping = shp->shm_file->f_mapping;

  97. sfd->id = shp->shm_perm.id;

  98. sfd->ns = get_ipc_ns(ns);

  99. sfd->file = shp->shm_file;

  100. sfd->vm_ops = NULL;

  101. err = security_mmap_file(file, prot, flags);

  102. if (err)

  103. goto out_fput;

  104. down_write(¤t->mm->mmap_sem);

  105. if (addr && !(shmflg & SHM_REMAP)) {

  106. err = -EINVAL;

  107. if (addr + size < addr)

  108. goto invalid;

  109. if (find_vma_intersection(current->mm, addr, addr + size))

  110. goto invalid;

  111. }

  112. addr = do_mmap_pgoff(file, addr, size, prot, flags, 0, &populate);

  113. *raddr = addr;

  114. err = 0;

  115. if (IS_ERR_VALUE(addr))

  116. err = (long)addr;

  117. invalid:

  118. up_write(¤t->mm->mmap_sem);

  119. if (populate)

  120. mm_populate(addr, populate);

  121. out_fput:

  122. fput(file);

  123. out_nattch:

  124. down_write(&shm_ids(ns).rwsem);

  125. shp = shm_lock(ns, shmid);

  126. shp->shm_nattch--;

  127. if (shm_may_destroy(ns, shp))

  128. shm_destroy(ns, shp);

  129. else

  130. shm_unlock(shp);

  131. up_write(&shm_ids(ns).rwsem);

  132. return err;

  133. out_unlock:

  134. rcu_read_unlock();

  135. out:

  136. return err;

  137. }

首先检查shmaddr的合法性并进行对齐,即调整为shmlba的整数倍。如果传入addr是0,前面检查部分只会加上一个MAP_SHARED标志,因为后面的mmap会自动为其分配地址。然后从那一段两行的注释开始,函数通过shmid尝试获取共享内存对象,并进行权限检查。然后修改shp中的一些数据,比如连接进程数加一。然后是通过alloc_file()创建真正的要做mmap的file。在mmap之前还要对地址空间进行检查,检查是否和别的地址重叠,是否够用。实际的映射工作就在do_mmap_pgoff()函数中做了。

shmdt

 
  
  1. SYSCALL_DEFINE1(shmdt, char __user *, shmaddr)

  2. {

  3. struct mm_struct *mm = current->mm;

  4. struct vm_area_struct *vma;

  5. unsigned long addr = (unsigned long)shmaddr;

  6. int retval = -EINVAL;

  7. #ifdef CONFIG_MMU

  8. loff_t size = 0;

  9. struct file *file;

  10. struct vm_area_struct *next;

  11. #endif

  12. if (addr & ~PAGE_MASK)

  13. return retval;

  14. down_write(&mm->mmap_sem);

  15. /*

  16. * This function tries to be smart and unmap shm segments that

  17. * were modified by partial mlock or munmap calls:

  18. * - It first determines the size of the shm segment that should be

  19. * unmapped: It searches for a vma that is backed by shm and that

  20. * started at address shmaddr. It records it's size and then unmaps

  21. * it.

  22. * - Then it unmaps all shm vmas that started at shmaddr and that

  23. * are within the initially determined size and that are from the

  24. * same shm segment from which we determined the size.

  25. * Errors from do_munmap are ignored: the function only fails if

  26. * it's called with invalid parameters or if it's called to unmap

  27. * a part of a vma. Both calls in this function are for full vmas,

  28. * the parameters are directly copied from the vma itself and always

  29. * valid - therefore do_munmap cannot fail. (famous last words?)

  30. */

  31. /*

  32. * If it had been mremap()'d, the starting address would not

  33. * match the usual checks anyway. So assume all vma's are

  34. * above the starting address given.

  35. */

  36. vma = find_vma(mm, addr);

  37. #ifdef CONFIG_MMU

  38. while (vma) {

  39. next = vma->vm_next;

  40. /*

  41. * Check if the starting address would match, i.e. it's

  42. * a fragment created by mprotect() and/or munmap(), or it

  43. * otherwise it starts at this address with no hassles.

  44. */

  45. if ((vma->vm_ops == &shm_vm_ops) &&

  46. (vma->vm_start - addr)/PAGE_SIZE == vma->vm_pgoff) {

  47. /*

  48. * Record the file of the shm segment being

  49. * unmapped. With mremap(), someone could place

  50. * page from another segment but with equal offsets

  51. * in the range we are unmapping.

  52. */

  53. file = vma->vm_file;

  54. size = i_size_read(file_inode(vma->vm_file));

  55. do_munmap(mm, vma->vm_start, vma->vm_end - vma->vm_start);

  56. /*

  57. * We discovered the size of the shm segment, so

  58. * break out of here and fall through to the next

  59. * loop that uses the size information to stop

  60. * searching for matching vma's.

  61. */

  62. retval = 0;

  63. vma = next;

  64. break;

  65. }

  66. vma = next;

  67. }

  68. /*

  69. * We need look no further than the maximum address a fragment

  70. * could possibly have landed at. Also cast things to loff_t to

  71. * prevent overflows and make comparisons vs. equal-width types.

  72. */

  73. size = PAGE_ALIGN(size);

  74. while (vma && (loff_t)(vma->vm_end - addr) <= size) {

  75. next = vma->vm_next;

  76. /* finding a matching vma now does not alter retval */

  77. if ((vma->vm_ops == &shm_vm_ops) &&

  78. ((vma->vm_start - addr)/PAGE_SIZE == vma->vm_pgoff) &&

  79. (vma->vm_file == file))

  80. do_munmap(mm, vma->vm_start, vma->vm_end - vma->vm_start);

  81. vma = next;

  82. }

  83. #else /* CONFIG_MMU */

  84. /* under NOMMU conditions, the exact address to be destroyed must be

  85. * given */

  86. if (vma && vma->vm_start == addr && vma->vm_ops == &shm_vm_ops) {

  87. do_munmap(mm, vma->vm_start, vma->vm_end - vma->vm_start);

  88. retval = 0;

  89. }

  90. #endif

  91. up_write(&mm->mmap_sem);

  92. return retval;

  93. }

接下来是shmdt(),这个函数非常简单,找到传入的shmaddr对应的虚拟内存数据结构vma,检查它的地址是不是正确的,然后调用do_munmap()函数断开对共享内存的连接。注意此操作并不会销毁共享内存,即使没有进程连接到它也不会,只有手动调用shmctl(id, IPC_RMID, NULL)才能销毁。

shmctl()总体就是一个switch语句,大多数做的是读取信息的或者设置标志位的工作,这里不赘述。

你可能感兴趣的:(linux,IPC,进程通信,共享内存)