#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
fork()函数用于创建一个和当前进程映像相同的子进程。fork执行一次;返回两次;可能有三种不同的值。如果执行成功;在父进程中返回子进程的ID;在子进程中返回0;如果执行失败返回-1;
vfork()函数功能与fork类似;二者区别如下;
fork子进程拷贝父进程的数据段;vfork子进程与父进程共享数据段;fork父、子进程的执行次序不确定;vfork子进程先运行;父进程后运行。#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int fork_pid = -1;
fork_pid = fork();
if (fork_pid > 0) {
printf(;I am a parent process, my pid is %d, my child is %d.
;, getpid(), fork_pid);
} else if (fork_pid == 0) {
printf(;I am a child process, my pid is %d, my parent is %d.
;, getpid(), getppid());
} else {
perror(;Create failed;);
exit(1);
}
exit(0);
}
运行结果
atreus;atreus-virtual-machine:~/code/220317$ make gcc -Wall main.c -o main atreus;atreus-virtual-machine:~/code/220317$ ./main I am a parent process, my pid is 8480, my child is 8481. I am a child process, my pid is 8481, my parent is 8480. atreus;atreus-virtual-machine:~/code/220317$
在早期的系统中;创建进程比较简单。当调用fork时;内核会把所有的内部数据结构复制一份;复制进程的页表项;然后把父进程的地址空间中的内容也复制到子进程的地址空间中。但是从内核角度来说;这种复制方式是非常耗时的。因此;在现代的系统中采取了更多的优化。现代的Linux系统采用了写时复制技术;Copy on Write;;而不是一创建子进程就将所有的数据都复制一份。
Copy on Write;COW;的主要思路是;如果子进程/父进程只是读取数据;而不是对数据进行修改;那么复制所有的数据是不必要的。因此;子进程/父进程只要保存一个指向该数据的指针就可以了。当子进程/父进程要去修改数据时;那么再复制该部分数据即可。这样也不会影响到子父进程的执行。因此;在执行fork时;子进程首先只复制一个页表项;当子进程/父进程有写操作时;才会对所有的数据块进行复制操作。
#include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
exec函数族用被执行的程序替换调用它的程序;以上六个函数均为库函数;最终都要执行系统调用execve来实现相应功能。
与fork创建一个新的进程;产生一个新的PID不同;exec会启动一个新程序;替换原有的进程;因此进程的PID不会改变。如果调用成功;exec不会有返回值;否则返回-1。
函数参数如下;
pathname;要执行的程序路径;可以是绝对路径或者是相对路径。在execl、execle和execv这3个函数中;使用带路径名的文件名作为参数。file;要执行的程序名称。如果该参数中包含/字符;则视为路径名直接执行。否则视为单独的文件名;系统将根据PATH环境变量指定的路径顺序搜索指定的文件。argv;被执行程序所需的命令行参数数组。envp;带有该参数的exec函数可以在调用时指定一个环境变量数组;其他不带该参数的exec函数则使用调用进程的环境变量;类似于main函数中的第三个参数。arg;要执行的程序的首个参数;即程序名本身;相当于argv[0]。...;命令行参数列表;调用相应程序时有多少命令行参数就需要有多少个输入参数项。注意;在使用此类函数时;在所有命令行参数的最后应该增加一个空的参数项NULL;表明命令行参数结束。main.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 10
int main() {
int fork_pid = -1;
char *envp[MAX_SIZE] = {;my_path;, NULL};
char *arg_v[MAX_SIZE] = {;show;, ;execv;, NULL};
char *arg_vp[MAX_SIZE] = {;show;, ;execvp;, NULL};
char *arg_vpe[MAX_SIZE] = {;show;, ;execvpe;, NULL};
fork_pid = fork();
if (fork_pid > 0) {
printf(;Parent porcess is %d, my child porcess is %d.
;, getpid(), fork_pid);
waitpid(fork_pid, NULL, 0);
} else if (fork_pid == 0) {
// execl(;./show;, ;show;, ;execl;, NULL);
// execlp(;./show;, ;show;, ;execlp;, NULL);
execle(;./show;, ;show;, ;execle;, NULL, envp);
// execv(;./show;, arg_v);
// execvp(;./show;, arg_vp);
// execvpe(;./show;, arg_vpe, envp);
perror(;Can;t execute the exec family of functions;);
exit(1);
} else {
perror(;Create failed:;);
exit(1);
}
exit(0);
}
show.c
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[], char *env[]) {
printf(;Show is execued by %s.
;, argv[1]);
printf(;Child porcess is %d, my parent porcess is %d.
;, getpid(), getppid());
/* 如果是execle或者execvpe调用的输出环境变量 */
if (*(argv[1] ; strlen(argv[1]) - 1) == ;e;) {
for (int i = 0; env[i] != NULL; i;;) {
printf(;env[%d] = %s
;, i, env[i]);
}
}
exit(0);
}
运行结果
atreus;atreus-virtual-machine:~/code/220317$ gcc -Wall main.c -o main main.c: In function ‘main’: main.c:16:11: warning: unused variable ‘arg_vpe’ [-Wunused-variable] 16 | char *arg_vpe[MAX_SIZE] = {;show;, ;execvpe;, NULL}; | ^~~~~~~ main.c:15:11: warning: unused variable ‘arg_vp’ [-Wunused-variable] 15 | char *arg_vp[MAX_SIZE] = {;show;, ;execvp;, NULL}; | ^~~~~~ main.c:14:11: warning: unused variable ‘arg_v’ [-Wunused-variable] 14 | char *arg_v[MAX_SIZE] = {;show;, ;execv;, NULL}; | ^~~~~ atreus;atreus-virtual-machine:~/code/220317$ gcc -Wall show.c -o show atreus;atreus-virtual-machine:~/code/220317$ ./main Parent porcess is 6753, my child porcess is 6754. Show is execued by execle. Child porcess is 6754, my parent porcess is 6753. env[0] = my_path atreus;atreus-virtual-machine:~/code/220317$
#include <stdlib.h>
int system(const char *command);
system()是一个封装好的库函数;而不是系统调用;它通过调用fork产生子进程;由子进程来执行execl(;/bin/sh;, ;sh;, ;-c;, command, (char *) NULL);;进而来执行参数command中的命令。
#include <stdlib.h>
int main() {
system(;ls -li;);
exit(0);
}
运行结果
atreus;atreus-virtual-machine:~/code/220317$ make gcc -Wall main.c -o main atreus;atreus-virtual-machine:~/code/220317$ ./main 总用量 52 393231 -rwxrwxr-x 1 atreus atreus 16736 3月 21 10:24 main 393268 -rw-rw-r-- 1 atreus atreus 70 3月 21 10:24 main.c 393270 -rw-rw-r-- 1 atreus atreus 38 3月 21 09:54 makefile 393257 -rwxrwxr-x 1 atreus atreus 16872 3月 21 10:11 show 393620 -rw-rw-r-- 1 atreus atreus 521 3月 21 10:10 show.c atreus;atreus-virtual-machine:~/code/220317$
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
wait()函数和waitpid()函数会使父进程等待子进程的退出;主要用于解决僵尸进程和孤儿进程。
僵尸进程和孤儿进程;
僵尸进程;一个父进程利用fork创建子进程;如果子进程退出;而父进程没有利用wait或者waitpid来获取子进程的状态信息;那么子进程的状态描述符依然保存在系统中。孤儿进程;一个父进程退出;而它的一个或几个子进程仍然还在运行;那么这些子进程就会变成孤儿进程;孤儿进程将被init进程;PID为1;所收养;并由init进程对它们完成状态收集的工作。UID PID PPID C STIME TTY TIME CMD root 1 0 0 08:29 ? 00:00:01 /sbin/init splash
waitpid()函数函数参数如下;
pid;参数pid是需要等待的一个或多个进程的pid;它必须是下面这四种情况之一;在调用成功时;waitpid返回状态发生改变的那个进程的pid。如果设置了WNOHANG参数;那么等待的进程状态没有改变时返回0。发生错误时返回-1。
code.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main() {
pid_t pid;
pid = fork();
if (pid == -1) {
// 创建进程失败
printf(;创建进程失败(%s)!
;, strerror(errno));
return -1;
} else if (pid == 0) {
// 子进程
sleep(2);
printf(;This is child process
;);
exit(6);
} else {
// 父进程
int status;
if (waitpid(-1, &status, 0) != -1) {
if (WIFEXITED(status)) {
printf(;子进程正常退出;退出代码%d
;, WEXITSTATUS(status));
}
}
printf(;This is parent process
;);
exit(0);
}
}
运行结果
atreus;AtreusdeMacBook-Pro % gcc code.c -o code atreus;AtreusdeMacBook-Pro % ./code This is child process 子进程正常退出;退出代码;6 This is parent process atreus;AtreusdeMacBook-Pro %
pipe()函数用于创建一个无名管道;
#include <unistd.h>
int pipe(int pipefd[2]);
参数pipefd是一个长度为2的整型数组;用于存放调用该函数所创建的管道的文件描述符。其中 pipefd[0]存放管道读取端的文件描述符;pipefd[1]存放管道写入端的文件描述符。调用成功时;返回值为0;调用失败时;返回值为-1。
由于管道里面是字节流;父子进程同时读写会导致内容混在一起;对于读管道的一方;解析起来就比较困难。常规的使用方法是父子进程一方只能写入;另一方只能读出;管道变成一个单向的通道;以方便使用。因此管道在创建成功后;要在分别关闭读端和写端后再使用。
管道的特点;
①管道只允许具有血缘关系的进程间通信;如父子进程间通信;
②管道只允许单向通信;
③读管道时;如果管道没有数据;读操作会被阻塞;写数据时;如果管道被写满;写操作也会被阻塞。
无名管道实现父子进程间通信
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_SIZE 100
int main() {
pid_t fork_pid;
int pipe_fd[2];
char buffer[MAX_SIZE];
if (pipe(pipe_fd) < 0) {//创建一个无名管道
perror(;Pipe error;);
exit(1);
}
fork_pid = fork();
if (fork_pid > 0) {
/* 父进程写管道 */
while (1) {
close(pipe_fd[0]);//父进程关闭管道读端
memset(buffer, 0, sizeof(buffer));
printf(;Write pipe:;);
scanf(%[^
]%*c;, buffer);
write(pipe_fd[1], buffer, strlen(buffer));//父进程写管道
if (strcmp(buffer, ;end;) == 0) break;//结束通信
usleep(100000);
}
wait(NULL);
} else if (fork_pid == 0) {
/* 子进程读管道 */
while (1) {
close(pipe_fd[1]);//子进程关闭管道写端
memset(buffer, 0, sizeof(buffer));
read(pipe_fd[0], buffer, sizeof(buffer));//子进程读管道
printf(;Read pipe:%s
;, buffer);
if (strcmp(buffer, ;end;) == 0) break;
}
} else {
/* 子进程创建失败 */
perror(;Fork error;);
exit(1);
}
exit(0);
}
运行结果
atreus;atreus-virtual-machine:~/code/220317$ make gcc -Wall main.c -o main atreus;atreus-virtual-machine:~/code/220317$ ./main Write pipe:Hello world! Read pipe:Hello world! Write pipe:Hello Read pipe:Hello Write pipe:end Read pipe:end atreus;atreus-virtual-machine:~/code/220317$
匿名管道应用的一个限制就是只能在具有亲缘关系的进程间通信;如果我们想在不相关的进程之间交换数据;可以使用FIFO文件来做这项工作;它经常被称为命名管道。
mkfifo()用来创建一个有名管道;
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
前一个参数是管道文件的文件名;后一个参数为文件权限。
注意事项;
①为了保证管道一定被创建;最好两个进程都包含创建管道的代码;谁先运行谁先创建;后运行的直接打开使用;
②不能以O_RDWR模式打开命名管道文件;其行为是未定义的;管道是单向的;不能同时读写。
有名管道实现非血缘进程间通信
写进程write.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_SIZE 100
#define FIFO_FILE ;./fifo;
int main() {
char buffer[MAX_SIZE];
int ret;
if (ret = mkfifo(FIFO_FILE, 0644) < 0) {//创建一个有名管道
if (ret == -1 && errno != EEXIST) {
perror(;Make fifo error;);
exit(1);
}
}
int fd = open(FIFO_FILE, O_WRONLY);
while (1) {
memset(buffer, 0, sizeof(buffer));
printf(;Write fifo:;);
scanf(%[^
]%*c;, buffer);
write(fd, buffer, strlen(buffer));//父进程写管道
if (strcmp(buffer, ;end;) == 0) break;//结束通信
}
exit(0);
}
读进程read.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_SIZE 100
#define FIFO_FILE ;./fifo;
int main() {
char buffer[MAX_SIZE];
int ret;
if (ret = mkfifo(FIFO_FILE, 0644) < 0) {//创建一个有名管道
if (ret == -1 && errno != EEXIST) {
perror(;Make fifo error;);
exit(1);
}
}
int fd = open(FIFO_FILE, O_RDONLY);
while (1) {
memset(buffer, 0, sizeof(buffer));
read(fd, buffer, sizeof(buffer));//子进程读管道
printf(;Read fifo:%s
;, buffer);
if (strcmp(buffer, ;end;) == 0) break;//结束通信
}
exit(0);
}
执行结果
消息队列传送的是有格式的数据;可以实现多进程网状交叉通信以及大规模数据的通信。消息队列建立前必须为其指定一个键值key;通过ftok();file to key;函数生成这个键值;
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
ftok.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
int main() {
key_t key = ftok(;./;, 8);
printf(;key is 0x%x.
;, key);
key_t key_ = ftok(;./;, 16);
printf(;key_ is 0x%x.
;, key_);
key_t key__ = ftok(;./ftok.c;, 8);
printf(;key__ is 0x%x.
;, key__);
return 0;
}
执行结果
atreus;atreus-virtual-machine:~/code/220317$ ls -li ./ 总用量 36 393231 -rwxrwxr-x 1 atreus atreus 16736 3月 22 12:45 ftok 393257 -rw-rw-r-- 1 atreus atreus 314 3月 22 12:46 ftok.c 393270 -rw-rw-r-- 1 atreus atreus 38 3月 21 09:54 makefile 393619 -rw-rw-r-- 1 atreus atreus 942 3月 22 12:31 receive.c 393276 -rw-rw-r-- 1 atreus atreus 1007 3月 22 12:31 send.c atreus;atreus-virtual-machine:~/code/220317$ ls -li ../ 总用量 4 393265 drwxrwxrwx 3 atreus atreus 4096 3月 22 12:45 220317 atreus;atreus-virtual-machine:~/code/220317$ gcc ftok.c -o ftok atreus;atreus-virtual-machine:~/code/220317$ ./ftok key is 0x8050031. key_ is 0x10050031. key__ is 0x8050029. atreus;atreus-virtual-machine:~/code/220317$
其中pathname用于指定一个已经存在的文件;proj_id为子序号;实际上只取其低8位。通过上述代码和执行结果可以大致看出这个key值的生成过程;高位是proj_id转化为十六进制;低位是pathname文件的索引结点转化为16进制;393265D = 60031H;后修改最高位;因此通过同一个消息队列通信的进程必须指定相同的文件和子序号。
在获取到key值后通过msgget()函数获取消息队列标识符;
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
其中msgflg是一个权限标志;标识消息队列的访问权限;权限标志可以与IPC_CREAT进行或运算;表示指定消息队列不存在时新创建一个消息队列。函数执行成功时返回一个以key命名的消息队列的标识符;非零整数;;失败时返回-1。
msgsnd()函数和msgrcv()函数分别用于发送和接收消息;
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
参数如下;
msqid;由msgget返回的消息队列标识符。msgp;一个指向准备发送或接收的消息的指针;消息必须按照上图中msgbuf的形式定义。msgsz;发送或接收的消息中的数据部分的长度;即不包括长整型消息类型成员变量。msgtyp; 实现接收优先级;如果等于零;就获取队列中的第一个消息;如果它大于零;将获取具有相同消息类型的第一个信息;如果它小于零;就获取类型等于或小于msgtype的绝对值的第一个消息。msgflg;用于控制当前消息队列满或没有符合接收条件的消息时的操作;为0则表示阻塞发送或接收操作。在调用成功时;msgsnd返回0;msgrcv返回接收的字节数。发生错误时返回-1。
msgctl()可以用来删除消息队列或进行其他操作;
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
其中cmd参数是将要采取的动作;buf是一个指向消息队列模式和访问权限的结构的指针;如果是想要删除消息队列;参数分别设置为IPC_RMID和0;
消息队列实现通信
send.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_SIZE 1024
#define MSG_FILE ;./;
typedef struct msgbuf {
long mtype;
char mtext[MAX_SIZE];
} msgbuf;
int main() {
int msgid;
msgbuf *snd_msg;
key_t key = ftok(MSG_FILE, 8);
printf(;Send key is 0x%x.
;, key);
if ((msgid = msgget(key, 0644 | IPC_CREAT)) < 0) {
perror(;Msgget error;);
exit(1);
}
/* 发送消息 */
snd_msg = (msgbuf *) malloc(sizeof(msgbuf));
while (1) {
memset(snd_msg, 0, sizeof(msgbuf));
printf(;Send message>>>;);
scanf(%[^
]%*c;, snd_msg->mtext);
snd_msg->mtype = 211;
msgsnd(msgid, snd_msg, sizeof(snd_msg->mtext), 0);
if (!strcmp(snd_msg->mtext, ;end;)) break;
usleep(1);
}
free(snd_msg);
/* 删除消息队列 */
if(msgctl(msgid, IPC_RMID, 0) == -1) {
perror(;Msgctl error;);
exit(1);
}
return 0;
}
receive.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_SIZE 1024
#define MSG_FILE ;./;
typedef struct msgbuf {
long mtype;
char mtext[MAX_SIZE];
} msgbuf;
int main() {
int msgid;
msgbuf *rcv_msg;
key_t key = ftok(MSG_FILE, 8);
printf(;Receive key is 0x%x.
;, key);
if ((msgid = msgget(key, 0644 | IPC_CREAT)) < 0) {
perror(;Msgget error;);
exit(1);
}
/* 接收消息 */
rcv_msg = (msgbuf *) malloc(sizeof(msgbuf));
while (1) {
memset(rcv_msg, 0, sizeof(msgbuf));
msgrcv(msgid, rcv_msg, sizeof(rcv_msg->mtext), 211, 0);//接收消息类型为1的消息
printf(;Receive message:%s
;, rcv_msg->mtext);
if (!strcmp(rcv_msg->mtext, ;end;)) break;
}
free(rcv_msg);
/* 删除消息队列 */
if(msgctl(msgid, IPC_RMID, 0) == -1) {
perror(;Msgctl error;);
exit(1);
}
return 0;
}
执行结果
管道和消息队列的使用需要频繁地访问内核;共享内存可以减少进入内核的次数;从而提高通信效率。与消息队列类似;共享内存在创建之前也需要通过ftok()生成一个键值;然后通过shmget()函数获取共享内存标识符。
在得到共享内存标识符后;可以通过shmat()函数把共享内存区映射到调用进程的地址空间中;通过shmdt()函数解除映射;
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
其中shmid是共享内存标识符;shmflag一般设置为0;表示读写模式;在映射函数中;shmaddr指定共享内存出现在进程内存地址的什么位置;指定为NULL的话内核会自己决定;在解除映射函数中;shmaddr指向连接的共享内存地址。
同样的;在使用结束后要通过shmctl()函数将共享内存删除;效果等同于ipcrm -m shmid命令
共享内存实现进程间通信
send.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_SIZE 4096
#define SHM_FILE ;./;
int main() {
int shmid;
char buffer[MAX_SIZE];
/* 创建共享内存 */
key_t key = ftok(SHM_FILE, 8);
printf(;Send key is 0x%x.
;, key);
if ((shmid = shmget(key, MAX_SIZE, 0644 | IPC_CREAT)) < 0) {
perror(;Shmget error;);
exit(1);
}
/* 映射共享内存 */
void *shm = shmat(shmid, NULL, 0);
if (shm == (void *)-1) {
perror(;Shmat error;);
exit(1);
}
memset(buffer, 0, sizeof(buffer));
/* 发送消息 */
printf(;Send message>>>;);
scanf(%[^
]%*c;, buffer);
memcpy(shm, buffer, strlen(buffer));
/* 解除共享内存映射 */
shmdt(buffer);
return 0;
}
receive.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_SIZE 4096
#define SHM_FILE ;./;
int main() {
int shmid;
char buffer[MAX_SIZE];
key_t key = ftok(SHM_FILE, 8);
printf(;Send key is 0x%x.
;, key);
if ((shmid = shmget(key, MAX_SIZE, 0644 | IPC_CREAT)) < 0) {
perror(;Shmget error;);
exit(1);
}
/* 映射共享内存 */
void *shm = shmat(shmid, NULL, 0);
if (shm == (void *)-1) {
perror(;Shmat error;);
exit(1);
}
memset(buffer, 0, sizeof(buffer));
/* 接收消息 */
memcpy(buffer, shm, sizeof(buffer));
printf(;Receive message:%s.
;, buffer);
/* 解除共享内存映射 */
shmdt(buffer);
/* 删除共享内存 */
if(shmctl(shmid, IPC_RMID, 0) == -1) {
perror(;Shmctl error;);
exit(1);
}
return 0;
}
执行结果
atreus;atreus-virtual-machine:~/code/220317$ ipcs -m ------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 连接数 状态 atreus;atreus-virtual-machine:~/code/220317$ gcc send.c -o s atreus;atreus-virtual-machine:~/code/220317$ gcc receive.c -o r atreus;atreus-virtual-machine:~/code/220317$ ./s Send key is 0x8050031. Send message>>>Hello world! atreus;atreus-virtual-machine:~/code/220317$ ipcs -m ------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 连接数 状态 0x08050031 23 atreus 644 4096 0 atreus;atreus-virtual-machine:~/code/220317$ ./r Send key is 0x8050031. Receive message:Hello world!. atreus;atreus-virtual-machine:~/code/220317$ ipcs -m ------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 连接数 状态 atreus;atreus-virtual-machine:~/code/220317$
信号量在通过ftok()函数获取键值;semget()函数获取信号量之后;需要先通过semctl()将信号量初始化;
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
这是一个系统调用;
semid是信号量集的标识符。semnum是操作信号在信号集中的编号;第一个信号的编号是0。cmd是要执行的操作;一般取SETVAL设置信号量集中的一个单独的信号量的值;或取GETVAL返回信号量集中的一个单个的信号量的值;在上述两种操作中最后一个参数置0。初始化后由semop()函数执行P操作和V操作;
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
struct sembuf
{
unsigned short int sem_num; /* semaphore number */
short int sem_op; /* semaphore operation */
short int sem_flg; /* operation flag */
};
nsops指出要操作的信号个数;sops指向一个sembuf结构体数组;这个结构体的三个成员变量如下;
sem_num;操作信号在信号集中的编号;第一个信号的编号是0。sem_op;如果其值为正数;该值会加到现有的信号内含值中;通常用于释放所控资源的使用权;如果其值为负数;而其绝对值又大于信号的现值;操作将会阻塞;通常用于获取资源的使用权;如果其值为0且没有设置IPC_NOWAIT;则调用该操作的进程或者线程将暂时睡眠;直到信号量的值为0。sem_flg;信号操作标志;可能的选择有两种;通过信号量实现共享内存读写同步
send.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FILE ;./;
#define MAX_SIZE 100
int main() {
int sem_id, shm_id;
struct sembuf sem_buf;
char buffer[MAX_SIZE];
/* 创建键值 */
key_t sem_key = ftok(FILE, 1);
printf(;The sem_key is 0x%x.
;, sem_key);
key_t shm_key = ftok(FILE, 2);
printf(;The shm_key is 0x%x.
;, shm_key);
/* 获取描述符 */
if ((sem_id = semget(sem_key, 2, 0644 | IPC_CREAT)) < 0) {
perror(;Semget error;);
exit(1);
}
printf(;The sem_id is %d.
;, sem_id);
if ((shm_id = shmget(shm_key, 2, 0644 | IPC_CREAT)) < 0) {
perror(;Shmget error;);
exit(1);
}
printf(;The shm_id is %d.
;, shm_id);
/* 初始化信号量 */
semctl(sem_id, 0, SETVAL, 1);//0-1
semctl(sem_id, 1, SETVAL, 0);//1-0
printf(;Sem-0 = %d.
;, semctl(sem_id, 0, GETVAL, 0));
printf(;Sem-1 = %d.
;, semctl(sem_id, 1, GETVAL, 0));
sem_buf.sem_flg = SEM_UNDO;
/* 映射共享内存 */
void *shm = shmat(shm_id, NULL, 0);
if (shm == (void *)-1) {
perror(;Shmat error;);
exit(1);
}
/* 发送消息 */
while (1) {
/* P-0 */
sem_buf.sem_num = 0;
sem_buf.sem_op = -1;
semop(sem_id, &sem_buf, 1);
/* 发送 */
memset(buffer, 0, sizeof(buffer));
printf(;Send message>>>;);
scanf(%[^
]%*c;, buffer);
memcpy(shm, buffer, strlen(buffer));
/* V-1 */
sem_buf.sem_num = 1;
sem_buf.sem_op = 1;
semop(sem_id, &sem_buf, 1);
if (strcmp(buffer, ;end;) == 0) break;
}
/* 解除共享内存映射 */
shmdt(buffer);
return 0;
}
receive.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FILE ;./;
#define MAX_SIZE 100
int main() {
int sem_id, shm_id;
struct sembuf sem_buf;
char buffer[MAX_SIZE];
/* 创建键值 */
key_t sem_key = ftok(FILE, 1);
printf(;The sem_key is 0x%x.
;, sem_key);
key_t shm_key = ftok(FILE, 2);
printf(;The shm_key is 0x%x.
;, shm_key);
/* 获取描述符 */
if ((sem_id = semget(sem_key, 2, 0644 | IPC_CREAT)) < 0) {
perror(;Semget error;);
exit(1);
}
printf(;The sem_id is %d.
;, sem_id);
if ((shm_id = shmget(shm_key, 2, 0644 | IPC_CREAT)) < 0) {
perror(;Shmget error;);
exit(1);
}
printf(;The shm_id is %d.
;, shm_id);
sem_buf.sem_flg = SEM_UNDO;
/* 映射共享内存 */
void *shm = shmat(shm_id, NULL, 0);
if (shm == (void *)-1) {
perror(;Shmat error;);
exit(1);
}
/* 接收消息 */
while (1) {
/* P-1 */
sem_buf.sem_num = 1;
sem_buf.sem_op = -1;
semop(sem_id, &sem_buf, 1);
/* 接收 */
memset(buffer, 0, sizeof(buffer));
memcpy(buffer, shm, sizeof(buffer));
memset(shm, 0, sizeof(buffer));
printf(;Receive message:%s.
;, buffer);
/* V-0 */
sem_buf.sem_num = 0;
sem_buf.sem_op = 1;
semop(sem_id, &sem_buf, 1);
if (strcmp(buffer, ;end;) == 0) break;
}
/* 解除共享内存映射 */
shmdt(buffer);
/* 删除 */
if(semctl(sem_id, IPC_RMID, 0) == -1) {
perror(;Semctl error;);
exit(1);
}
if(shmctl(shm_id, IPC_RMID, 0) == -1) {
perror(;Shmctl error;);
exit(1);
}
return 0;
}
执行结果
信号有默认处理、忽略、执行用户需要执行的动作三种处理方式;常用信号如下;
kill()函数用于向任意进程或进程组发信号;
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数如下;
pid;用来指定接收信号的进程;有如下四种取值情况;signal()函数用于改变信号的处理方式;
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数如下;
signum指明要处理的信号编号。handler指定一个函数指针;它有如下三种情况;signal函数的使用
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void handle(int sig) {
if (sig == SIGINT) {
printf(;
SIGINT.
;);
}
if (sig == SIGALRM) {
printf(;
SIGALRM.
;);
}
}
int main() {
signal(SIGINT, handle);
signal(SIGALRM, handle);
alarm(10);//十秒后向本进程发送SIGALRM信号
pause();//等待一个已经被用户处理的信号
return 0;
}
执行结果
atreus;atreus-virtual-machine:~/code/220317$ make gcc -Wall main.c -o m atreus;atreus-virtual-machine:~/code/220317$ ./m ^C SIGINT. atreus;atreus-virtual-machine:~/code/220317$ ./m SIGALRM. atreus;atreus-virtual-machine:~/code/220317$