shared-memory-and-semaphore-machenism

# 分类

  • 管道
  • 消息队列
  • 信号
  • 共享内存
  • 信号量
  • 套接字

# 共享内存

1
2
3
4
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
   shmget()  returns  the identifier of the System V shared memory segment associated with the value of the argument key.  It may be used either to obtain the identifier of a previously created shared memory segment  (when shmflg is zero and key does not have the value IPC_PRIVATE), or to create a new set.

shmflg 和文件的控制权限一样。
注意到如果 key 是 IPC_PRIVATE ,那就相当于匿名 shm,只能用于有亲属关系的进程通信。

   shmat()  attaches  the  System V shared memory segment identified by shmid to the address space of the calling process.  The attaching address is specified by shmaddr with one of the following criteria:

shmaddr 一般为空 让操作系统决定

   shmdt() detaches the shared memory segment located at the address specified by shmaddr from the address  space of the calling process.

   shmctl()  performs  the control operation specified by cmd on the System V shared memory segment whose identifier is given in shmid.

删除共享内存用 PC_RMID

# 实操 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
#define PAGE_SIZE 0x1000
int main(){
int shmid;
key_t key = ftok(PATHNAME, PROJ_ID);
assert(key != -1);
shmid = shmget(key, PAGE_SIZE, 0640|IPC_CREAT);
assert(shmid != -1);
char *shmptr = shmat(shmid, NULL, 0);
printf(
"shmptr is %p\n"
"procid is %d\n"
,shmptr, getpid()
);
memset(shmptr, "A", 0x10);
getchar();
shmdt(shmptr);
int res = shmctl(shmid, IPC_RMID, 0);
assert(res != -1);
return 0;
}

利用一个可以访问的目录创建 shmid(proj 号和当前目录可以返回一个唯一的 shmid)
在当前路径下创建一次共享内存 不删除的话就一直存在。

用命令

1
2
ipcs -m # 查看所有共享内存
ipcrm -m <shmid> # 删除一个共享内存

# 实操 2

server.c 用来创建共享内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
#define PAGE_SIZE 0x1000
int main()
{
key_t key = ftok(PATHNAME, PROJ_ID);
assert(key != -1);
int shmid;
if((shmid = shmget(key, PAGE_SIZE, IPC_CREAT | IPC_EXCL | 0666)) == -1){
perror("shmget");
exit(-1);
}

char *shmptr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while(i++ < 26)
{
printf("client# %s\n",shmptr);
sleep(1);
}
shmdt(shmptr);
sleep(2);
assert(shmctl(shmid,IPC_RMID,NULL) != -1);
return 0;
}

client.c 用来连上共享内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
#define PAGE_SIZE 0x1000
int main()
{

key_t key = ftok(PATHNAME,PROJ_ID);
assert(key != -1);
int shmid = 0;
if((shmid = shmget(key, PAGE_SIZE, 0)) == -1){
perror("shmget");
exit(-1);
}

char *shmptr = shmat(shmid,NULL,0);
sleep(2);
int i = 0;
while(i < 26)
{
shmptr[i] = 'A' + i;
i++;
shmptr[i] = 0;
sleep(1);
}
shmdt(shmptr);
sleep(2);
return 0;
}

1

# 信号量

1
2
3
int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, size_t nsops);
int semctl(int semid, int semnum, int cmd, ...);
   The  semget()  system  call  returns the System V semaphore set identifier associated with the argument key.

创建方法两种 一种用 key 里的 IPC_PRIVATE ,一种用 semflg 里的 IPC_CREAT
nsems 是信号量个数。

semop 就是等待锁和释放锁
在 semctl 中 需要用到 semun

1
2
3
4
5
6
7
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};

semop 中 需要用到

1
2
3
4
5
struct sembuf{
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
}
  • sem_op 参数

    • sem_op > 0 信号加上 sem_op 的值,表示进程释放控制的资源;
    • sem_op = 0 如果没有设置 IPC_NOWAIT,则调用进程进入睡眠状态,直到信号量的值为 0;否则进程不回睡眠,直接返回 EAGAIN
    • sem_op < 0 信号加上 sem_op 的值。若没有设置 IPC_NOWAIT ,则调用进程阻塞,直到资源可用;否则进程直接返回 EAGAIN
  • sem_flg 参数

    • 该参数可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。只有将 sem_flg 指定为 SEM_UNDO 标志后,semadj (所指定信号量针对调用进程的调整值)才会更新。 此外,如果此操作指定 SEM_UNDO,系统更新过程中会撤消此信号灯的计数(semadj)。此操作可以随时进行 — 它永远不会强制等待的过程。调用进程必须有改变信号量集的权限。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/sem.h>

class CSEM
{
private:
union semun // 用于信号灯操作的共同体。
{
int val;
struct semid_ds *buf;
unsigned short *arry;
};

int sem_id; // 信号灯描述符。
public:
bool init(key_t key); // 如果信号灯已存在,获取信号灯;如果信号灯不存在,则创建信号灯并初始化。
bool wait(); // 等待信号灯挂出。
bool post(); // 挂出信号灯。
bool destroy(); // 销毁信号灯。
};

int main(int argc, char *argv[])
{
CSEM sem;

// 用key值0x5000 初始信号灯。
if (sem.init(0x5000)==false)
{ printf("sem.init failed.\n"); return -1; }
printf("sem.init ok\n");

// 等待信信号挂出,等待成功后,将持有锁。
if (sem.wait() == false)
{ printf("sem.wait failed.\n"); return -1; }
printf("sem.wait ok\n");

sleep(10); // 在sleep的过程中,运行其它的book259程序将等待锁。

// 挂出信号灯,释放锁。
if (sem.post() == false)
{ printf("sem.post failed.\n"); return -1; }
printf("sem.post ok\n");

// 销毁信号灯。
// if (sem.destroy()==false) { printf("sem.destroy failed.\n"); return -1; }
// printf("sem.destroy ok\n");
}

bool CSEM::init(key_t key)
{
// 获取信号灯。
if ( (sem_id=semget(key,1,0640)) == -1)
{
// 如果信号灯不存在,创建它。
// No semaphore set exists for key and semflg did not specify IPC_CREAT.
if (errno == ENOENT)
{
if ( (sem_id=semget(key,1,0640|IPC_CREAT)) == -1)
{ perror("init 1 semget()"); return false; }

// 信号灯创建成功后,还需要把它初始化成可用的状态。
union semun sem_union;
// 就是设置值为1
sem_union.val = 1;
if (semctl(sem_id,0,SETVAL,sem_union) < 0)
{ perror("init semctl()"); return false; }
}
else
{ perror("init 2 semget()"); return false; }
}

return true;
}

bool CSEM::destroy()
{
if (semctl(sem_id,0,IPC_RMID) == -1)
{ perror("destroy semctl()"); return false; }

return true;
}

bool CSEM::wait()
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) { perror("wait semop()"); return false; }

return true;
}

bool CSEM::post()
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) { perror("post semop()"); return false; }

return true;
}

这个代码最重要的一点是,在 semop 为 - 1 这里
等待一个其他进程执行 (semop(sem_id, &sem_b, 1) ,其中 sem_b.sem_op = 1 , 也就是释放一个资源.
而最初的资源数量是 SETVAL 所创建的

1
2
3
4
5
6
7
8
9
10
bool CSEM::wait()
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) { perror("wait semop()"); return false; }

return true;
}

# 信号量对共享内存加锁

就是在共享内存操作之中使用上文提到的 wait 和 post,这其实就是个 P-V 操作,搞这么神神叨叨的。
然后这 PV 之间才对共享内存操作.
部分代码再上文。
client.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
char *shmptr = 0;
int shm_register(){
int shmid = shmget( (key_t)0x5005, 1024, 0640|IPC_CREAT);
if ( shmid == -1 )
{ printf("shmget() failed\n"); return -1; }
shmptr = (char *)shmat(shmid, 0,0);
return shmid;
}
int main(int argc, char *argv[])
{
CSEM sem;

// 用key值0x5000 初始信号灯。
if (sem.init(0x5000)==false)
{ printf("sem.init failed.\n"); return -1; }
printf("sem.init ok\n");

// 等待信信号挂出,等待成功后,将持有锁。
if (sem.wait() == false)
{ printf("sem.wait failed.\n"); return -1; }
printf("sem.wait ok\n");

// 申请一个共享内存并将shmptr指过去
printf("Proc1 write 0x10 A to shm");
int shmid = shm_register();
memset(shmptr, 'A', 0x10);
getchar();

// 挂出信号灯,释放锁。
if (sem.post() == false)
{ printf("sem.post failed.\n"); return -1; }
printf("sem.post ok\n");

getchar();
printf("Proc1 shm contents: %s\n", shmptr);
//detach shared memory
shmdt(shmptr);

// 销毁信号灯。
// if (sem.destroy()==false) { printf("sem.destroy failed.\n"); return -1; }
// printf("sem.destroy ok\n");
}

server.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
char *shmptr = 0;
int shm_attach(){
int shmid = shmget( (key_t)0x5005, 1024, 0);
if ( shmid == -1 )
{ printf("shmget() failed\n"); return -1; }
shmptr = (char *)shmat(shmid, 0,0);
return shmid;
}
int main(int argc, char *argv[])
{
CSEM sem;

// 用key值0x5000 初始信号灯。
if (sem.init(0x5000)==false)
{ printf("sem.init failed.\n"); return -1; }
printf("sem.init ok\n");

// 等待信信号挂出,等待成功后,将持有锁。
if (sem.wait() == false)
{ printf("sem.wait failed.\n"); return -1; }
printf("sem.wait ok\n");

// 申请一个共享内存并将shmptr指过去
int shmid = shm_attach();
printf("Proc2 contents is %s\n", shmptr);
memset(shmptr, 'B', 0x10);
getchar();

// 挂出信号灯,释放锁。
if (sem.post() == false)
{ printf("sem.post failed.\n"); return -1; }
printf("sem.post ok\n");

//detach shared memory
shmdt(shmptr);

// 销毁信号灯。
// if (sem.destroy()==false) { printf("sem.destroy failed.\n"); return -1; }
// printf("sem.destroy ok\n");
}
  • client 先初始化信号量,然后获取资源,申请到共享内存,写入’A’,释放资源
  • server 连上这个信号量,连上这个 shm,打印出 client 写入的’A’,获取资源,写入’B’
  • client 释放资源之后再看内存,已经变成’B’的形状了

2