cha50.虚拟内存操作

50.1

编写一个程序使其为RLIMITMEMLOCK资源限制设置一个值之后将数量超过这个限制的内存锁进内存来验证RLIMITMEMLOCK资源限制的作用。

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
//
// Created by root on 7/18/23.
//
#include <utils.h>

#include <limits.h>
#include <sys/resource.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <signal.h>

#define USER 10087

void test(__rlimit_resource_t res, const char *name,void (*task)(__rlimit_resource_t res, struct rlimit *)) {
logger(LOG_INFO, "test: %d(%s)", res, name);
pid_t pid;
CHECK_RET((pid = fork()) != -1, return;);
if(!pid) {
struct rlimit lim;
CHECK_RET(getrlimit(res, &lim) != -1, return;);
logger(LOG_INFO, "soft=%lu, hard=%lu", lim.rlim_cur, lim.rlim_max);
task(res, &lim);
logger(LOG_INFO, "soft=%lu, hard=%lu", lim.rlim_cur, lim.rlim_max);
exit(0);
}
int status;
CHECK_RET(waitpid(pid, &status, 0) != -1, return;);
if(WIFSIGNALED(status)) logger(LOG_INFO, "status:%d, coredump:%d, term sig:%s(%d)", WEXITSTATUS(status), WCOREDUMP(status), strsignal(WTERMSIG(status)), WTERMSIG(status));
else if(WIFSTOPPED(status)) logger(LOG_INFO, "status:%d, coredump:%d, term sig:%s(%d)", WEXITSTATUS(status), WCOREDUMP(status), strsignal(WSTOPSIG(status)), WSTOPSIG(status));
else logger(LOG_INFO, "status:%d, coredump:%d", WEXITSTATUS(status), WCOREDUMP(status));
CHECK_RET(WEXITSTATUS(status) != 0 || WCOREDUMP(status) || WIFSTOPPED(status) || WIFSIGNALED(status), return;);
}

#define invoke_test(res) test(res, #res, f##res)

#define fun(name) void f##name(__rlimit_resource_t res, struct rlimit *lim)

fun(RLIMIT_MEMLOCK) {
long pagesize = sysconf(_SC_PAGESIZE);
bool error_occured = false;
lim->rlim_cur = lim->rlim_max = pagesize * 2; // = pagesize时,lock一个也会出错,可能已经默认有一页的内存被lock了(比如代码段,数据段
CHECK_EXIT(setrlimit(res, lim) != -1);
CHECK_EXIT(setuid(USER) != -1);
void *mem;
mem = malloc(pagesize*2);
CHECK_LOG(mem != NULL);
if(mlock(mem, pagesize) == -1) { //success
CHECK_LOG(false);
error_occured = true;
}
CHECK_LOG(munlock(mem, pagesize) != -1);
safe_free(mem);

mem = malloc(pagesize*3);
CHECK_LOG(mem != NULL);
if(mlock(mem, pagesize*2) == -1) { //fail
CHECK_LOG(false);
error_occured = true;
}
CHECK_LOG(munlock(mem, pagesize *2) != -1);
safe_free(mem);

if((mem = mmap(NULL, pagesize *3, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS | MAP_LOCKED, -1, 0)) == MAP_FAILED) { //fail
CHECK_LOG(false);
error_occured = true;
} else {
CHECK_LOG(munmap(mem, pagesize *3) != -1);
}

CHECK_LOG((mem = mmap(NULL, pagesize *3, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)) != MAP_FAILED);
if(mlock(mem, pagesize*3) == -1) { //fail
CHECK_LOG(false);
error_occured = true;
}
CHECK_LOG(munmap(mem, pagesize *3) != -1);


if(error_occured) {
exit(1);
}
exit(0);
}

#define BUFFER_SIZE 2048
struct msgType {
long mtype;
char mcontent[BUFFER_SIZE];
};
#define REQ_SIZE (BUFFER_SIZE)

void on_exit_msg(int status, void *id) {
CHECK_LOG(msgctl(*(int *)id, IPC_RMID, NULL) != -1);
}
void timeout(int sig) {
logger(LOG_INFO, "received sig(%d):%s", sig, strsignal(sig));
exit(1);
// CHECK_LOG(signal(SIGALRM, SIG_DFL) != SIG_ERR);
// raise(SIGALRM);
}
fun(RLIMIT_MSGQUEUE) {
struct msgType msg;
msg.mtype = 1;
strcpy(msg.mcontent, "Meow!!");
int id;
CHECK_EXIT((id = msgget(IPC_PRIVATE, IPC_CREAT | 0666)) != -1);
CHECK_EXIT(msgsnd(id, &msg, REQ_SIZE, 0) != -1);
CHECK_EXIT(msgctl(id, IPC_RMID, NULL) != -1);

lim->rlim_cur = lim->rlim_max = BUFFER_SIZE/2;
CHECK_EXIT(setrlimit(res, lim) != -1);
CHECK_EXIT(setuid(USER) != -1);
on_exit(on_exit_msg, &id);
CHECK_EXIT(signal(SIGALRM, timeout) != SIG_ERR);
CHECK_EXIT((id = msgget(IPC_PRIVATE, IPC_CREAT | 0666)) != -1);
CHECK_EXIT(msgsnd(id, &msg, REQ_SIZE, 0) != -1);
CHECK_EXIT(msgsnd(id, &msg, REQ_SIZE, 0) != -1);
CHECK_EXIT(msgsnd(id, &msg, REQ_SIZE, 0) != -1);
CHECK_EXIT(msgsnd(id, &msg, REQ_SIZE, 0) != -1);
CHECK_EXIT(msgsnd(id, &msg, REQ_SIZE, 0) != -1);
CHECK_EXIT(msgsnd(id, &msg, REQ_SIZE, 0) != -1);
CHECK_EXIT(msgsnd(id, &msg, REQ_SIZE, 0) != -1);
CHECK_EXIT(msgsnd(id, &msg, REQ_SIZE, 0) != -1);
CHECK_EXIT(alarm(1) != -1);
CHECK_EXIT(msgsnd(id, &msg, REQ_SIZE, 0) != -1);

}

fun(RLIMIT_RSS);

int main(int argc, char ** argv) {
invoke_test(RLIMIT_MEMLOCK); //还没学
invoke_test(RLIMIT_MSGQUEUE); //还没学

// invoke_test(RLIMIT_RSS); //linux没作用
}

50.2

写一个程序来验证madvise MADV DONTNEED操作在一个可写MAP_PRIVATE映射上的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//
// Created by root on 7/18/23.
//
#include <utils.h>

#include <limits.h>
#include <sys/resource.h>
#include <sys/mman.h>

int main(int argc, char ** argv) {
long pagesize = sysconf(_SC_PAGESIZE);
char *mem;
CHECK_EXIT((mem = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) != MAP_FAILED);
memset(mem, 'a', pagesize);
CHECK_EXIT(madvise(mem, pagesize, MADV_DONTNEED) != -1);
for(int i = 0; i < pagesize; i++) {
if(mem[i] == 0) logger(LOG_INFO, "mem:%p filled with '\\0'", mem);
else if(mem[i] == 'a') logger(LOG_INFO, "mem:%p remains to be 'a'", mem);
else logger(LOG_INFO, "mem:%p contains dirty data", mem);
}
CHECK_EXIT(munmap(mem, pagesize) != -1);
}

cha49.内存映射

读书笔记

文件映射 匿名映射
私有映射 老刘吃独食 (文件), 别人可以看 (读取同一块内存区域), 但是想吃 (修改, 写时复制)需要自己做一份一模一样的(修改不会反应到文件上) 老刘吃空气 (全0大块内存), 给儿子看, 儿子想吃需要自己做一份一样的 (写时复制), 不给老李家看, 不给老李家吃
共享映射 老刘吃饭 (文件), 给儿子吃 (子进程可访问 修改), 也给老李吃 (非相关进程也可访问 修改)(修改会反应到文件上) 老刘吃空气 (全0内存), 给儿子吃 (子进程可访问 修改), 不给老李家吃 (非相关进程不可访问 修改)

创建公共头文件

为了减少每个练习中重复的定义,如logger, safe_free,定义一个头文件共所有程序使用

utils.h

include/utils.h
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
#ifndef __MEOW_UTILS__
#define __MEOW_UTILS__
//
// Created by root on 9/14/23.
//
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <syslog.h>
#include <wait.h>
#include <sys/stat.h>
#include <sys/mman.h>


extern FILE *logfile;
extern bool syslog_enable;

#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL LOG_DEBUG
#endif
#define STRING_MSG "ERROR OCCURED!"

#define alloc_sprintf(__alloc_sprintf_str, __format...) do { \
int __alloc_sprintf_len = snprintf(NULL, 0, __format); \
(__alloc_sprintf_str) = malloc(__alloc_sprintf_len+1); \
if(__alloc_sprintf_str != NULL) \
snprintf(__alloc_sprintf_str, __alloc_sprintf_len+1, __format); \
} while(0)

#define logger(level, msg...) do { \
if(level <= DEBUG_LEVEL) { \
fprintf(logfile, "[%ld] ", (long) getpid()); \
fprintf(logfile, msg); \
fprintf(logfile, "\n"); \
} \
if(syslog_enable) { \
char *__syslog_enable__data; \
alloc_sprintf(__syslog_enable__data, msg); \
syslog(level, "%s", __syslog_enable__data); \
safe_free(__syslog_enable__data); \
} \
} while(0)

#define CHECK_RET_MSG_IMPL(x, strx, ret, msg...) \
logger(LOG_DEBUG, "checking: \"%s\"", strx); \
do { \
if(!(x)) { \
if(errno != 0) logger(LOG_ERR, "Error(%d): %s", errno, strerror(errno)); \
logger(LOG_ERR, "%s:%d", __FILE__, __LINE__); \
logger(LOG_ERR, "unmet condition:\"%s\"", strx); \
logger(LOG_ERR, msg); \
ret; \
} \
errno = 0; \
} while(0)

#define CHECK_RET_MSG(x, ret, msg...) CHECK_RET_MSG_IMPL(x, #x, ret, msg...)
#define CHECK_MSG(x, msg...) CHECK_RET_MSG_IMPL(x, #x, return -1;, msg)
#define CHECK_EXIT_MSG(x, msg...) CHECK_RET_MSG_IMPL(x, #x, exit(EXIT_FAILURE);, msg)
#define CHECK_LOG_MSG(x, msg...) CHECK_RET_MSG_IMPL(x, #x, ;, msg)

#define CHECK_RET(x, ret) CHECK_RET_MSG_IMPL(x, #x, ret;, STRING_MSG)
#define CHECK(x) CHECK_RET_MSG_IMPL(x, #x, return -1;, STRING_MSG)
#define CHECK_EXIT(x) CHECK_RET_MSG_IMPL(x, #x, exit(EXIT_FAILURE);, STRING_MSG)
#define CHECK_LOG(x) CHECK_RET_MSG_IMPL(x, #x, ;, STRING_MSG)

int safe_atoi(const char *num, int *ret);
#define safe_free(__safe_free_ptr) do { if(__safe_free_ptr) { free(__safe_free_ptr); (__safe_free_ptr) = NULL;} } while(0)
#endif
阅读更多

cha48.System V 共享内存

读书笔记

shmat/mmap时可以指定虚拟内存地址,指定其位置,这个位置是需要提前malloc或sbrk创建出来的吗?

48.1

使用事件标记来替换程序清单48-2(svshm_xfr_writerc)和程序清单48-3(svshm_xfr_reader.c)中的二元信号量。(就是27节的event flag)

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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
//
// Created by root on 9/14/23.
//
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <syslog.h>
#include <wait.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>

#define DEBUG_LEVEL LOG_DEBUG

#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL LOG_INFO
#endif
#define STRING_MSG "MSG"
FILE *logfile = NULL;
bool syslog_enable = false;
void __attribute__ ((constructor)) init() {
syslog_enable = false;
logfile = stderr;
}

#define alloc_sprintf(__alloc_sprintf_str, __format...) do { \
int __alloc_sprintf_len = snprintf(NULL, 0, __format); \
(__alloc_sprintf_str) = malloc(__alloc_sprintf_len+1); \
if(__alloc_sprintf_str != NULL) \
snprintf(__alloc_sprintf_str, __alloc_sprintf_len+1, __format); \
} while(0)

#define logger(level, msg...) do { \
if(level <= DEBUG_LEVEL) { \
fprintf(logfile, "[%ld] ", (long) getpid()); \
fprintf(logfile, msg); \
fprintf(logfile, "\n"); \
} \
if(syslog_enable) { \
char *data; \
alloc_sprintf(data, msg); \
syslog(level, "%s", data); \
safe_free(data); \
} \
} while(0)

#define COND_RET(x, ret, msg...) \
do { \
if(!(x)) { \
if(errno != 0) logger(LOG_ERR, "Error(%d): %s", errno, strerror(errno)); \
logger(LOG_ERR, "%s:%d", __FILE__, __LINE__); \
logger(LOG_ERR, "unmet condition:\"%s\"", #x); \
logger(LOG_ERR, msg); \
ret; \
} \
errno = 0; \
} while(0)

#define CHECK(x, msg...) COND_RET(x, return -1;, msg)
#define CHECK_EXIT(x, msg...) COND_RET(x, exit(EXIT_FAILURE);, msg)
#define CHECK_LOG(x, msg...) COND_RET(x, ;, msg)

#define safe_free(__safe_free_ptr) do { if(__safe_free_ptr) { free(__safe_free_ptr); (__safe_free_ptr) = NULL;} } while(0)

#define BUFFER_SIZE 4096

union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
#if defined(__linux__)
struct seminfo *__buf;
#endif
};

typedef struct {
int semid;
}EventFlag_t;

int incrsem(int sem_id, int semnum, short incr, short flg) {
struct sembuf sembuf;
sembuf = (struct sembuf){
.sem_num=semnum,
.sem_flg=flg,
.sem_op=incr
};
return semop(sem_id, &sembuf, 1);
}

int P(int sem_id) {
return incrsem(sem_id, 0, -1, 0);
}

int V(int sem_id) {
return incrsem(sem_id, 0, 1, 0);
}

int waitFor(int sem_id) {
return incrsem(sem_id, 1, 0, 0);
}

int getsem(int sem_id, int semnum, short *n) {
union semun arg;
int ret = semctl(sem_id, semnum, GETVAL, arg);
*n = ret;
return ret;
}

int setsem(int sem_id, int semnum, short n) {
union semun arg;
arg.val = n;
return semctl(sem_id, semnum, SETVAL, arg);
}

int notifyAll(int sem_id) {
return setsem(sem_id, 1, 0);
}
EventFlag_t *newEventFlag(const char *file, char x) {
EventFlag_t eventFlag;
key_t key = ftok(file, x);
eventFlag.semid = semget(key, 4, IPC_CREAT | IPC_EXCL | 0666); // sem[0] as mutex, sem[1] as notifier, sem[2]|sem[3] as flag
union semun arg;
if(eventFlag.semid == -1) {
if(errno == EEXIST) {
eventFlag.semid = semget(key, 4, 0666);
COND_RET(eventFlag.semid != -1, return NULL, STRING_MSG);
struct semid_ds ds;
arg.buf = &ds;
COND_RET(semctl(eventFlag.semid, 0, IPC_STAT, arg) != -1, return NULL, STRING_MSG);
while (ds.sem_otime == 0) {
logger(LOG_INFO, "waiting for sem init");
sleep(1);
COND_RET(semctl(eventFlag.semid, 0, IPC_STAT, arg) != -1, return NULL, STRING_MSG);
}
logger(LOG_INFO, "semget get old");
} else {
CHECK_LOG(false, STRING_MSG);
return NULL;
}
} else {
arg.array = (unsigned short *)&(unsigned short[]){0, 0, 0, 0}; // sem[0] = 1, sem[1] = 0
if(semctl(eventFlag.semid, 0, SETALL, arg) == -1) {
CHECK_LOG(false, STRING_MSG);
CHECK_LOG(semctl(eventFlag.semid, 0, IPC_RMID) != -1, STRING_MSG);
return NULL;
}
if(incrsem(eventFlag.semid, 0,1,0) == -1) {
CHECK_LOG(false, STRING_MSG);
CHECK_LOG(semctl(eventFlag.semid, 0, IPC_RMID) != -1, STRING_MSG);
return NULL;
}
logger(LOG_INFO, "semget create new");
}
return memcpy(malloc(sizeof(EventFlag_t)), &eventFlag, sizeof(EventFlag_t));
}

int __setEventFlag(EventFlag_t *eventFlag, int flag) {
short currentFlag;

CHECK(getsem(eventFlag->semid, 2, &currentFlag) != -1, STRING_MSG);
CHECK(setsem(eventFlag->semid, 2, currentFlag | (flag & 0xffff)) != -1, STRING_MSG);

CHECK(getsem(eventFlag->semid, 3, &currentFlag) != -1, STRING_MSG);
CHECK(setsem(eventFlag->semid, 3, currentFlag | ((flag >> 16) & 0xffff)) != -1, STRING_MSG);

CHECK(notifyAll(eventFlag->semid) != -1, STRING_MSG);
}

int setEventFlag(EventFlag_t *eventFlag, int flag) {
CHECK(P(eventFlag->semid) != -1, STRING_MSG);
CHECK(__setEventFlag(eventFlag, flag) != -1, STRING_MSG);
CHECK(V(eventFlag->semid) != -1, STRING_MSG);
return 0;
}

int __clearEventFlag(EventFlag_t *eventFlag, int flag) {
short currentFlag;

CHECK(getsem(eventFlag->semid, 2, &currentFlag) != -1, STRING_MSG);
CHECK(setsem(eventFlag->semid, 2, currentFlag & ~(flag & 0xffff)) != -1, STRING_MSG);

CHECK(getsem(eventFlag->semid, 3, &currentFlag) != -1, STRING_MSG);
CHECK(setsem(eventFlag->semid, 3, currentFlag & ~((flag >> 16) & 0xffff)) != -1, STRING_MSG);
// CHECK(notifyAll(eventFlag->semid) != -1, STRING_MSG);
return 0;
}
int clearEventFlag(EventFlag_t *eventFlag, int flag) {
CHECK(P(eventFlag->semid) != -1, STRING_MSG);
CHECK(__clearEventFlag(eventFlag, flag) != -1, STRING_MSG);
CHECK(V(eventFlag->semid) != -1, STRING_MSG);
return 0;
}

int __getEventFlag(EventFlag_t *eventFlag, int *flag) {
if(flag) {
short currentFlag = 0;
int ret = 0;
CHECK(getsem(eventFlag->semid, 2, &currentFlag) != -1, STRING_MSG);
ret |= currentFlag;
CHECK(getsem(eventFlag->semid, 3, &currentFlag) != -1, STRING_MSG);
ret |= currentFlag << 16;
*flag = ret;
return 0;
}
return -1;
}

int getEventFlag(EventFlag_t *eventFlag, int *flag) {
CHECK(P(eventFlag->semid) != -1, STRING_MSG);
CHECK_LOG(__getEventFlag(eventFlag, flag) != -1, STRING_MSG);
CHECK(V(eventFlag->semid) != -1, STRING_MSG);
return 0;
}
#define waitForMethod(eventFlag, flag, method) do { \
int currentFlag;\
CHECK(P(eventFlag->semid) != -1, STRING_MSG);\
__getEventFlag(eventFlag, &currentFlag); \
while (method) {\
CHECK(V(eventFlag->semid) != -1, STRING_MSG);\
CHECK(waitFor(eventFlag->semid) != -1, STRING_MSG);\
CHECK(P(eventFlag->semid) != -1, STRING_MSG);\
__getEventFlag(eventFlag, &currentFlag); \
}\
CHECK(V(eventFlag->semid) != -1, STRING_MSG);\
} while(0)

int waitForAny(EventFlag_t *eventFlag, int flag) {
waitForMethod(eventFlag, flag, (currentFlag & flag) == 0);
return 0;
}
int waitForAll(EventFlag_t *eventFlag, int flag) {
waitForMethod(eventFlag, flag, (currentFlag & flag) != flag);
return 0;
}

int waitForAllAndClear(EventFlag_t *eventFlag, int flag) {
int currentFlag;
CHECK(P(eventFlag->semid) != -1, STRING_MSG);
__getEventFlag(eventFlag, &currentFlag);
while ((currentFlag & flag) != flag) {
CHECK(V(eventFlag->semid) != -1, STRING_MSG);
CHECK(waitFor(eventFlag->semid) != -1, STRING_MSG);
CHECK(P(eventFlag->semid) != -1, STRING_MSG);
__getEventFlag(eventFlag, &currentFlag);
}
__clearEventFlag(eventFlag, flag);
CHECK(V(eventFlag->semid) != -1, STRING_MSG);
return 0;
}
void destroyEventFlag(EventFlag_t **eventFlag) {
if(eventFlag) {
if(*eventFlag) {
semctl((*eventFlag)->semid, 0, IPC_RMID);
}
safe_free(*eventFlag);
}
}

#define WRITE_FLAG 1
#define READ_FLAG 2
struct ShmStruct {
ssize_t len;
char buf[BUFFER_SIZE - sizeof(int)];
};

void onExitEventFlag(int status, void *arg) {
destroyEventFlag((EventFlag_t **)&arg);
logger(LOG_INFO, "onExitEventFlag, status = %d", status);
}

void onExitSHM(int status, void *id) {
shmctl(*(int *)id, IPC_RMID, NULL);
logger(LOG_INFO, "onExitSHM, status = %d", status);
}

void onExitSHMdt(int status, void *ptr) {
shmdt(ptr);
logger(LOG_INFO, "onExitSHMdt, status = %d", status);
}

void handler(int sig) {
exit(0);
}
int main(int argc, char **argv) {
CHECK_EXIT(argc == 2, "Usage: %s writer|reader", argv[0]);
signal(SIGINT, handler);
signal(SIGTERM, handler);
signal(SIGHUP, handler);
EventFlag_t *eventFlag = newEventFlag(argv[0], 'a');
on_exit(onExitEventFlag, eventFlag);
key_t key = ftok(argv[0], 'c');
CHECK_EXIT(key != -1, STRING_MSG);
int shmid = shmget(key, BUFFER_SIZE, IPC_CREAT | IPC_EXCL | 0666);
int savedErrno = errno;
CHECK_EXIT(shmid != -1 || errno == EEXIST, STRING_MSG);
if(shmid != -1) {
setEventFlag(eventFlag, WRITE_FLAG);
} else {
if(savedErrno == EEXIST) {
shmid = shmget(key, BUFFER_SIZE, 0666);
} else {
CHECK_EXIT(false, "code will never reach");
}
}
on_exit(onExitSHM, &shmid);
struct ShmStruct *shmp = shmat(shmid, NULL, 0);
CHECK_EXIT(shmp != NULL, STRING_MSG);
on_exit(onExitSHMdt, shmp);
if(!strcmp(argv[1], "writer")) {
size_t size = 0;
bool stop = false;
for(int i = 0; !stop ;i++) {
CHECK_EXIT(waitForAllAndClear(eventFlag, WRITE_FLAG) != -1, STRING_MSG);
logger(LOG_INFO, "write start");
CHECK_EXIT((shmp->len = read(STDIN_FILENO, shmp->buf, BUFFER_SIZE - sizeof(int))) >= 0, STRING_MSG);
size += shmp->len;
if(shmp->len == 0) {
stop = true;
}
CHECK_EXIT(setEventFlag(eventFlag, READ_FLAG) != -1, STRING_MSG);
logger(LOG_INFO, "write end: i = %d, size = %lu", i, size);
}
} else {
size_t size = 0;
bool stop = false;
for(int i = 0; !stop ;i++) {
CHECK_EXIT(waitForAllAndClear(eventFlag, READ_FLAG) != -1, STRING_MSG);
logger(LOG_INFO, "read start");
CHECK_EXIT(write(STDIN_FILENO, shmp->buf, shmp->len) == shmp->len, STRING_MSG);
size += shmp->len;
if(shmp->len == 0) {
stop = true;
}
CHECK_EXIT(setEventFlag(eventFlag, WRITE_FLAG) != -1, STRING_MSG);
logger(LOG_INFO, "read end: i = %d, size = %lu", i, size);
}

}
}

48.2

阅读更多

cha47.System V 信号量

47.2 47.3 47.4

  • 使用信号量,实现进程间的同步
  • 验证SEM_UNDO是否会改变sempid
  • 实现P V操作,实现testP(在程序清单47-l0给出的代码(binary_sems.c)中增加一个reserveSemNBO函数来使用PC_NOWAIT标记实现有条件的预留操作。)
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <stdbool.h>
#include <stddef.h>
#include <syslog.h>
#include <wait.h>


#define DEBUG_LEVEL LOG_DEBUG

#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL LOG_INFO
#endif
FILE *logfile = NULL;
bool syslog_enable = false;

#define alloc_sprintf(__alloc_sprintf_str, __format...) do { \
int __alloc_sprintf_len = snprintf(NULL, 0, __format); \
(__alloc_sprintf_str) = malloc(__alloc_sprintf_len+1); \
if(__alloc_sprintf_str != NULL) \
snprintf(__alloc_sprintf_str, __alloc_sprintf_len+1, __format); \
} while(0)

#define logger(level, msg...) do { \
if(level <= DEBUG_LEVEL) { \
fprintf(logfile, msg); \
fprintf(logfile, "\n"); \
} \
if(syslog_enable) { \
char *data; \
alloc_sprintf(data, msg); \
syslog(level, "%s", data); \
safe_free(data); \
} \
} while(0)

#define COND_RET(x, ret, msg...) \
do { \
if(!(x)) { \
if(errno == 0)logger(LOG_ERR, "%s:%d\nunmet condition:\"%s\"\n", __FILE__, __LINE__, #x); \
else logger(LOG_ERR, "%s:%d\nerror: %s\nunmet condition:\"%s\"\n", __FILE__, __LINE__,strerror(errno), #x); \
logger(LOG_ERR, msg); \
logger(LOG_ERR, "\n"); \
ret \
} \
} while(0)

#define CHECK(x, msg...) COND_RET(x, return -1;, msg)
#define CHECK_EXIT(x, msg...) COND_RET(x, exit(EXIT_FAILURE);, msg)
#define CHECK_LOG(x, msg...) COND_RET(x, ;, msg)

#define safe_free(__safe_free_ptr) do { if(__safe_free_ptr) { free(__safe_free_ptr); (__safe_free_ptr) = NULL;} } while(0)

#define BUFFER_SIZE 4096


key_t sem_key = 0;
int sem_id = 0;
void rmsem() {
CHECK_LOG(semctl(sem_id, 0, IPC_RMID) != -1, "");
}

union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
#if defined(__linux__)
struct seminfo *__buf;
#endif
};

pid_t taskid = 0;

int safe_atoi(const char *num) {
const char *ptr = num;
while (ptr && *ptr) {
CHECK_EXIT(*ptr >= '0' && *ptr <= '9', "");
ptr++;
}
return atoi(num);
}

int incrsem(int semnum, short incr, short flg) {
struct sembuf sembuf;
sembuf = (struct sembuf){
.sem_num=semnum,
.sem_flg=flg,
.sem_op=incr
};
return semop(sem_id, &sembuf, 1);
}
void siginthandler(int sig) {
rmsem();
}

int initAvai(int semnum) {
union semun arg;
arg.val = 1;
if(semctl(sem_id, semnum, SETVAL, arg) == -1) return -1;
return incrsem(semnum, 1, 0);
}


int initInUse(int semnum) {
union semun arg;
arg.val = 0;
if(semctl(sem_id, semnum, SETVAL, arg) == -1) return -1;
return incrsem(semnum, 0, 0);
}


int P(int semnum) {
int ret;
do {
ret = incrsem(semnum, -1, 0);
} while (ret == -1 && errno == EINTR);
return ret;
}

int testP(int semnum) { // return 1 if sem would be blocked, return 0 if sem would not be blocked.
int ret = incrsem(semnum, -1, IPC_NOWAIT);
if(ret == -1) {
if(errno == EAGAIN)return 1;
else return ret;
}
return 0;
}

int V(int semnum) {
return incrsem(semnum, 1, 0);
}

void init(int argc, char *argv[]) {
logfile = stderr;
sem_key = ftok(argv[0], 'x');
// taskid = safe_atoi(argv[1]); // 1, 2
taskid = fork();
logger(LOG_INFO, "PID = %ld, taskID = %d", (long)getpid(), taskid);
CHECK_EXIT(taskid != -1, "");
union semun arg;
if((sem_id = semget(sem_key, 3, IPC_CREAT | IPC_EXCL | 0666)) != -1) {
atexit(rmsem);
signal(SIGINT, siginthandler);
arg.array = (unsigned short *)&(unsigned short[]){0, 0, 0}; // sem[0] = 1, sem[1] = 0
CHECK_EXIT(semctl(sem_id, 0, SETALL, arg) != -1, "");
CHECK_EXIT(initInUse(1) != -1, "");
CHECK_EXIT(initAvai(0) != -1, "");
} else {
if(errno != EEXIST) {
CHECK_EXIT(false, "");
}
sem_id = semget(sem_key, 3, 0666);
}
struct semid_ds ds;
arg.buf = &ds;
CHECK_EXIT(semctl(sem_id, 0, IPC_STAT, arg) != -1, "");
while (ds.sem_otime == 0) {
logger(LOG_INFO, "waiting for sem init");
sleep(1);
CHECK_EXIT(semctl(sem_id, 0, IPC_STAT, arg) != -1, "");
}
logger(LOG_INFO, "finish init. PID = %ld, taskID = %d", (long)getpid(), taskid);
}

pid_t printsem(int semnum) {
pid_t pid;
CHECK_EXIT((pid = semctl(sem_id, semnum, GETPID)) != -1, "");
logger(LOG_INFO, "semnum=%d, last semop pid=%ld", semnum, (long) pid);
return pid;
}


int main(int argc, char *argv[]) {
init(argc, argv);
switch (taskid) {
case 0:
CHECK_EXIT(P(2) != -1, "");
CHECK_EXIT(incrsem(0, -1, SEM_UNDO) != -1, "");
CHECK_EXIT(V(1) != -1, "");
CHECK_EXIT(P(2) != -1, "");
break;
default:
printsem(0);
CHECK_EXIT(V(2) != -1, "");
CHECK_EXIT(P(1) != -1, "");
printsem(0);
CHECK_EXIT(incrsem(0, 1, 0) != -1, "");
printsem(0);
CHECK_EXIT(V(2) != -1, "");
wait(NULL);
pid_t lastpid = printsem(0);
if(lastpid == taskid) {
logger(LOG_INFO, "IPC_UNDO will change last pid");
} else if (lastpid == getpid()) {
logger(LOG_INFO, "IPC_UNDO will not change last pid");
} else {
logger(LOG_INFO, "other process change sem");
}

break;
}
}

47.6

使用命名管道实现一个二元信号量协议。提供函数来预留、释放以及有条件地预留信号量。

需要共享fd

阅读更多

cha46.System V 消息队列

46.2

改造44.8节中的序号客户端-服务器应用程序使之使用System V消息队列。使用单个消息队列来传输客户端到服务器以及服务器到客户端之间的消息。使用 46.8节中介绍的消息类型规范。

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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <stdbool.h>
#include <stddef.h>
#include <syslog.h>
#include <wait.h>

//#define DEBUG_LEVEL LOG_DEBUG

#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL LOG_INFO
#endif

FILE *logfile = NULL;

void logger(int level, const char *msg, ...) {
if(level <= DEBUG_LEVEL) {
va_list fmt;
va_start(fmt, msg);
vfprintf(logfile, msg, fmt);
va_end(fmt);
fprintf(logfile, "\n");
}
}
#define COND_RET(x, ret, msg...) \
do { \
errno = 0;\
if(!(x)) { \
if(errno == 0)logger(LOG_ERR, "%s:%d\nunmet condition:\"%s\"\n", __FILE__, __LINE__, #x); \
else logger(LOG_ERR, "%s:%d\nerror: %s\nunmet condition:\"%s\"\n", __FILE__, __LINE__,strerror(errno), #x); \
logger(LOG_ERR, msg); \
logger(LOG_ERR, "\n"); \
ret \
} \
} while(0)

#define CHECK(x, msg...) COND_RET(x, return -1;, msg)
#define CHECK_EXIT(x, msg...) COND_RET(x, exit(1);, msg)
#define CHECK_LOG(x, msg...) COND_RET(x, ;, msg)

#define safe_free(ptr) do { if(ptr) { free(ptr); ptr = NULL;} } while(0)

char *alloc_sprintf(const char * __format, ...) {
va_list fmt;
va_start(fmt, __format);
int len = vsnprintf(NULL, 0, __format, fmt);
va_end(fmt);
char *str = malloc(len+1);
COND_RET(str != NULL, return NULL;, "");
va_start(fmt, __format);
vsnprintf(str, len+1, __format, fmt);
va_end(fmt);
return str;
}


char *MSQ_KEY_FILE = NULL;
#define BUFFER_SIZE 4096
static pid_t SELF_PID = -1;
static int MSG_ID = -1;
#define SERVER_MSG_TYPE 1
#define CLIENT_MSG_TYPE SELF_PID

#define REQ_TOUCH 1
#define REQ_RM 2
#define REQ_CAT 3
#define REQ_LS 4
#define REQ_RDLK 5

#define RET_MORE 0x0001
#define RET_SUCCESS 0x0010

struct ServerReturnType {
long mtype;
int msgType;
char mcontent[BUFFER_SIZE];
};
struct ClientRequestType {
long mtype;
int msgType;
pid_t pid;
char mcontent[BUFFER_SIZE];
};
#define REQ_SIZE (offsetof(struct ClientRequestType, mcontent) - offsetof(struct ClientRequestType, msgType) + BUFFER_SIZE)
#define RET_SIZE (offsetof(struct ServerReturnType, mcontent) - offsetof(struct ServerReturnType, msgType) + BUFFER_SIZE)
void atexitCloseLogfile(void) {
logger(LOG_DEBUG, "SERVER: atexitCloseLogfile");
CHECK_LOG(fclose(logfile) != -1, "");
}

void atexitFreeKeyfile(void) {
logger(LOG_DEBUG, "SERVER: atexitFreeKeyfile");
safe_free(MSQ_KEY_FILE);
}

void atexitRMID(void) {
logger(LOG_DEBUG, "SERVER: atexitRMID");
CHECK_LOG(msgctl(MSG_ID, IPC_RMID, NULL) != -1, "");
}


void client(const char *filename, int msgType) {
struct ServerReturnType ret;
struct ClientRequestType req;

req.mtype = SERVER_MSG_TYPE;
req.msgType = msgType;
req.pid = SELF_PID;
strcpy(req.mcontent, filename);

ssize_t send = msgsnd(MSG_ID, &req, REQ_SIZE, 0);
CHECK_EXIT(send != -1, "");
do {
ssize_t recv = msgrcv(MSG_ID, &ret, RET_SIZE, CLIENT_MSG_TYPE, 0);
CHECK_EXIT(recv != -1, "");
if(ret.msgType & RET_SUCCESS) {
printf("oJBk: ");
} else {
printf("fail: ");
}
printf("%s", ret.mcontent);
} while (ret.msgType & RET_MORE);
printf("\n");
}

void waitChild(int sig, siginfo_t *info, void *buf) {
logger(LOG_DEBUG, "SERVER: received sig:%d(%s)", sig, strsignal(sig));
if(sig == SIGCHLD)CHECK_LOG(waitpid(info->si_pid, NULL, 0) != -1, "");
}
char *execute(char *cmd, bool *success) {
*success = false;
FILE *f = popen(cmd, "r");
if(!f) {
logger(LOG_DEBUG, "SERVER: execute, f is NULL");
free(cmd);
return NULL;
}
char content[BUFFER_SIZE] = {0};
char *ret = strdup("");
while (fgets(content, BUFFER_SIZE-1, f) != NULL) {
char *concat = alloc_sprintf("%s%s", ret, content);
free(ret);
ret = concat;
}
free(cmd);
int status = pclose(f);
*success = WIFEXITED(status) && (WEXITSTATUS(status) == 0);
logger(LOG_DEBUG, "SERVER: execute success=%d, ret = %s", *success, ret);
return ret;
}

char *touch(char *filename, bool *success) {
char *cmd = alloc_sprintf("touch %s 2>&1", filename);
return execute(cmd, success);
}

char *rm(char *filename, bool *success) {
char *cmd = alloc_sprintf("rm -rf %s 2>&1", filename);
return execute(cmd, success);
}

char *ls(char *filename, bool *success) {
char *cmd = alloc_sprintf("ls %s 2>&1", filename);
return execute(cmd, success);

}

char *rdlk(char *filename, bool *success) {
char *cmd = alloc_sprintf("readlink %s 2>&1", filename);
return execute(cmd, success);

}

char *cat(char *filename, bool *success) {
char *cmd = alloc_sprintf("cat %s 2>&1", filename);
return execute(cmd, success);
}

void server_exit(int sig) {
exit(0);
}

char *splitby(char *mcontent, const char *sep) {
while (*sep) {
char *ptr = strchr(mcontent, *sep);
if(ptr) *ptr = '\0';
sep++;
}
return mcontent;
}

void server() {
struct sigaction action;
struct ClientRequestType req;
action.sa_flags = SA_RESTART;
CHECK_EXIT(sigemptyset(&action.sa_mask) != -1, "");
action.sa_sigaction = waitChild;
CHECK_EXIT(sigaction(SIGCHLD, &action, NULL) != -1, "");
CHECK_EXIT(atexit(atexitRMID) != -1, "");
CHECK_EXIT(signal(SIGHUP, server_exit) != SIG_ERR, "");
CHECK_EXIT(signal(SIGINT, server_exit) != SIG_ERR, "");
ssize_t msg_len;
for(;;) {
memset(&req, 0, sizeof(struct ClientRequestType));
msg_len = msgrcv(MSG_ID, &req, REQ_SIZE, SERVER_MSG_TYPE, 0);
if(msg_len == -1) {
if(errno == EINTR) continue;
CHECK_EXIT(true, "");
}
logger(LOG_DEBUG, "read msg");
struct ServerReturnType ret;
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGCHLD);
switch (fork()) {
case -1:
CHECK_EXIT(false, "");
break;
case 0:
sigprocmask(SIG_SETMASK, &sigset, NULL); // 防止pclose删除消息队列
memset(&ret, 0, sizeof(struct ServerReturnType));
ret.mtype = req.pid;
char *mcontent;
bool success;
splitby(req.mcontent, "; |>&");
switch (req.msgType) {
case REQ_TOUCH:
mcontent = touch(req.mcontent, &success);
break;
case REQ_RM:
mcontent = rm(req.mcontent, &success);
break;
case REQ_CAT:
mcontent = cat(req.mcontent, &success);
break;
case REQ_LS:
mcontent = ls(req.mcontent, &success);
break;
case REQ_RDLK:
mcontent = rdlk(req.mcontent, &success);
break;
default:
mcontent = alloc_sprintf("unsupported msgType:%d", req.msgType);
success = false;
break;
}
if(success) {
ret.msgType = RET_SUCCESS;
logger(LOG_DEBUG, "SERVER: success");
} else {
ret.msgType = 0;
logger(LOG_DEBUG, "SERVER: fail");
}
size_t len = strlen(mcontent);
char *tcontent = mcontent;
ret.msgType |= RET_MORE;
for(int i = 0; i < len/BUFFER_SIZE; i++) {
memcpy(ret.mcontent, mcontent, BUFFER_SIZE);
CHECK_LOG(msgsnd(MSG_ID, &ret, RET_SIZE, 0) != -1, "");
mcontent += BUFFER_SIZE;
}
memcpy(ret.mcontent, mcontent, len%BUFFER_SIZE);
ret.msgType &= ~RET_MORE;
CHECK_LOG(msgsnd(MSG_ID, &ret, RET_SIZE, 0) != -1, "");
safe_free(tcontent);
fflush(NULL);
_exit(0); // 不要执行atxite注册的函数
break;
default:
break;
}
}
}


int main(int argc, char **argv) { //处理参数
logfile = stderr;
CHECK(argc > 1, "Usage: %s [server|client] argv...", argv[0]);
MSQ_KEY_FILE = realpath(strdup(argv[0]), NULL);
CHECK(atexit(atexitFreeKeyfile) != -1, "");

SELF_PID = getpid();
CHECK(SELF_PID != -1, "");

static key_t __MSG_KEY = -1;
__MSG_KEY = ftok(MSQ_KEY_FILE, 't');
CHECK(__MSG_KEY != -1, "");
MSG_ID = msgget(__MSG_KEY, IPC_CREAT | 0666);
CHECK(MSG_ID != -1, "");

if(!strcmp("client", argv[1])) {
CHECK(argc > 3, "Usage: %s client filename cmd", argv[0]);
int cmd;
if(!strcmp(argv[3], "touch")) {
cmd = REQ_TOUCH;
} else if(!strcmp(argv[3], "ls")) {
cmd = REQ_LS;
} else if(!strcmp(argv[3], "cat")) {
cmd = REQ_CAT;
} else if(!strcmp(argv[3], "readlink")) {
cmd = REQ_RDLK;
} else if(!strcmp(argv[3], "rm")) {
cmd = REQ_RM;
} else {
cmd = -1;
}
client(argv[2], cmd);
} else if(!strcmp("server", argv[1])) {
CHECK(true, "Usage: %s server [--daemon=] [--logfile=]", argv[0]);
bool deamonlize = false;
char *logfilename = NULL;
for(int i = 2; i < argc; i++) {
if(!strncmp("--daemon=", argv[i], 9)) {
if(!strcmp(argv[i] + 9, "true")) {
logger(LOG_DEBUG,"SERVER: daemon");
deamonlize = true;
} else {
logger(LOG_DEBUG,"SERVER: non-daemon");
}
} else if(!strncmp("--logfile=", argv[i], 10)) {
logfilename = argv[i] + 10;
logger(LOG_DEBUG,"SERVER: logfile: %s", logfilename);
} else {
logger(LOG_DEBUG, "Unknown argv: %s", argv[i]);
}
}
if(deamonlize) {
CHECK(daemon(0,0) != -1, "");
}
if(logfilename == NULL) {
logfile = stderr;
} else {
int logfileFd;
CHECK((logfileFd = open(logfilename, O_WRONLY | O_CREAT, 0600)) != -1, "");
logfile = fdopen(logfileFd, "w");
CHECK(logfile != NULL, "");
CHECK(dup2(logfileFd, STDERR_FILENO) != -1, "");
CHECK(dup2(logfileFd, STDOUT_FILENO) != -1, "");
CHECK(atexit(atexitCloseLogfile) != -1, "");
}
server();
} else {
CHECK(false, "Usage: %s [server|client] argv...", argv[0]);
}
}

46.3

在46.8节中的客户端-服务器应用程序中客户端为何在消息体(在clientId 字段中)中传递其消息队列的标识符,而不是在消息类型(mtype)中传递?

  • clientid有可能为0,mtype不能为0
阅读更多

cha45.System V IPC

45.1 45.2

  • 编写一个程序来验证ftok0所采用的算法是否如45.2节中描述的那样使用了文件的i-node号、次要设备号以及proj值。(通过几个例子打印出所有这些值以及ftok的返回值的十六进制形式即可)。
  • 实现ftok
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
//
// Created by root on 8/30/23.
//

#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>

void error(const char *file, int line,const char *str, ...) {
va_list fmt;
va_start(fmt, str);
fprintf(stderr, "error:%s %s:%d\n", strerror(errno), file, line);
vfprintf(stderr, str, fmt);
fprintf(stderr, "\n");
va_end(fmt);
}

#define ERROR_EXIT(ret, msg...) do { error(__FILE__, __LINE__, msg); ret } while(0)

#define ERROR(msg...) ERROR_EXIT(exit(1);, msg)
#define FAIL(ret, msg...) ERROR_EXIT(ret, msg)
#define PRINT_ERROR_STR(msg...) FAIL(;, msg)


key_t _ftok(const char *file, int proj) {
struct statx filestat;
int dirfd = open(".", O_RDONLY);
if(statx(dirfd, file, 0, 0, &filestat) == -1) FAIL(return -1;, "");
close(dirfd);
return ((proj&0xff) << 24) | ((filestat.stx_dev_minor & 0xff) << 16) | ((filestat.stx_ino & 0xffff));
}

int main(int argc, char **argv) {
char *file = argv[1];
struct statx filestat;
int dirfd = open(".", O_RDONLY);
if(statx(dirfd, file, 0, 0, &filestat) == -1) ERROR("");
for(int proj = 1; proj <= 255; proj++) {
printf("proj:%02X, dev:%02X, i-node:%04X, ", proj, filestat.stx_dev_minor & 0xff, filestat.stx_ino & 0xffff);
printf("ftok:%08X\n", ftok(file, proj));
printf("_ftok:%08X\n", _ftok(file, proj));
}
close(dirfd);
}

45.3

验证(通过实验)45.5节中有关用于生成System VIPC标识符的算法的声明。

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

#define _GNU_SOURCE /* See feature_test_macros(7) */

#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define IPCMNI 32768

int main(int argc, char *argv[]) {
key_t key = ftok(argv[0], 0x6a);
int id = msgget(key, IPC_CREAT | 0666), calid;
struct msqid_ds msqidDs;
msgctl(id, IPC_STAT, &msqidDs);
calid = msqidDs.msg_perm.__seq * IPCMNI;
FILE *filemsg = fopen("/proc/sysvipc/msg", "r");
char msgline[2048];
int index = 0;
fgets(msgline, 2048, filemsg);
key_t keyi;
do {
char *nextline = fgets(msgline, 2048, filemsg);
if (nextline == NULL) {
break;
}
sscanf(nextline, "%ld", &keyi);
index++;
} while (keyi != key);
calid += index;
if (calid == id) {
printf("true!\n");
} else {
printf("false?\n");
}
msgctl(id, IPC_RMID, &msqidDs);
}
阅读更多

cha44.管道和FIFO

管道和FIFO

  • 通过pipe调用获取两个fdfd[0]为管道的输入端,fd[1]为输出端,允许相关的进程之间通过管道相连
  • 管道的使用的单项的,如果某些进程即读取管道又写入管道,可能会与其他读取/写入管道的进程产生竞态条件
  • 对管道read时,如果管道另一端有进程打开且管道内没有内容,则会阻塞,直到另一端写入(此时读取到写入的数据),或另一端关闭(此时read返回EOF
    • 利用这一特性,可以作为进程间的同步机制
  • 一般子进程要关闭不需要的另一端
    • 当所有写入端都关闭时,从管道中读取,会返回EOF,帮助程序了解当前管道的使用状况
    • 当所有读取端都关闭时,向管道内写入数据,系统会向进程发送SIGPIPE,默认杀死进程。若修改SIGPIPE默认行为,则write失败,错误为EPIPE。若对SIGPIPE使用了SA_RESTARTwrite另一端已经关闭的管道,write也不会重启
  • stdio对管道使用块缓冲
  • FIFO通过mkfifomknod创建,类似于管道,区别在于
    • 通过文件名open后获取fdopen时指定read还是write
    • open时没有使用O_NONBLOCK时,打开另一端关闭的FIFO时,会阻塞
    • open时使用O_NONBLOCK | O_WRONLY时,向另一侧关闭的FIFO中写入时,导致ENXIO错误,使用O_NONBLOCK | O_RDONLY则立刻成功
    • 不使用O_NONBLOCKread,情况等于管道,启用时,如果FIFO中没有数据,产生EAGAIN错误
    • write时读取端关闭,则产生SIGPIPE,返回EPIPE。其他情况比较复杂。

验证

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
//
// Created by root on 8/29/23.
//
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>

void error(const char *file, int line,const char *str, ...) {
va_list fmt;
va_start(fmt, str);
fprintf(stderr, "error:%s %s:%d\n", strerror(errno), file, line);
vfprintf(stderr, str, fmt);
fprintf(stderr, "\n");
va_end(fmt);
}

#define ERROR(...) do { error(__FILE__, __LINE__, __VA_ARGS__); exit(1); } while(0)
#define ERROR_PRINT(...) error(__FILE__, __LINE__, __VA_ARGS__)
#define FAIL(...) do { error(__FILE__, __LINE__, __VA_ARGS__); return -1; } while(0)

char *alloc_sprintf(const char * __format, ...) {
va_list fmt;
va_start(fmt, __format);
int len = vsnprintf(NULL, 0, __format, fmt);
va_end(fmt);
char *str = malloc(len+1);
if(str == NULL) ERROR("");
va_start(fmt, __format);
vsnprintf(str, len+1, __format, fmt);
va_end(fmt);
return str;
}

#define safe_free(ptr) do { if(ptr) { free(ptr); ptr = NULL;} } while(0)

char *fifo;
void cleanup() {
safe_free(fifo);
}

#define test(flag) do { \
int fd = open(fifo, flag); \
if(fd == -1) { \
error(__FILE__, __LINE__, "flag = %s",#flag); \
} \
close(fd); \
} while(0)

void pipehandler(int sig) {
char *content = alloc_sprintf("pid:%d, received signal:%d(%s)\n", getpid(), sig, strsignal(sig));
write(STDOUT_FILENO, content, strlen(content));
safe_free(content);
}

int main(int argc, char **argv) {
char buf[1024];
atexit(cleanup);
fifo = alloc_sprintf("%s-fifo", argv[0]);
printf("fifo=%s\n", fifo);
if(mkfifo(fifo, S_IRUSR | S_IWUSR | S_IWGRP) == -1 && errno != EEXIST) {
ERROR("");
}
// 非阻塞,打开没有写入端的FIFO
test(O_RDONLY | O_NONBLOCK);
// 非阻塞,打开没有读取端的FIFO
test(O_WRONLY | O_NONBLOCK);

int pipefd[2] = {0};
if(pipe(pipefd) == -1) ERROR("");
if(sigaction(SIGPIPE, &(struct sigaction) {
.sa_flags=0,
.sa_handler=pipehandler }, NULL) == -1) ERROR("");
if(sigaction(SIGUSR1, &(struct sigaction) {
.sa_flags=SA_RESTART,
.sa_handler=pipehandler }, NULL) == -1) ERROR("");
if(sigaction(SIGUSR2, &(struct sigaction) {
.sa_flags=0,
.sa_handler=pipehandler }, NULL) == -1) ERROR("");

ssize_t readsize = 0;
pid_t pid;
if((pid = fork()) != 0) {
if((readsize = read(pipefd[0], buf, 1024)) < 0) {
ERROR_PRINT("");
} // SIGUSR1中断read,SIGUSR1有SA_RESTART,重启,成功读取
if(write(STDOUT_FILENO, buf, readsize) != readsize) {
ERROR("");
}
kill(pid, SIGUSR1);
readsize = 0;
if((readsize = read(pipefd[0], buf, 1024)) < 0) {
ERROR_PRINT("");
} // SIGUSR2中断read,SIGUSR2没有SA_RESTART,不重启,没有成功读取
if(write(STDOUT_FILENO, buf, readsize) != readsize) {
ERROR("");
}
} else {
close(pipefd[0]);
kill(getppid(), SIGUSR1);
sleep(1);
write(pipefd[1], &(char[]){'o', 'k', '!', '\n'}, 4);
pause();
sleep(1);
kill(getppid(), SIGUSR2);
sleep(1);
write(pipefd[1], &(char[]){'o', 'j', 'b', 'k', '!', '\n'}, 6);
close(pipefd[1]);
exit(0);
}
wait(NULL);
close(pipefd[0]);
close(pipefd[1]);
if(pipe(pipefd) == -1) ERROR("");
close(pipefd[1]);
if((readsize = read(pipefd[0], buf, 1024)) < 0) {
ERROR_PRINT("");
} // 向写入端已经关闭的一端读取
printf("向写入端已经关闭的一端读取, readsize = %lu\n", readsize);
close(pipefd[0]);

if(pipe(pipefd) == -1) ERROR("");
close(pipefd[0]);
if(write(pipefd[1], "12345", 5) != 5) {
ERROR_PRINT("");
} // 向读取端已经关闭的一端写入,SIGPIPE有SA_RESTART但是此时不重启
close(pipefd[1]);

return 0;
}

书上有一些不清楚的地方,用这个代码可以验证一下

44.1

阅读更多

cha42.共享库高级特性

42.1 42.2

  • 编写一个程序来验证当使用dlclose0关闭一个库时如果其中的符号还在被其他库使用的话将不会卸载这个库。
  • 在程序清单42-1中的程序(dynload.c)中添加一个dladdr0调用以获取与dlsym返回的地址有关的信息。打印出返回的 DI inf 结构中各个字段的值并验证这些值是否与预期的值一样。
practice42.1.a.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//
// Created by root on 8/25/23.
//
#include <stdio.h>

int foo(int a, int b) {
printf("foo\n");
return a+b;
}

int bar(int a, int b) {
printf("bar\n");
return a*b;
}

void __attribute__ ((constructor)) init() {
printf("constructing\n");
}

void __attribute__ ((destructor())) dest() {
printf("destructing\n");
}

practice42.1.b.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
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
//
// Created by root on 8/25/23.
//
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>

void error(const char *file, int line,const char *str, ...) {
va_list fmt;
va_start(fmt, str);
fprintf(stderr, "error:%s %s:%d\n", strerror(errno), file, line);
vfprintf(stderr, str, fmt);
fprintf(stderr, "\n");
va_end(fmt);
}

#define ERROR(...) do { error(__FILE__, __LINE__, __VA_ARGS__); exit(1); } while(0)
#define FAIL(...) do { error(__FILE__, __LINE__, __VA_ARGS__); return -1; } while(0)

int main(int argc, char **argv) {
if(argc < 1) {
ERROR("Usage: %s shared-object", argv[0]);
}
void *libhandler = dlopen(argv[1], RTLD_NOW);
if(libhandler == NULL) {
ERROR(dlerror());
}
void *libhandler1 = dlopen(argv[1], RTLD_NOW);
if(libhandler1 == NULL) {
ERROR(dlerror());
}
int (*f)(int, int) = dlsym(libhandler, "foo");
if(f == NULL) {
ERROR(dlerror());
}
int (*f1)(int, int) = dlsym(libhandler1, "bar");
if(f1 == NULL) {
ERROR(dlerror());
}
Dl_info info;
memset(&info, 0, sizeof(Dl_info));
if(dladdr(f, &info) == 0) {
ERROR(dlerror());
}
printf("addr=%p\n\tfname=%s\n\tfbase=%p\n\tsname=%s\n\tsaddr=%p\n", f, info.dli_fname, info.dli_fbase, info.dli_sname, info.dli_saddr);
memset(&info, 0, sizeof(Dl_info));
if(dladdr(f1, &info) == 0) {
ERROR(dlerror());
}
printf("addr=%p\n\tfname=%s\n\tfbase=%p\n\tsname=%s\n\tsaddr=%p\n", f1, info.dli_fname, info.dli_fbase, info.dli_sname, info.dli_saddr);
printf("%d, %d\n", f(1,2), f1(2,3));
printf("dlclose libhandler\n");
dlclose(libhandler);
printf("dlclose libhandler1\n");
dlclose(libhandler1);
}

cha40.登录记账

40-1 40-4

  • 实现getlogin0。在40.5节中曾提到过当进程运行在一些软件终端模拟器下时getlogin0可能无法正确工作,在那种情况下就在虚拟控制台中进行测试。

  • 实现一个简单的who(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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//
// Created by root on 8/23/23.
//
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <utmpx.h>
#include <string.h>
#include <time.h>

char *__getlogin() {
static char login[__UT_NAMESIZE];
char *tty = ttyname(0);
if(tty) tty++;
tty=strchr(tty, '/');
if(tty) tty++;
printf("tty=%s\n", tty);
size_t ttylen = strlen(tty);
struct utmpx *ut;
setutxent();
while((ut = getutxent()) != NULL) {
printf("type:\t%s\nline:\t%s\nuser:\t%s\nhost:\t%s\ntime:\t%s\n\n",
ut->ut_type == EMPTY ? "EMPTY" :
ut->ut_type == RUN_LVL ? "RUN_LVL" :
ut->ut_type == BOOT_TIME ? "BOOT_TIME" :
ut->ut_type == NEW_TIME ? "NEW_TIME" :
ut->ut_type == OLD_TIME ? "OLD_TIME" :
ut->ut_type == INIT_PROCESS ? "INIT_PROCESS" :
ut->ut_type == USER_PROCESS ? "USER_PROCESS" :
ut->ut_type == DEAD_PROCESS ? "DEAD_PROCESS" :
ut->ut_type == LOGIN_PROCESS ? "LOGIN_PROCESS" : "unknown",
ut->ut_line, ut->ut_user, ut->ut_host, ctime((time_t *)&ut->ut_tv.tv_sec)
);
if(!strncmp(tty, ut->ut_line, ttylen) &&
(ut->ut_type == INIT_PROCESS ||
ut->ut_type == USER_PROCESS ||
ut->ut_type == LOGIN_PROCESS)) {
strcpy(login, ut->ut_user);
}
}
endutxent();
return login;
}

int main() {
printf("getlogin=%s\n", getlogin());
printf("getlogin=%s\n", __getlogin());
}

40-2 40-3

  • 修改程序清单40-3中的程序(utmpx loginc)使它除了更新utmp和wtmp文件之外还更新lastlog文件。
  • 阅读login(3)、logout(3)以及logwtmp(3)的手册。实现这些函数
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
156
157
158
159
160
161
162
163
164
165
166
//
// Created by root on 8/24/23.
//
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <utmpx.h>
#include <stdarg.h>
#include <time.h>
#include <paths.h>
#include <wait.h>
#include <lastlog.h>
#include <pwd.h>
#include <fcntl.h>
#include <pwd.h>
#include <crypt.h>
#include <shadow.h>

void error(const char *file, int line,const char *str, ...) {
va_list fmt;
va_start(fmt, str);
fprintf(stderr, "error:%s %s:%d\n", strerror(errno), file, line);
vfprintf(stderr, str, fmt);
fprintf(stderr, "\n");
va_end(fmt);
}

#define ERROR(...) do { error(__FILE__, __LINE__, __VA_ARGS__); exit(1); } while(0)
#define FAIL(...) do { error(__FILE__, __LINE__, __VA_ARGS__); return -1; } while(0)

char *alloc_sprintf(const char * __format, ...) {
va_list fmt;
va_start(fmt, __format);
int len = vsnprintf(NULL, 0, __format, fmt);
va_end(fmt);
char *str = malloc(len+1);
if(str == NULL) ERROR("");
va_start(fmt, __format);
vsnprintf(str, 0, __format, fmt);
va_end(fmt);
return str;
}

int tmpxlog(struct utmpx *ut, const char *user, const char *ttyName) {
memset(ut, 0, sizeof(struct utmpx));
ut->ut_type = USER_PROCESS;
strncpy(ut->ut_user, user, sizeof(ut->ut_user));
if(time((time_t *)&ut->ut_tv.tv_sec) == -1) {
ERROR("");
}
ut->ut_pid = getpid();
strcpy(ut->ut_line, ttyName + 5);
strcpy(ut->ut_id, ttyName + 8);
strcpy(ut->ut_host, "meow-bash");
setutxent();
if(pututxline(ut) == NULL) ERROR("");
updwtmpx(_PATH_WTMP, ut);
return 0;
}

int lastlog(uid_t uid, const char *ttyName) {
struct lastlog lastlog = {
.ll_host="meow-bash"
};
strcpy(lastlog.ll_line, ttyName + 5);
if(time((time_t *)&lastlog.ll_time) == -1) {
ERROR("");
}
int lstlogfd = open(_PATH_LASTLOG, O_RDWR);
if(lseek(lstlogfd, uid * sizeof(struct lastlog), SEEK_SET) == -1) ERROR("");
if(write(lstlogfd, &lastlog, sizeof(struct lastlog)) != sizeof(struct lastlog)) ERROR("");
close(lstlogfd);
return 0;
}

int tmpxlogout(struct utmpx *ut) {
ut->ut_type = DEAD_PROCESS;
if(time((time_t *)&ut->ut_tv.tv_sec) == -1) {
ERROR("");
}
strncpy(ut->ut_user, "", sizeof(ut->ut_user));
setutxent();
if(pututxline(ut) == NULL) ERROR("");
updwtmpx(_PATH_WTMP, ut);
endutxent();
return 0;
}

int checkpwd(const char *username) {
char *shadow = NULL;
struct passwd *usrpwd;
if((usrpwd = getpwnam(username)) == NULL) FAIL("username:%s not found", username);
shadow = usrpwd->pw_passwd;
char *pass = getpass("Password: ");
if(!strcmp(shadow, "x")) {
struct spwd *shadowpwd;
if((shadowpwd = getspnam(username)) == NULL) FAIL("shadowpwd not found");
shadow = shadowpwd->sp_pwdp;
pass = crypt(pass, shadow);
}
if(strcmp(shadow, pass) == 0) FAIL("password not match!");
return 0;
}

char *user = NULL;

int init_check(int argc, char *argv[]) {
if(argc < 2) {
user = malloc(2048);
char hostname[2048];
gethostname(hostname, 2048);
printf("%s login: ", hostname);
scanf("%s", user);
} else {
user = strdup(argv[1]);
}
if(getuid() != 0) {
ERROR("uid must be 0\n");
}
}

int main(int argc, char *argv[]) {
struct utmpx ut;
char *homedir = NULL;
uid_t uid;
struct passwd *passwd = NULL;
char *ttyName = ttyname(0);
if(ttyName == NULL) {
ERROR("");
}

init_check(argc, argv);

if(checkpwd(user) == -1) ERROR("");

passwd = getpwnam(user);
uid = passwd->pw_uid;
homedir = alloc_sprintf("%s/.bashrc", passwd->pw_dir);

tmpxlog(&ut, user, ttyName);
lastlog(uid, ttyName);
printf("meow-meow-bash login\n");

fflush(NULL);
switch (fork()) {
case -1:
ERROR("");
break;
case 0:
setuid(uid);
if(execlp("/bin/bash", "/bin/bash", "--init-file", homedir, NULL) == -1) {
ERROR("");
}
break;
default:
wait(NULL);
break;
}
tmpxlogout(&ut);
printf("meow-meow-bash logout\n");
free(homedir);
free(user);
}

cha39.能力

读书笔记

进程能力分为:

类型 解释
许可集 进程可使用的能力,删除一个能力是不可逆的
有效集 进程当前能使用的能力
可继承集 exec之后,可以继承、进入许可集的能力集(规定被exec的文件可以继承哪些能力)

文件能力分为:

类型 解释
许可集 exec时添加到进程的许可集
有效集 1位,关闭,则exec后进程有效集为空;开启,exec后有效集为许可集
可继承集 文件可继承集与进程可继承集相交后,作为exec后可被继承、进入许可集的能力集合(规定被exec的文件可以继承哪些能力)

exec前后计算公式

阅读更多