attached detached shared memory 共享内存 shm ipcs

In computer software, shared memory is either

  • a method of inter-process communication (IPC), i.e. a way of exchanging data between programs running at the same time. One process will create an area in RAM which other processes can access;
  • a method of conserving memory space by directing accesses to what would ordinarily be copies of a piece of data to a single instance instead, by using virtual memory mappings or with explicit support of the program in question. This is most often used for shared libraries and for Execute in place (XIP).

Since both processes can access the shared memory area like regular working memory, this is a very fast way of communication (as opposed to other mechanisms of IPC such as named pipesUnix domain sockets or CORBA). On the other hand, it is less scalable, as for example the communicating processes must be running on the same machine (of other IPC methods, only Internet domain sockets—not Unix domain sockets—can use a computer network), and care must be taken to avoid issues if processes sharing memory are running on separate CPUs and the underlying architecture is not cache coherent.

IPC by shared memory is used for example to transfer images between the application and the X server on Unix systems, or inside the IStream object returned by CoMarshalInterThreadInterfaceInStream in the COM libraries under Windows.

Dynamic libraries are generally held in memory once and mapped to multiple processes, and only pages that had to be customized for the individual process (because a symbol resolved differently there) are duplicated, usually with a mechanism known as copy-on-write that transparently copies the page when a write is attempted, and then lets the write succeed on the private copy.

Compared to multiple address space operating systems, memory sharing -- especially of sharing procedures or pointer-based structures -- is simpler in single address space operating systems.[2]

https://en.wikipedia.org/wiki/Shared_memory

 

https://zh.wikipedia.org/zh-cn/系统调用

电脑中,系统调用(英语:system call),指运行在用户空间程序操作系统内核请求需要更高权限运行的服务。这些服务可能包含访问文件系统、创建和销毁进程、进程间通信和内存分配[1]系统调用在进程和作系统之间提供了一个重要的接口。系统调用提供用户程序与操作系统之间的接口。大多数系统交互式操作需求在内核态执行。如设备IO操作或者进程间通信。

 

用户空间(用户态)和内核空间(内核态)

操作系统的进程空间可分为用户空间内核空间,它们需要不同的执行权限。其中系统调用运行在内核空间

库函数

系统调用和普通库函数调用非常相似,只是系统调用由操作系统内核提供,运行于内核核心态,而普通的库函数调用由函数库或用户自己提供,运行于用户态。

典型实现(Linux)

Linux 在x86上的系统调用通过 int 80h 实现,用系统调用号来区分入口函数。操作系统实现系统调用的基本过程是:

  1. 应用程序调用库函数(API);
  2. API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态;
  3. 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
  4. 系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数;
  5. 中断处理函数返回到 API 中;
  6. API 将 EAX 返回给应用程序。

应用程序调用系统调用的过程是:

  1. 把系统调用的编号存入 EAX;
  2. 把函数参数存入其它通用寄存器;
  3. 触发 0x80 号中断(int 0x80)。

 

 

ipcs

------ Message Queues --------
key msqid owner perms used-bytes messages

------ Shared Memory Segments --------
key shmid owner perms bytes nattch status

------ Semaphore Arrays --------
key semid owner perms nsems

 

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define SHM_SIZE 4096
#define SHM_KEY_PATH "/tmp"
#define SHM_KEY_ID 'A'

int main() {
    key_t key;
    int shmid;
    char *shm_ptr;
    
    // Generate IPC key from file path
    key = ftok(SHM_KEY_PATH, SHM_KEY_ID);
    if (key == -1) {
        perror("ftok failed");
        exit(1);
    }
    printf("Generated key: %d\n", key);
    
    // Create shared memory segment
    shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }
    printf("Shared memory ID: %d\n", shmid);
    
    // Attach shared memory to process address space
    shm_ptr = (char *)shmat(shmid, NULL, 0);
    if (shm_ptr == (char *)-1) {
        perror("shmat failed");
        exit(1);
    }
    printf("Attached at address: %p\n", (void *)shm_ptr);
    
    // Write data to shared memory
    const char *message = "Hello from shared memory!";
    strcpy(shm_ptr, message);
    printf("Written to shared memory: %s\n", shm_ptr);
    
    // Read data from shared memory
    printf("Read from shared memory: %s\n", shm_ptr);
    
    // Modify data
    strcat(shm_ptr, " - Modified!");
    printf("After modification: %s\n", shm_ptr);
    
    // Detach shared memory
    if (shmdt(shm_ptr) == -1) {
        perror("shmdt failed");
        exit(1);
    }
    printf("Detached from shared memory\n");
    
    // Remove shared memory segment
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl failed");
        exit(1);
    }
    printf("Shared memory segment removed\n");
    
    return 0;
}

 

writer

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define SHM_SIZE 4096
#define SHM_KEY_PATH "/tmp"
#define SHM_KEY_ID 'A'

int main() {
    key_t key;
    int shmid;
    char *shm_ptr;
    
    // Generate IPC key from file path
    key = ftok(SHM_KEY_PATH, SHM_KEY_ID);
    if (key == -1) {
        perror("ftok failed");
        exit(1);
    }
    printf("[Writer] Generated key: %d\n", key);
    
    // Create shared memory segment
    shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }
    printf("[Writer] Shared memory ID: %d\n", shmid);
    
    // Attach shared memory to process address space
    shm_ptr = (char *)shmat(shmid, NULL, 0);
    if (shm_ptr == (char *)-1) {
        perror("shmat failed");
        exit(1);
    }
    printf("[Writer] Attached at address: %p\n", (void *)shm_ptr);
    
    // Write data to shared memory
    const char *message = "Hello from writer process!";
    strcpy(shm_ptr, message);
    printf("[Writer] Written to shared memory: %s\n", shm_ptr);
    
    printf("[Writer] Data written. Waiting for reader...\n");
    printf("[Writer] Now run the reader process to read this data.\n");
    printf("[Writer] Press Enter to continue and cleanup...");
    getchar();
    
    // Detach shared memory
    if (shmdt(shm_ptr) == -1) {
        perror("shmdt failed");
        exit(1);
    }
    printf("[Writer] Detached from shared memory\n");
    
    // Remove shared memory segment
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl failed");
        exit(1);
    }
    printf("[Writer] Shared memory segment removed\n");
    
    return 0;
}

 

reader

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define SHM_SIZE 4096
#define SHM_KEY_PATH "/tmp"
#define SHM_KEY_ID 'A'

int main() {
    key_t key;
    int shmid;
    char *shm_ptr;
    
    // Generate same IPC key
    key = ftok(SHM_KEY_PATH, SHM_KEY_ID);
    if (key == -1) {
        perror("ftok failed");
        exit(1);
    }
    printf("[Reader] Generated key: %d\n", key);
    
    // Get existing shared memory segment (don't create)
    shmid = shmget(key, SHM_SIZE, 0666);
    if (shmid == -1) {
        perror("shmget failed - make sure writer is running first");
        exit(1);
    }
    printf("[Reader] Shared memory ID: %d\n", shmid);
    
    // Attach shared memory to process address space
    shm_ptr = (char *)shmat(shmid, NULL, 0);
    if (shm_ptr == (char *)-1) {
        perror("shmat failed");
        exit(1);
    }
    printf("[Reader] Attached at address: %p\n", (void *)shm_ptr);
    
    // Read data from shared memory
    printf("[Reader] Reading from shared memory: %s\n", shm_ptr);
    
    // Modify data
    strcat(shm_ptr, " - Read by reader process!");
    printf("[Reader] Modified data: %s\n", shm_ptr);
    
    // Detach shared memory
    if (shmdt(shm_ptr) == -1) {
        perror("shmdt failed");
        exit(1);
    }
    printf("[Reader] Detached from shared memory\n");
    
    return 0;
}

gcc writer.c -o writer;./writer
[Writer] Generated key: 1090519044
[Writer] Shared memory ID: 1
[Writer] Attached at address: 0x74bd8af81000
[Writer] Written to shared memory: Hello from writer process!
[Writer] Data written. Waiting for reader...
[Writer] Now run the reader process to read this data.
[Writer] Press Enter to continue and cleanup...

 

gcc reader.c -o reader;./reader
[Reader] Generated key: 1090519044
[Reader] Shared memory ID: 1
[Reader] Attached at address: 0x7b6f8c482000
[Reader] Reading from shared memory: Hello from writer process!
[Reader] Modified data: Hello from writer process! - Read by reader process!
[Reader] Detached from shared memory

 

ipcs

------ Message Queues --------
key msqid owner perms used-bytes messages

------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x41000004 1 a 666 4096 1

------ Semaphore Arrays --------
key semid owner perms nsems

 

ipcs -m

------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x41000004 1 a 666 4096 1

 

 

杀死进程,共享内存还存在。

kill -9 1546
ipcs

------ Message Queues --------
key msqid owner perms used-bytes messages

------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x41000004 1 a 666 4096 0

------ Semaphore Arrays --------
key semid owner perms nsems

 

 writer

package main

import (
    "fmt"
    "os"
    "syscall"
    "unsafe"
)

const (
    SHM_SIZE = 4096
    SHM_KEY  = 0x1234
)

func main() {
    shmid, _, err := syscall.Syscall(
        syscall.SYS_SHMGET,
        uintptr(SHM_KEY),
        uintptr(SHM_SIZE),
        uintptr(0666|0x200),
    )
    if int(shmid) == -1 {
        fmt.Fprintf(os.Stderr, "shmget failed: %v\n", err)
        os.Exit(1)
    }
    fmt.Printf("[Writer] Shared memory ID: %d\n", shmid)

    shmaddr, _, err := syscall.Syscall(
        syscall.SYS_SHMAT,
        shmid,
        0,
        0,
    )
    if int(shmaddr) == -1 {
        fmt.Fprintf(os.Stderr, "shmat failed: %v\n", err)
        os.Exit(1)
    }
    fmt.Printf("[Writer] Attached at address: %p\n", unsafe.Pointer(shmaddr))

    message := "Hello from Go writer process!"
    data := []byte(message)
    for i, b := range data {
        *(*byte)(unsafe.Pointer(shmaddr + uintptr(i))) = b
    }
    *(*byte)(unsafe.Pointer(shmaddr + uintptr(len(data)))) = 0

    fmt.Printf("[Writer] Written to shared memory: %s\n", message)
    fmt.Println("[Writer] Data written. Waiting for reader...")
    fmt.Println("[Writer] Now run the reader process to read this data.")
    fmt.Print("[Writer] Press Enter to continue and cleanup...")
    fmt.Scanln()

    _, _, err = syscall.Syscall(
        syscall.SYS_SHMDT,
        shmaddr,
        0,
        0,
    )
    if int(err) != 0 {
        fmt.Fprintf(os.Stderr, "shmdt failed: %v\n", err)
        os.Exit(1)
    }
    fmt.Println("[Writer] Detached from shared memory")

    _, _, err = syscall.Syscall(
        syscall.SYS_SHMCTL,
        shmid,
        uintptr(0),
        0,
    )
    if int(err) != 0 {
        fmt.Fprintf(os.Stderr, "shmctl failed: %v\n", err)
        os.Exit(1)
    }
    fmt.Println("[Writer] Shared memory segment removed")
}

 

reader (writer)

package main

import (
    "fmt"
    "os"
    "syscall"
    "unsafe"
)

const (
    SHM_SIZE = 4096
    SHM_KEY  = 0x1234
)

func readString(addr uintptr) string {
    var result []byte
    for i := 0; i < SHM_SIZE; i++ {
        b := *(*byte)(unsafe.Pointer(addr + uintptr(i)))
        if b == 0 {
            break
        }
        result = append(result, b)
    }
    return string(result)
}

func writeString(addr uintptr, s string) {
    data := []byte(s)
    for i, b := range data {
        if i >= SHM_SIZE-1 {
            break
        }
        *(*byte)(unsafe.Pointer(addr + uintptr(i))) = b
    }
    if len(data) < SHM_SIZE {
        *(*byte)(unsafe.Pointer(addr + uintptr(len(data)))) = 0
    }
}

func main() {
    shmid, _, err := syscall.Syscall(
        syscall.SYS_SHMGET,
        uintptr(SHM_KEY),
        uintptr(SHM_SIZE),
        uintptr(0666),
    )
    if int(shmid) == -1 {
        fmt.Fprintf(os.Stderr, "shmget failed - make sure writer is running first: %v\n", err)
        os.Exit(1)
    }
    fmt.Printf("[Reader] Shared memory ID: %d\n", shmid)

    shmaddr, _, err := syscall.Syscall(
        syscall.SYS_SHMAT,
        shmid,
        0,
        0,
    )
    if int(shmaddr) == -1 {
        fmt.Fprintf(os.Stderr, "shmat failed: %v\n", err)
        os.Exit(1)
    }
    fmt.Printf("[Reader] Attached at address: %p\n", unsafe.Pointer(shmaddr))

    message := readString(shmaddr)
    fmt.Printf("[Reader] Reading from shared memory: %s\n", message)

    modifiedMessage := message + " - Read by Go reader process!"
    writeString(shmaddr, modifiedMessage)
    fmt.Printf("[Reader] Modified data: %s\n", modifiedMessage)

    _, _, err = syscall.Syscall(
        syscall.SYS_SHMDT,
        shmaddr,
        0,
        0,
    )
    if int(err) != 0 {
        fmt.Fprintf(os.Stderr, "shmdt failed: %v\n", err)
        os.Exit(1)
    }
    fmt.Println("[Reader] Detached from shared memory")
}

 

./go_writer
[Writer] Shared memory ID: 2
[Writer] Attached at address: 0x79df7f209000
[Writer] Written to shared memory: Hello from Go writer process!
[Writer] Data written. Waiting for reader...
[Writer] Now run the reader process to read this data.
[Writer] Press Enter to continue and cleanup...

 

./go_reader
[Reader] Shared memory ID: 2
[Reader] Attached at address: 0x713f11063000
[Reader] Reading from shared memory: Hello from Go writer process!
[Reader] Modified data: Hello from Go writer process! - Read by Go reader process!
[Reader] Detached from shared memory

 

ipcs

------ Message Queues --------
key msqid owner perms used-bytes messages

------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x41000004 1 a 666 4096 0
0x00001234 2 a 666 4096 1

------ Semaphore Arrays --------
key semid owner perms nsems

 

 

 

 

 

 

 

image

 

 

 

进程间通信IPC(二)

 
System V
共享内存共享内存区是最快的IPC形式。
一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再设计到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据(而系统调用又是有时间成本的)1,共享内存的原理如果进程需要使用一个动态库,这个动态库也必须被加载到内存,可是这个动态库加载到内存之后,进程如何看到这个动态库,其实是把动态库映射到这个进程地址空间里。那能不能映射到其他进程的地址空间里呢?答案是可以的,那动态库就可以在操作系统内被多个进程实现代码级别的共享。共享内存也是相似的道理。第一步:在物理内存中创建一段空间第二部:把这一部分空间映射到进程A的地址空间当中。进程A中申请一块空间,在堆和栈之间,这块叫共享区。然后更新一下页表映射关系图片设想:可能同时存在多组进程,都在使用不同的共享内存来进行通信,那么在Linux内,就可能有多个共享内存同时存在->那么OS需不需要对共享内存进行管理呢->怎么进行管理->先描述,再组织

我们猜一下:

共享内存,一定要有对应的描述共享内存的内核结构体对象!+物理内存->进程和共享内存的关系,就是内核数据结构之间的关系。对共享内存的管理就变成了对链表的增删查改。

操作系统怎么知道共享内存有没有人使用?->共享内存描述结构体里包含引用计数

事实也正是如此

图片
共享内存函数
int shmget(key_t key,size_t size,int shmflg);
参数:key:这个共享内存段名字size:共享内存大小shmflg:由9个权限标志构成,用法和创建文件时使用的mode模式标志是一样的取值为IPC_CREAT:创建共享内存,如果目标共享内存不存在,则创建,否则打开这个已经存在的共享内存并返回取值为IPC_EXCL:单独使用无意义!必须和IPC_CREAT组合使用才有意义。如果要创建的shm不存在,就创建它,如果已经存在,shmget就会出错返回。->只要shmget成功返回,一定是一个全新的共享内存!key怎么设置呢?假设进程A和进程B要进行进程间通信。那么就让A和B事先约定一个key引入ftok函数图片

把这个值传入shmget。

shmget如果成功的话,返回一个唯一的贡献内存标识符,由这个值来标识创建好的共享内存。

图片两个指令:
ipcs -m:查看共享内存ipcrm -m shmid:可以删除共享内存shmid可以通过ipcs -m来看到
问题:一个进程创建共享内存之后,如果进程结束了,发现共享内存还存在。进程结束了,如果没有进行删除共享内存,共享内存资源会一直存在---和文件不一样,进程一旦结束,打开的文件就会被关闭共享内存的资源,生命周期随内核---如果没有显式的删除,即便进程退出了,ipc资源依旧被占用问题:为什么删除的时候不用key而用shmid?key是用户用来"找"共享内存的,shmid是内核真正"识别"共享内存的唯一标识,删除共享内存是内核操作而不是用户操作,所以用shmid
int shmctl(int shmid,int cmd,struct shmid_ds* buf);
图片
  • shmid: 由 shmget 返回的共享内存标识码
  • cmd: 将要采取的动作 (有三个可取值)
  • buf: 指向一个保存着共享内存的模式状态和访问权限的数据结构

cmd的三种取值:IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值IPC_RMID:删除共享内存段shmat函数
voidshmat(int shmid,const void* shmaddr,int shmflg);
返回值为映射成功之后起始的虚拟地址。只要得到起始虚拟地址就可以,之后加偏移量就行。shmat将共享内存连接到进程的地址空间中const void* shmaddr:一般不用管设置为NULL就可以int shmflg:设置为0,表示不用管权限问题
shmdt函数将共享内存段与当前进程脱离注意:将共享内存段与当前进程脱离不等于删除共享内存段
int shmdt(const void* shmaddr);
共享内存的优点:

1,互相映射,读,写直接被对方看到。

2,不需要进行系统调用获取或者写入内容,直接以指针地址的方式进行访问。

3,动态库进行加载到内存,让内存看到的原理也是如此,只不过采取mmap的方式,来进行文件和共享内存的映射。

 

缺点:通信双方,没有所谓的"同步机制"!->会导致写入端写入了一部分数据,就被读端读走了。

共享内存,没有保护机制(指的是对共享内存数据的保护)!!->这也是共享内存速度快的原因。

如果我们非要把共享内存保护起来,有没有其他方法可以选择呢?

让这两个进程再建立一条管道,这个管道是为了"通知"准备的。A进程一旦往共享内存写了两个AA,再管道写一个通知字符,唤醒进程B。进程B首先读取管道,如果为空,不做读取。进程A如果写一个A,那么不发通知字符。

我们可以通过命名管道自己的同步机制,来完成对共享内存局部的保护。

 

 

共享内存数据结构
structshmid_ds {structipc_perm      shm_perm;    /* operation perms */int                  shm_segsz;   /* size of segment (bytes) */__kernel_time_t      shm_atime;   /* last attach time */__kernel_time_t      shm_dtime;   /* last detach time */__kernel_time_t      shm_ctime;   /* last change time */__kernel_ipc_pid_t   shm_cpid;    /* pid of creator */__kernel_ipc_pid_t   shm_lpid;    /* pid of last operator */unsignedshort       shm_nattch;  /* no. of current attaches */unsignedshort       shm_unused;  /* compatibility */void                *shm_unused2; /* ditto - used by DIPC */void                *shm_unused3; /* unused */};
图片

ipc_perm里面有mode,这个是权限。

__key:我们用shmget(key,,,),就会被设置到共享内存的描述结构体中

 


System V消息队列
  • 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
  • 特性方面
    • IPC 资源必须删除,否则不会自动清除,除非重启,所以 system V IPC 资源的生命周期随内核

进程间IPC前提:让不同进程看到同一份资源->维护成为一个队列。

图片

进程A,B要向队列里放数据,不能只放数据,还要打上类型标签。

本质就是Int type

例如int type = 1表示A,int type =2表示B

图片

结论1:消息队列,提供了一种,一个进程给另一个进程发送“有类型数据块”的方式。

结论2:存在多个消息队列,那么操作系统就要对多个消息队列进行管理!

先描述,再组织

在内部一定要有描述该消息队列的结构体。

struct msgid_ds.每一个节点,也可以称为带数据块的结构体。

比如struct node{ int type; char buffer[],struct node* next};

结论3:两个要通信的进程,怎么保证自己看到的是同一个消息队列?

进程A和进程B在上层约定一个key,由创建者把这个key设置到消息队列描述结构体里,B拿这个key去获取。保证A和B看到同一份消息队列。


System V 信号量

信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥。

共享内存->看到同一份资源->为通信提供前提(好处)

->如果没有所谓"保护机制",导致数据不一致(缺点)->一,没有被保护起来的公共资源。二,我们各自的代码,访问了这个没有被保护的公共资源。

->解决方案:信号量,这是一种具体的方案!

保护机制?保护(约束)谁->保护临界区代码,就是变相的保护临界资源


并发编程

  • 多个执行流 (进程), 能看到的同一份公共资源:共享资源
  • 被保护起来的共享资源叫做临界资源
  • 保护的方式常见:互斥与同步
  • 任何时刻,只允许一个执行流访问资源,叫做互斥
  • 多个执行流,访问临界资源的时候,具有一定的顺序性,叫做同步
  • 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
  • 在进程中涉及到互斥资源的程序段叫临界区->访问资源对应的代码。你写的代码 = 访问临界资源的代码 (临界区)+ 不访问临界资源的代码 (非临界区)
  • 所谓的对共享资源进行保护,本质是对访问共享资源的代码进行保护
图片图片把这个当成一个电影院,电影院里有票,抢到了票就有相应的座位可以去做。对于共享内存,我们可以把共享内存按照不同的区域来部分使用。我们担心->1,不要访问同一个位置.2,不要放入过多的进程进来信号量:计数器,该计数器描述的是临界资源中,资源数量的多少。

所有进程,访问临界资源中的一小块,就必须先申请信号量。如果申请信号量失败,就要阻塞挂起!

进程访问资源前,先申请信号量,本质是:是对资源的预订机制。(就像抢电影票一样,只要抢到了票,就有了座位)

细节1:信号量本身就是共享资源!申请 sem--,对计数器--,得保证原子性,称为P操作

对信号量sem++,这个操作也必须是原子的,这个称为V操作

细节2:只有1或者0两态的信号量,叫做二元信号量!->就是互斥!


最后附上图:内核是如何组织管理IPC资源的图片

 

posted @ 2026-04-15 17:04  papering  阅读(6)  评论(0)    收藏  举报