Posix IPC (信号量,消息队列,共享内存)

上表是打开或者创建Posix IPC对象所用的各种oflag的值

前3行指定怎么打开对象:只读只写或者是读写,消息队列能够以任何一种模式打开,信号量的打开不指定模式( 任何信号量的读写都需要读写访问权限 ),共享内存区对象则不能以只写模式打开

以下4行是可选的:

O_CREAT:若不存在则创建由函数的第一个参数所指明的消息队列,信号量或者共享内存区对象

O_EXCL:如果和O_CREAT一起指定,那么IPC函数只在指定名字的对象不存在时才会创建,否则返回EEXIST错误

O_NONBLOCK:该标志使得消息队列在队列为空时的读或者队列填满时的写不被阻塞

O_TRUNC:若以读写模式打开了一个已存在的共享内存区对象,该标识使得该对象的长度被截为0

#### 大多数的Unix内核按如下步骤进行权限测试

1)当前进程的有效用户是超级用户(UID=0),允许访问

2)当前进程的有效用户ID=IPC对象的属主ID的前提下,若相应的用户权限位已设置,那就允许访问,否则不允许

3)当前进程的有效组ID=该IPC对象的组ID的前提下,若相应权限位已设置,允许访问,否则不允许

4)其它用户的访问权限位若已设置,允许访问,否则不允许

2.0  共享内存

共享内存概念

  所谓共享内存,就是多个进程间共同地使用同一段物理内存空间,它是通过将同一段物理内存映射到不同进程的虚拟空间来实现的。由于映射到不同进程的虚拟空间中,不同进程可以直接使用,不需要像消息队列那样进行复制,所以共享内存的效率很高。Posix 共享内存可以通过mmap()映射普通文件机制来实现,也可以System V共享内存机制来实现,System V是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信,也就是说每个共享内存区域对应特殊文件系统shm中的一个文件。

共享内存原理

  System V共享内存把所有共享数据放在共享内存区,任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面。 System V共享内存通过shmget函数获得或创建一个IPC共享内存区域,并返回相应的标识符,内核在保证shmget获得或创建一个共享内存区,初始化该共享 内存区相应的shmid_kernel结构,同时还将在特殊文件系统shm中创建并打开一个同名文件,并在内存中建立起该文件的相应的dentry及 inode结构,新打开的文件不属于任何一个进程,所有这一切都是系统调用shmget函数完成的。

信号量的模型:

1.0    互斥模型   

2.0    单次同步模型

3.0    迭代同步模型

-----------------------------------------------------------------------------------无名信号量

#include <semaphore.h>

初始化无名信号量 sem 的值为 value
pshared  

0 :表示在一个进程内的线程间共享信号量,
其它:否则表示在不同进程间共享,此时必须使用共享内存,才能在不同进程中访问这个信号量

成功返回 0,失败返回 -1
int sem_init(sem_t *sem, int pshared, unsigned int value);

Link with -pthread.
#include <semaphore.h>

销毁无名信号量 sem
成功返回 0,失败返回 -1
int sem_destroy(sem_t *sem);

Link with -pthread.

-----------------------------------------------------------------------------------获取信号量值

获取信号量 sem 的值,保存到 sval 中
成功返回 0,失败返回 -1
int sem_getvalue(sem_t *sem, int *sval);
 

------------------------------------------------------------------------------------------有名信号量

创建信号量 name,并初始化信号量值为 value
sem_t *sem_open(const char *name, int oflag,
            mode_t mode, unsigned int value);

打开已存在的信号量 name
sem_t *sem_open(const char *name, int oflag);

关闭信号量 sem
成功返回 0,失败返回 -1

int sem_close(sem_t *sem);

删除命名信号量 name
成功返回 0,失败返回 -1

int sem_unlink(const char *name);

释放信号量 sem
成功返回 0,失败返回 -1

int sem_post(sem_t *sem);

获取信号量 sem,如果不成功则阻塞
int sem_wait(sem_t *sem);
获取信号量 sem,如果不成功,则立即返回
int sem_trywait(sem_t *sem);
获取信号量 sem,如果不成功,在超时值 abs_timeout 内会阻塞,
超时值到期就会返回
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

1.   先关闭信号最

2.   删除命名信号量

注:
1. 命名信号量在 /dev/shm 目录下会有相应的文件名,
形式为:sem.信号量文件名。如:sem.mysem

---------------------------------------------------------------------------------------------------------------------------共享内存

共享内存三种应用:

1    mmap-映射文件

2    mmap-匿名映射

3    mmap-共享内存

#include <sys/mman.h>

映射 fd 文件从 offset 开始的 length 字节到进程内存
addr 可以指定映射的地址,此时 flags 必须指定为 MAP_FIXED,
由于这样做可移植性不好,因此建议不要指定。
成功返回映射的进程内存地址,失败返回 MAP_FAILED
void *mmap(    void *addr, size_t length, int prot, int flags,int fd, off_t offset      );

prot 可取值:
PROT_NONE   映射页不可访问,或以下三种的任意“或”
PROT_READ   映射页可读
PROT_WRITE 映射页可写
PROT_EXEC   映射页可执行

flags 常用值:
MAP_SHARED  对映射的内存所做的修改同样影响到文件
MAP_PRIVATE  对映射的内存所做的修改仅对该进程可见,对文件没有影响
MAP_FIXED      返回值必须等于 addr,因为这不利于可移植性,
所以不鼓励使用此标志。
MAP_ANONYMOUS 匿名映射,与任何文件无关,fd 和 offset 被忽略,
但一些实现要求 fd 此时必须为 -1

    // 匿名映射到进程的进程内存

  1.     mmap-映射文件
  2.     mmap-匿名映射
  3.     mmap-共享内存

    void *mmap(    void *addr,          size_t length,           int prot,           int flags,       int fd,          off_t offset );

  1.     destaddr = mmap(NULL, size, PROT_READ | PROT_WRITE,    MAP_SHARED,               destfd,    0);
  2.     data = mmap(NULL, size,      PROT_READ | PROT_WRITE,  MAP_SHARED | MAP_ANONYMOUS,  -1,   0);
  3.      addr = mmap(NULL, size,     PROT_READ | PROT_WRITE,    MAP_SHARED   ,       fd,         0);

#include <sys/mman.h>
#include <sys/stat.h>        /* For mode constants */
#include <fcntl.h>           /* For O_* constants */

打开名为 name 的共享内存对象
成功返回非负的文件描述符,失败返回 -1
共享内存对象在 /dev/shm 目录下有对应的文件,如:myshm
int shm_open(const char *name, int oflag, mode_t mode);   //名字     打开属性,   访问属性  

删除名为 name 的共享内存对象
成功返回 0,失败返回 -1
int shm_unlink(const char *name);       //   共享内存对象

Link with -lrt.

#include <unistd.h>
#include <sys/types.h>

调整文件 path 或 fd 的大小到 length
成功返回 0,失败返回 -1
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

    // 调整共享内存大小
    size = 512;
    if (-1 == ftruncate(fd, size))

同步内存映射的进程内存 addr 中 length 字节到文件
成功返回 0,失败返回 -1
int msync(void *addr, size_t length, int flags);

// 同步到磁盘文件
msync(destaddr, size, MS_SYNC);

flags 常见值:
MS_ASYNC      执行异步写
MS_SYNC       执行同步写 
MS_INVALIDATE 使高速缓存的数据失效,从文件中读回数据

其中 MS_ASYNC 和 MS_SYNC这两个常值中必须指定一个,但不能都指定。
它们的差别是,一旦写操作已由内核排入队列,MS_ASYNC即返回,
而MS_SYNC则要等到写操作完成后才返回。

----------------------------------以下是system v pic 共享内存

#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
ftok()      //获取key值            
shmget()    //创建/获取共享内存 
shmat()     //挂接共享内存        
shmdt()     //脱接共享内存        
shmctl()    //删除共享内存   

ftok()

//获取key值, key值是System V IPC的标识符,成功返回key,失败返回-1设errno
//同pathname+同 proj_id==>同key_t;
key_t ftok(const char *pathname, int proj_id);

pathname :文件名
proj_id: 1~255的一个数,表示project_id

key_t key=ftok(".",100);    //“.”就是一个存在且可访问的路径, 100是假设的proj_id
    if(-1==key)
        perror("ftok"),exit(-1);

shmget()

//创建/获取共享内存,成功返回共享内存的标识符shmid,失败返回-1设errno
int shmget(key_t key, size_t size, int shmflg);     //多设为int shmid=...  和shmat()一起用比较好看

key :ftok()的返回值
size:共享内存的大小,实际会按照页的大小(PAGE_SIZE)来分配。0表示获取已经分配好的共享内存
shmflg:具体的操作标志

  • IPC_CREAT:若不存在则创建, 需要在shmflg中"|权限信息", eg: |0664; 若存在则打开
  • IPC_EXCL:与IPC_CREAT搭配使用, 若存在则创建失败==>报错,set errno
  • 0 :获取已经存在的共享内存
//创建shared memory
shmid=shmget(key,4,IPC_CREAT|IPC_EXCL|0664);
if(-1==shmid)
    perror("shmget"),exit(-1);

Q:既然shmget()可以创建, 那要ftok()有啥用
A:shmget才是创建共享内存, ftok()只是用来产生一个key,其实这个key的位置自己随意填一个数也可以运行,但是相对系统生成的,很容易造成冲突,所以最好用ftok产生一个key

shmat()

//挂接共享内存,成功返回映射内存的地址,失败返回(void*)-1设errno
void *shmat(int shmid, const void *shmaddr, int shmflg);

shmid: shmget()的返回值
shmaddr

  • NULL表示由系统选择 (同mmap())
  • 非NULL且shflg是SHM_RND,会按照页对齐的原则从shmaddr开始找最近的地址开始分配分,否则shmaddr指定的地址必须是页对齐的
  • shmflg :操作的标志, 给0即可
    • SHM_RDONLY表示挂接到该共享内存的进程必须有读权限
    • SHM_REMAP (Linux-specific)表示如果要映射的共享内存已经有现存的内存,那么就将旧的替换
//挂接共享内存
void* pv=shmat(shmid,NULL,0);
if((void*)-1==pv)
    perror("shmat"),exit(-1);

shmdt()

//脱接共享内存,成功返回0,失败返回-1设errno
int shmdt(const void *shmaddr);
//脱接shm
int res=shmdt(pv);
if(-1==res)
    perror("shmdt"),exit(-1);

shmctl()

//共享内存管理,成功返回0,失败返回-1设errno
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmid:共享内存的id,由shmget()返回
buf : shmid_ds类型的指针

struct shmid_ds {
   struct ipc_perm  shm_perm;       /* Ownership and permissions */
    size_t          shm_segsz;      /* Size of segment (bytes) */
    time_t          shm_atime;      /* Last attach time */
    time_t          shm_dtime;      /* Last detach time */
    time_t          shm_ctime;      /* Last change time */
    pid_t           shm_cpid;       /* PID of creator */
    pid_t           shm_lpid;       /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;     /* No. of current attaches */
    ...
};
//<sys/ipc.h>
struct ipc_perm {
   key_t            __key;      /* Key supplied to shmget(2) */
   uid_t            uid;        /* Effective UID of owner */
   gid_t            gid;        /* Effective GID of owner */
   uid_t            cuid;       /* Effective UID of creator */
   gid_t            cgid;       /* Effective GID of creator */
   unsigned short   mode;       /* Permissions + SHM_DEST and SHM_LOCKED flags */
   unsigned short   __seq;      /* Sequence number */
};

cmd

  • IPC_STAT   表示从内核中拷贝关于这个shmid的信息到buf指向的shmid_ds中
  • IPC_SET     将buf指向的shmid_ds的信息写入到内核的结构体中,同时更新成员shm_ctime
  • IPC_RMID   销毁共享内存
  • IPC_INFO(Linux-specific)返回系统对共享内存的限制写入到buf指向的时shminfo结构体中
//_GNU_SOURCE
struct  shminfo {
        unsigned long   shmmax; /* Maximum segment size */
        unsigned long   shmmin; /* Minimum segment size; always 1 */
        unsigned long   shmmni; /* Maximum number of segments */
        unsigned long   shmseg; /* Maximum number of segments that a process can attach; unused within kernel */
        unsigned long   shmall; /* Maximum number of pages of shared memory, system-wide */
 };
 //shmmni, shmmax, and shmall 可以童工/proc里的同名文件进行修改
  • SHM_INFO(Linux-specific) 返回一个shm_info结构体来表示该共享内存消耗的系统资源
//_GNU_SOURCE
struct shm_info {
        int             used_ids;   /* # of currently existing segments */
        unsigned long   shm_tot;    /* Total number of shared memory pages */
        unsigned long   shm_rss;    /* # of resident shared memory pages */
        unsigned long   shm_swp;    /* # of swapped shared memory pages */
        unsigned long   swap_attempts; /* Unused since Linux 2.4 */
        unsigned long   swap_successes;/* Unused since Linux 2.4 */
 };
  • SHM_STAT(Linux-specific) 为IPC_STAT返回一个shmid_ds结构结构体,不同的是shmid的参数不是一个标识符,而是内核中一个包含了系统中所有共享内存信息的索引
  • SHM_LOCK防止系统将共享内存放到swap区,IPC_STAT读到的信息中SHM_LOCKED标记就被设置了
  • SHM_UNLOCK 解除锁定,即允许共享内存被系统放到swap区
//使用IPC_RMID删除共享内存
int res=shmctl(shmid,IPC_RMID,NULL);
if(-1==res)
    perror("shmctl"),exit(-1);

例子

//Sys V IPC shm
int shmid;          //定义全局变量记录id

void fa(int signo)
{
    printf("deleting shared memories...\n");

    sleep(3);//其实没用

    int res=shmctl(shmid,IPC_RMID,NULL);

    if(-1==res)
        perror("shmctl"),exit(-1);

    printf("delete success\n");

    exit(0);    //ctrl+C已经不能结束while(1),用exit(0)来终结
}
int main(){
    //获取key
    key_t key=ftok(".",100);    //.就是一个存在且可访问的路径, 100是随便给的

    if(-1==key)
        perror("ftok"),exit(-1);

    printf("key=%#x\n",key);    //打印出进制的标示,即0x

    //创建shared memory
    shmid=shmget(key,4,IPC_CREAT|IPC_EXCL|0664);

    if(-1==shmid)
        perror("shmget"),exit(-1);

    printf("shmid=%d\n",shmid);

    //挂接shm
    void* pv=shmat(shmid,NULL,0);

    if((void*)-1==pv)
        perror("shmat"),exit(-1);

    printf("link shared memory success\n");

    //访问shm
    int* pi=(int*)pv;

    *pi=100;

    //脱接shm
    int res=shmdt(pv);

    if(-1==res)
        perror("shmdt"),exit(-1);

    printf("unlink success\n");

    //如果不再使用,删除shm
    printf("删除共享内存请按Ctrl C...\n");

    if(SIG_ERR==signal(SIGINT,fa))
        perror("signal"),exit(-1);

    while(1);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/h490516509/article/details/85243267