08 November 2013

学习共享内存和信号量的结合使用

学习自《linux网络编程》,以下实例程序也是仿照书上的,但是觉得书本上的实现有问题,会发生死锁,自己修改了实现方法。

实例程序shmcopy功能:把一个文件复制为另一个文件(usage:shmcopy a b)。每次调用shmcopy就形成两个进程——读进程和写进程。它们共享两个缓冲区,这两个缓冲区作为共享内存顿来实现。当读进程把数据写入第一个缓冲区期间,写进程就把第二个缓冲区内容写出去。反之亦然。由于读和写是并发进行的,所以数据吞吐量增加了。

为了使两个进程同步,防止读进程把缓冲区装满之前,写进程就把该缓冲区内容写出去。我们使用了两个信号量。因为共享存储器机构本身没有提供同步功能。

书上的实现思路(读进程和写进程都是循环执行的):

complict

我的思路(修改了写进程):

right

源代码:

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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#ifndef __USE_FILE_OFFSET64
#define __USE_FILE_OFFSET64
#endif

#ifndef __USE_LARGEFILE64
#define __USE_LARGEFILE64
#endif

#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif

#include<stdio.h>

#include<stdlib.h>

#include<signal.h>

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/shm.h>

#include<sys/sem.h>

#include<sys/stat.h>

#include<fcntl.h>

/* 共享内存的关键字 */
#define SHMKEY1 (key_t)0x10
#define SHMKEY2 (key_t)0x15
/* 信号量的关键字 */
#define SEMKEY (key_t)0x20

/* 读写缓冲区的大小32M */
#define SIZE 33554428

/* 缓冲区数据结构 */
struct databuf {
    int d_nread;
    char d_buf[SIZE];
};

#define IFLAGS (IPC_CREAT|IPC_EXCL)
/* 将-1强制转换为struct databuf*类型,用于shmat执行结果的判断 */
#define ERR ((struct databuf*)-1)

static int shmid1,shmid2,semid;

void fatal(char *mes) {
    perror(mes);
    exit(1);
}
/* 对共享存储器段的初始化 */
void getseg(struct databuf **p1, struct databuf **p2) {
    if ((shmid1=shmget(SHMKEY1, sizeof(struct databuf), 0600|IFLAGS)) < 0) {
        fatal("shmget");
    }
    if ((shmid2=shmget(SHMKEY2, sizeof(struct databuf), 0600|IFLAGS)) < 0) {
        fatal("shmget");
    }

    if ((*p1=(struct databuf*)(shmat(shmid1, 0, 0))) == ERR)
        fatal("shmat");
    if ((*p2=(struct databuf*)(shmat(shmid2, 0, 0))) == ERR)
        fatal("shmat");
}
/* 对信号量的初始化 */
void getsem() {
    if ((semid=semget(SEMKEY, 2, 0600|IFLAGS)) < 0) {
        fatal("semget");
    }
    if (semctl(semid, 0, SETVAL, 0) < 0) {
        fatal("semctl");
    }
    if (semctl(semid, 1, SETVAL, 0) < 0) {
        fatal("semctl");
    }
}
/* 程序结束时释放共享内存和信号量*/
void removex() {
    if (shmctl(shmid1, IPC_RMID, 0) < 0)
        fatal("shmctl");
    if (shmctl(shmid2, IPC_RMID, 0) < 0)
        fatal("shmctl");
    if (semctl(semid, IPC_RMID, 0) < 0) {
        fatal("semctl");
    }
}
/* 定义对两个信号量的操作*/
struct sembuf p1={0,-1,0},p2={1,-1,0},v1={0,1,0},v2={1,1,0};
/* 读进程 */
void reader(int semid, struct databuf *buf1, struct databuf *buf2, char *filepath) {
    int fd = open(filepath, O_RDONLY|O_LARGEFILE);
    if (fd == -1) {
        fatal("open");
    }

    for (;;) {
        buf1->d_nread = read(fd, buf1->d_buf, SIZE);
        semop(semid, &v1, 1);
        if (buf1->d_nread == -1 || buf1->d_nread == 0) {
            return;
        }
        semop(semid, &p2, 1);

        buf2->d_nread = read(fd, buf2->d_buf, SIZE);
        semop(semid, &v2, 1);
        if (buf2->d_nread == -1 || buf2->d_nread == 0) {
            return;
        }
        semop(semid, &p1, 1);
    }
}
/* 写进程 */
void writer(int semid, struct databuf *buf1, struct databuf *buf2, char *filepath) {
    int fd = open(filepath, O_WRONLY|O_CREAT|O_LARGEFILE, S_IREAD|S_IWRITE|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
    if (fd == -1) {
        fatal("open");
    }

    for (;;) {
        if (buf2->d_nread == -1 || buf2->d_nread == 0) {
            return;
        }
        write(fd, buf2->d_buf, buf2->d_nread);
        semop(semid, &v2, 1);
        semop(semid, &p1, 1);
        if (buf1->d_nread == -1 || buf1->d_nread == 0) {
            return;
        }
        write(fd, buf1->d_buf, buf1->d_nread);
        semop(semid, &v1, 1);
        semop(semid, &p2, 1);

    }
}
/* 主进程 */
void main(int argc, char *argv[]) {
    int pid;
    struct databuf *buf1,*buf2;
    getsem();

    getseg(&buf1, &buf2);
    buf2->d_nread = -2;//使写进程在第一次无法将buf2中数据写入文件
    switch(pid = fork()) {
        case -1:fatal("fork");break;
        case 0:writer(semid, buf1, buf2, argv[2]);break;
        default:reader(semid, buf1, buf2, argv[1]);
                removex();
    }
    exit(0);
}

在测试时,发现无法读入超过2G的文件,google了下,原来32位系统C程序要打开超过2G的文件,需要一些方法:

  1. 方法一:同上面代码一样,在程序中添加宏定义,以及在打开文件时,加上O_LARGEFILE,之后使用gcc直接编译;
  2. 方法二:在gcc编译时加上一些参数:gcc -o shmcopy2 shmcopy2.c -D_FILE_OFFSET_BITS=64。

测试使用的是一个2.8G的文件,结果是性能和cp命令差不多。