概述
每个进程有独立的进程空间:
好处————安全
缺点:开销大(独立的地址空间);进程的通信更加困难(对其他进程的操作开销也大)
广义上的进程间通信:
A进程写给文件/数据库,B进程从文件/数据库里读取
狭义上的真正的“进程间通信”
- 管道
- 信号
- 消息队列
- 共享内存
- 信号量
- 套接字
进程间通信的原理
尽管进程空间是各自独立的,相互之间没有任何可以共享的空间,但至少还有一个共享的,那就是OS,因为甭管运行有多少个进程,但是它们共用OS只有一个。
既然大家共用的是同一个OS,那么显然,所有的进程可以通过大家都共享第三方OS来实现数据的转发。
因此进程间通信的原理就是,OS作为所有进程共享的第三万,会提供相应的机制,以实现进程间数据的转发,达到数据共享的目的
信号
信号是一种向进程发送通知,告诉其某件事情发生了的一种简单通信机制
古老,应用广泛; 仅做通知,不做数据传输; 本质上是整数值(SIG开头);
信号列表
信号的产生
另一个进程发生信号;内核发送信号;底层硬件发送信号
信号发送
ps命令:查看进程的信息
终端
kill命令:kill -s 《signal》 《pid》
程序中给一个进程发信号
给当前进程发信号
raise
alarm
abort
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main()
{// kill(-1, SIGINT);//给所有进程发出信号(SIGINT是终止信号)// raise(SIGINT); //给当前进程发出终止信号alarm(5); //定时器到期,操作系统将发送 SIGALRM 信号给进程。//默认情况下,如果进程没有对 SIGALRM 信号进行处理,它将终止进程的执行//while(1);pause(); //挂起当前进程(相比于while(1)这种cpu消耗性更节约资源)abort(); //错误地退出return 0;
}
信号的处理方式
1.默认处理; 2.忽略; 3.执行用户需要执行的操作(捕获);
信号处理API
signal
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main()
{// kill(-1, SIGINT);//给所有进程发出信号(SIGINT是终止信号)// raise(SIGINT); //给当前进程发出终止信号alarm(5); //定时器到期,操作系统将发送 SIGALRM 信号给进程。//默认情况下,如果进程没有对 SIGALRM 信号进行处理,它将终止进程的执行//while(1);pause(); //挂起当前进程(相比于while(1)这种cpu消耗性更节约资源);直到有一个信号来return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>void handler(int sig) // 信号处理函数
{if (sig == SIGALRM) // 可以判断是哪个信号调用的处理函数{printf("handler with alarm\n");}else if (sig == SIGINT){printf("handler with ctrl+c\n");}
}
int main()
{// signal(SIGALRM,SIG_IGN);/sigalrm信号被忽略 则pause不会接收到信号,一直挂起// signal(SIGALRM,SIG_DFL);//sigalrm信号变为默认 则五秒后,printf输出;signal(SIGALRM, handler); // sigalrm信号转向 ”处理函数“————handler;signal(SIGINT, handler);alarm(5);pause(); // 当处理了一个信号处理函数,会唤醒pauseprintf("main is over\n");return 0;
}
sigaction
异步IO的实现
fcntl(0, __F_SETOWN, getpid()); // 将sigio信号设置成由当前的进程接收
signal(SIGIO, handler); // 设置信号处理函数
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>void handler(int sig) // 定义的信号处理函数
{char buffer[1024];memset(buffer, 0, sizeof(buffer));int ret = read(0, buffer, sizeof(buffer) - 1);buffer[ret] = '\0';printf("%s\n", buffer);
}int main(int argc, char **argv)
{int fd;fd = open("/dev/input/mouse0", O_RDWR); // 打开一个文件描述符if (fd == -1){perror("fd open error\n");exit(-1);}int flags = fcntl(0, F_GETFL);flags = flags | O_ASYNC; // 获取fd的flags,并加上0_ASYNC(异步读取)fcntl(0, F_SETFL, flags);fcntl(0, __F_SETOWN, getpid()); // 将sigio信号设置成由当前的进程接收signal(SIGIO, handler); // 设置信号线处理函数while (1) // 主函数的操作不受影响{int cor = 0;read(fd, &cor, sizeof(int));printf("handler is going: cor =%d\n", cor);}return 0;
}
优化进程等待
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>void handler(int sig)
{wait(NULL);printf(("handler & wait\n"));
}
int main()
{signal(SIGCHLD, handler);pid_t pid = fork();if (pid > 0){while (1){printf("father is going\n");sleep(1);}}if (pid == 0){printf("child is going\n");}return 0;
}
信号屏蔽字
作用:屏蔽信号
sigset_t数据类型
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>int main(int argc, char **argv)
{sigset_t set; // 定义一个信号字sigemptyset(&set); // 清空:全置为0sigaddset(&set, SIGINT); // 将sigint信号加入该信号集——也就是将对应位置为1sigprocmask(SIG_SETMASK, &set, NULL); // 设置信号罩pid_t pid = fork();if (pid > 0){while (1){printf("father \n");sleep(1);}}if (pid == 0){while (1){printf("child \n");sleep(1);}}return 0;
}
未决(处理)信号集
也是六十四位的int,记录了未处理的信号
pause() 函数会一直等待直到收到一个信号。当进程接收到一个信号时,如果该信号没有被忽略并且没有注册对应的信号处理函数,pause() 函数会被信号中断并返回 -1,同时将 errno 设置为 EINTR。
管道
无格式,读取后数据会删除
无名管道
内核会开辟一个“管道”,通信的进程通过共享这个管道从而实现通信
int pipe(int pipefd【2】);
特点:
1.只允许具有血管关系的进程间通信,如父子进程间的通信
2.管道只允许单向通信
3.读管道时,没有数据就会堵塞;写数据,写满缓冲区会休眠
4.数据被读出后,数据就会被管道删除
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>int main(int agrc, char **argv)
{int fd[2];pipe(fd);pid_t pid = fork();if (pid > 0){close(fd[0]);char buffer[1024];while (1){memset(buffer, 0, sizeof(buffer));scanf("%s", buffer);write(fd[1], buffer, sizeof(buffer));}}else if (pid == 0){int flags =fcntl(fd[0],F_GETFL);flags=flags|O_NONBLOCK;fcntl(fd[0],F_SETFL,flags);close(fd[1]);char buffer[1024];while (1){memset(buffer, 0, sizeof(buffer));read(fd[0], buffer, sizeof(buffer));printf("buffer is %s\n", buffer);sleep(1);}}return 0;
}
注意事项
SIGPIPE信号:
1.写管道时,如果管道的读端被close了话,向管道"写"数据的进程会被内核发送一个SIGPIPE信号,发这个信号的目的就是想通知你,管道所有的"读"都被关闭了。
2.由于这个信号的默认动作是终止,所以收到这个信号的进程会被终止,如果你不想被终止的
话,你可以忽略、捕获、或者屏蔽这个信号。
3.只有当管道所有的读端都被关闭时,才会产生这个信号,只有还有一个读端开着,就不会产生
signal (SIGPIPE, SIG_IGN)来忽略sigpipe这个信号
无名管道结合异步IO
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>int fd[2];
void handle(int sig)
{if (sig == SIGIO){char buffer[1024];memset(buffer, 0, sizeof(buffer));read(fd[0], buffer, sizeof(buffer) - 1);printf("%s\n", buffer);}
}
int main(int agrc, char **argv)
{pipe(fd);pid_t pid = fork();if (pid > 0) // 父进程:写{close(fd[0]);char buffer[1024];while (1){memset(buffer, 0, sizeof(buffer));scanf("%s", buffer);write(fd[1], buffer, strlen(buffer));}}else if (pid == 0) // 子进程:读{// close(fd[0]);close(fd[1]);int flags = fcntl(fd[0], F_GETFL);flags = flags | O_ASYNC;fcntl(fd[0], F_SETFL, flags);fcntl(fd[0], __F_SETOWN, getpid());signal(SIGIO, handle);pause();}return 0;
}
有名管道
管道应用的一个重大限制是它没有名字,只适合具有亲缘性质的进程之间通信。命名管道克服了这种限制,FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。
例子:
FILE1:#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>#define FILE_PATH "./pipe"void rm_pipe(int sig)
{if(sig==SIGINT){remove(FILE_PATH);}
}int main()
{if(mkfifo(FILE_PATH,0777)<0){perror("mkfifo error");exit(-1);}int fd=open(FILE_PATH,O_WRONLY);if(fd==-1){perror("fd open error");exit(-1);}signal(SIGINT,rm_pipe);while(1){char buffer[1024];memset(buffer,0,sizeof(buffer));scanf("%s",buffer);write(fd,buffer,strlen(buffer));}return 0;
}FILE2:#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>#define FILE_PATH "./pipe"int main()
{int fd=open(FILE_PATH,O_RDONLY);if(fd==-1){perror("fd open error");exit(-1);}while(1){char buffer[1024];memset(buffer,0,sizeof(buffer));int ret=read(fd,buffer,sizeof(buffer)-1);buffer[ret]='\0';printf("%s\n",buffer);}return 0;
}
注意事项:
“"有名管道"这种特殊文件,只能使用mkfifo函数来创建
为了保证有名管道一定被创建,最好是两个进程都包含创建管道的代码,谁先运行就谁先创建,后运行的发现管道经创建好了,那就直接open打开使用。
不能以O_RDWR模式打开命名管道FIFO文件,否则其行为是未定义的,管道是单向的,不能同时读写
System V IPC
特点:
与管道不同,他完全使用了不同的实现机制,与文件没有任何关系,也就是说内核不再以文件形式
System V IPC不在以文件形式存在,所以没有文件描述符这个东西,但有类似的标识符
任何进程间通信时,都可以使用System V IPC来通信
优点:减少进程间通信的开销(文件的开销大于链表、内存、整形);Linux和Unix都通用
消息队列
消息队列的本质就是由内核创建的用于存放消息的链表,由于是存放消息的,所以我们就把这个链表称为消息队列
分类
System V的消息队列
Posix消息队列团
消息的组成(结构体)
1.消息编号:识别消息;
2.消息正文:真正的信息内容
消息队列API
创建
key值:
1.指定为IPC_PRIVATE宏,指定这个宏后,每次调用msgget时都会创建一个新的消息
队列。如果你每次使用的必须是新消息队列的话,就可以指定这个,不过这个用的很少。因为一般来说,只要有一个消息队列可以用来通信就可以了﹐并不需要每次都创建一个全新的消息队列。
2.自己指定一个整数型,但容易重复指定。本来我想创建一个新的消息队列,结果我所指定的这个整形数﹐之前就已经被用于创建某个消息队列了,当我的指定重复时msgget就不会创建新消息队列,而是使用的是别人之前就创建好的消息队列。所以我们也不会使用这种方式来指定key值
3.key_t ftok(const char *pathname, int proj_id);
ftok通过指定路径名和一个整形数,就可以计算并返回一个唯一对应的key值,只要路径名和整形数不变,所对应的key值就唯一不变的。
不过由于ftok只会使用整形数《 proj_id》的低8位,因此我们往往会指定为一个ASCII码值,因为ASCII码值刚好是8位的整形数。
msgflag
指定创建时的原始权限,比如0664
创建一个新的消息队列时,除了原始权限,还需要指定IPC_CREAT选项。
msgid = msgget(key, 0664 | IPC_CREAT);
查看消息队列命令:
ipcs -a是默认的输出信息:打印出当前系统中所有的进程间通信方式的信息
ipcs -m打印出使用共享内存进行进程间通信的信息
ipcs -q打印出使用消息队列进行进程间通信的信息
ipcs -s打印出使用信号量进行进程间通信的信息
获取属性及删除
进程结束后,system v ipc不会自动删除,进程结束后,使用ipc依然能够查看到
1.重启OS
2.使用ipcrm命令删除:
ipcrm -Q msgkey移除用msqkey创建的消息队列
ipcrm -q msqid移除用msqid标识的消息队列
3.int msgctl(int msqid, int cmd, struct msqid_ds *buf);//也可以获取消息队列的属性
cmd:
IPC_STAT:将msqid消息队列的属性信息,读到第三个参数所指定的缓存。
IPC_SET:IPC_SET:使用第三个参数中的新设置去依改消息队列的属性
定一个struct msqid_ds buf
将新的属性信息设置到buf中
cmd指定为IPC_SET后,msgctl函数就会使用buf中的新属性去修改消息队列原有的属性
IPC_RMID:删除消息队列,第三个参数置为空
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define FILE "./msg_file"
int main()
{key_t key;key = ftok(FILE, 'F'); // 定义key值int msgid = msgget(key, 0777 | IPC_CREAT); // 创建消息队列if (msgid < 0){perror("msgget error");}printf("%x\n", key);printf("%d\n", msgid);// msgctl(msgid,IPC_RMID,NULL); 删除队列return 0;
}
发送
接收
删除
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <stdlib.h>#define FILE_PATH "./path"
int msgpid;
void handler(int sig)
{if (sig == SIGINT){msgctl(msgpid, IPC_RMID, NULL);}
}
struct msgbuf
{long mstype;char mstext[1024];
};
int main(int argc, char **argv)
{signal(SIGINT, handler); // 后续循环只能用ctrl+c来退出,只能通过信号处理函数来删除信号队列key_t key = ftok(FILE_PATH, 'K');msgpid = msgget(key, 0777 | IPC_CREAT);if (msgpid < 0){perror("msgget error");}pid_t pid = fork();if (pid < 0){perror("fork error");}else if (pid == 0) // 发消息到队列{while (1){struct msgbuf m1;memset(&m1, 0, sizeof(struct msgbuf));printf("input type:");scanf("%ld", &m1.mstype);printf("input text:");scanf("%s", m1.mstext);if (msgsnd(msgpid, &m1, sizeof(m1.mstext), IPC_NOWAIT) < 0){perror("msgsnd error");_exit(-1);}}}else // 从队列读消息{while (1){struct msgbuf m2;memset(&m2, 0, sizeof(struct msgbuf));if (msgrcv(msgpid, &m2, sizeof(m2.mstext), 3, MSG_NOERROR) < 0){perror("msgrcv error");exit(-1);}else{printf("msg rcv:%s\n", m2.mstext);}sleep(1);}}return 0;
}
消息队列的使用步骤
创建 收发 删除
代码实例
消息队列的特点
传送有格式的消息流
多进程网状交叉通信,消息队列是上上之选
能实现大规模(进程规模多,不是说数据量大)数据的通信
通过共同参数的ftok函数,生成的信号队列,可以实现两个无血缘关系进程的读写:
msg_write.c:
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <stdlib.h>#define FILE_PATH "./path"
int msgpid;
void handler(int sig)
{if (sig == SIGINT){msgctl(msgpid, IPC_RMID, NULL);}
}
struct msgbuf
{long mstype;char mstext[1024];
};
int main(int argc, char **argv)
{signal(SIGINT, handler); // 后续循环只能用ctrl+c来退出,只能通过信号处理函数来删除信号队列key_t key = ftok(FILE_PATH, 'K');msgpid = msgget(key, 0777 | IPC_CREAT);if (msgpid < 0){perror("msgget error");}while (1){struct msgbuf m1;memset(&m1, 0, sizeof(struct msgbuf));printf("input type:");scanf("%ld", &m1.mstype);printf("input text:");scanf("%s", m1.mstext);if (msgsnd(msgpid, &m1, sizeof(m1.mstext), IPC_NOWAIT) < 0){perror("msgsnd error");_exit(-1);}}return 0;
}msg_read.c :
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <stdlib.h>#define FILE_PATH "./path"
int msgpid;
void handler(int sig)
{if (sig == SIGINT){msgctl(msgpid, IPC_RMID, NULL);}
}
struct msgbuf
{long mstype;char mstext[1024];
};
int main(int argc, char **argv)
{signal(SIGINT, handler); // 后续循环只能用ctrl+c来退出,只能通过信号处理函数来删除信号队列key_t key = ftok(FILE_PATH, 'K');msgpid = msgget(key, 0777 | IPC_CREAT);if (msgpid < 0){perror("msgget error");}while (1){struct msgbuf m2;memset(&m2, 0, sizeof(struct msgbuf));if (msgrcv(msgpid, &m2, sizeof(m2.mstext), 3, 0) < 0) //阻塞的读{perror("msgrcv error");exit(-1);}else{printf("msg rcv:%s\n", m2.mstext);}sleep(1);}return 0;
}
共享内存
让同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新
API:
创建:
删除:
1.重启OS
2.使用ipcrm命令删除:
ipcrm -M shmkey移除用shmkey创建的共享内存段
ipcrm -m shmid移除用shmid标识的共享内存段
3.int shmctl(int shmid, int cmd, struct shmid_ds *buf);
映射:
char *shm_c = shmat(shmid1, NULL, 0); // shmat结果是void类型的指针(强制类型转换)// null代表系统分配内存地址;// 0代表可读可写;shm_rdonly代表只读
取消映射:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>#define SIZE_T 4096
#define FILE_PATh ".demo1"int shmid1;void handler(int sig)
{shmctl(shmid1, IPC_RMID, NULL);printf("delete done\n");exit(1);
}int main(int argc, char **argv)
{key_t key1 = ftok(FILE_PATh, 'F');shmid1 = shmget(key1, SIZE_T, 0777 | IPC_CREAT);if (shmid1 == -1){perror("shmget error");exit(-1);}signal(SIGINT, handler);char *shm_c = shmat(shmid1, NULL, 0); // shmat结果是void类型的指针(强制类型转换)// null代表系统分配内存地址;// 0代表可读可写;shm_rdonly代表只读pid_t pid = fork();if (pid < 0){perror("fork error");exit(-1);}else if (pid > 0) // 父进程 用来向内存写{while (1){char buffer[1024];memset(buffer, 0, sizeof(buffer));scanf("%s", buffer);strcpy(shm_c, buffer);}}else // 子进程 用来从内存读{while (1){char buffer[1024];memset(buffer, 0, sizeof(buffer));memcpy(buffer, shm_c, sizeof(buffer));memset(shm_c, 0, sizeof(buffer));printf("receive buffer = %s\n", buffer);sleep(1);}}return 0;
}
改进为阻塞读取(以节省CPU资源):
1.信号:
【pause()
函数会一直等待直到收到一个信号。当进程接收到一个信号时,如果该信号没有被忽略并且没有注册对应的信号处理函数,pause()
函数会被信号中断并返回 -1,同时将 errno
设置为 EINTR
】
2.信号量
特点
开销最小,减少进入内核次数;
直接使用地址来读写,效率更高,适用于大数据量的通信
作业:实现任意进程间的阻塞读取
读数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/shm.h>#define FILE_PATH "./pipe_file"
#define SIZE_T 4096void handler(int sig)
{if (sig == SIGINT){remove(FILE_PATH);exit(-1);}if (sig == SIGUSR1)//空处理,可以唤醒pause{}
}
int main(int argc, char **argv) // 读
{signal(SIGINT, handler);signal(SIGUSR1, handler);if (mkfifo(FILE_PATH, 0777) < 0) // 创建有名管道{perror("mkfifo error");exit(-1);}int fd = open(FILE_PATH, O_WRONLY); /// 只写打开有名管道文件if (fd < 0){perror("open error");exit(-1);}pid_t pid1 = getpid(); // 获取当前进程pid号,并通过有名管道传给 写 进程printf("%d\n", pid1);if (write(fd, &pid1, sizeof(pid_t)) < 0){perror("write pid1 error");exit(-1);}key_t key1 = ftok(FILE_PATH, 'F'); // 配置共享内存int shmid1 = shmget(key1, SIZE_T, 0777 | IPC_CREAT);if (shmid1 < 0){perror("shmid1 error");exit(-1);}char *shm = (char *)shmat(shmid1, NULL, 0); // 共享内存if (shm == NULL){perror("shm error");exit(-1);}while (1) // 取出共享内存内的数据{printf("please wait output\n");pause();char buffer[1024];memset(buffer, 0, sizeof(buffer));strcpy(buffer, shm);printf("receive buffer :%s\n", buffer);memset(shm, 0, SIZE_T);}return 0;
}写数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/shm.h>#define FILE_PATH "./pipe_file"
#define SIZE_T 4096int main(int argc, char **argv) // 写
{int fd = open(FILE_PATH, O_RDONLY); // 只读打开有名管道if (fd < 0){perror("open error");exit(-1);}pid_t pid1;read(fd, &pid1, sizeof(pid_t)); // 把 读 进程pid号通过管道读出来printf("%d\n",pid1);key_t key1 = ftok(FILE_PATH, 'F'); // 配置共享内存int shmid1 = shmget(key1, SIZE_T, 0777 | IPC_CREAT);if (shmid1 < 0){perror("shmid1 error");exit(-1);}char *shm = (char*)shmat(shmid1, NULL, 0); // 共享内存if (shm == NULL){perror("shm error");exit(-1);}while (1) // 写数据,写完就传个信号给 读 进程{char buffer[1024];printf("please input\n");scanf("%s", buffer);strcpy(shm, buffer);kill(pid1,SIGUSR1);}return 0;
}
信号量(信号锁、信号灯)
当多个进程/线程进行共享访问的时候,用于资源保护,以防止资源出现干扰的情况
进程同步:进程按照一定的顺序执行(不是指先后顺序,而是指互斥)
进程竞态:
互斥:对于互斥操作来说,多进程共享操作时,多个进程间不关心谁先操作、谁后操作的先后顺序问题,它们只关心,自己操作时候,别人不能操作
同步:所谓同步就是,多个共享操作时,进程必须要有统
操作的步调,按照一定的顺序来操作
解决方法:加锁
信号量(信号锁):信号量其实是一个OS创建的,供相关进程共享的int变量,只不过我们在调用相关API创建信号量时,我们创建的都是一个信号量集合,所谓集合就是可能会包含好多个信号量。
用于互斥时,集合中只包含一个信号量。
用于同步时,集合中会包含多个信号量,至于多少个,需要看情况
API:
1.配置信号队列:int semget (key_t key,int nsems,int semflg)
参数:
key:用ftok获取key值
nsems:指定集合中信号量的个数 【用于互斥时,数量都指定为1,因为只需要一个信号量】
semfig:权限:―般都设置为0664 | IPC_CREAT 【设置同消息队列和共享内存】
2.控制信号队列:int semctl(int semid,int semnum,int cmd,...)
参数:
semnum:集合中某个信号量的编号(集合中某个信号量的编号:信号量的编号为非负整数,而且是自动从0开始)
cmd:IPC_STAT; IPC_SET; IPC_RMID
可变参数:......
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>#define FILE_PATH "./sem_demo"int semid;
void delete_sem(int, int);
void handler(int sig)
{delete_sem(semid, 0);
}void creat_sem(int nsems) // 创建
{key_t key = ftok(FILE_PATH, 'f');int semid = semget(key, nsems, 0777 | IPC_CREAT);if (semid < 0){perror("semget error");exit(-1);}
}
void init_sem(int semid, int semnum, int val) // 初始化制定信号量的值
{semctl(semid, semnum, SETVAL, val);
}
void delete_sem(int semid, int semnum) // 删除制定信号量的值
{if (semctl(semid, semnum, IPC_RMID) < 0){perror("semctl delete error");}
}
int main(int argc, char **argv)
{key_t key = ftok(FILE_PATH, 'F');semid = semget(key, 3, 0777 | IPC_CREAT);if (semid < 0){perror("semget error");exit(-1);}printf("%d\n", semid);pid_t pid = fork();if (pid > 0){while (1){sleep(1);}}if (pid == 0){signal(SIGINT, handler);//只能在子进程中注册,在main函数内注册的话,当contrl+c时候,,父子进程都要调用一次handler,也就是删除的函数,则会报错while (1){sleep(1);}}// printf("%x\n", key);// semctl(semid, 0, IPC_RMID);return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>#define FILE_PATH "./sem_demo"int semid;
pid_t pid;
void delete_sem(int);void handler(int sig)
{printf("handler is used\n");delete_sem(0);semctl(semid, 0, IPC_RMID);exit(-1);
}void lock(int semid1, int semnum1) // 封装 上锁 函数
{struct sembuf sembuffer1[1];sembuffer1[0].sem_num = semnum1;sembuffer1[0].sem_op = -1;sembuffer1[0].sem_flg = SEM_UNDO;semop(semid1, sembuffer1, 1);
}void unlock(int semid1, int semnum1) // 封装 解锁 函数
{struct sembuf sembuffer1[1];sembuffer1[0].sem_num = semnum1;sembuffer1[0].sem_op = 1;sembuffer1[0].sem_flg = SEM_UNDO;semop(semid1, sembuffer1, 1);
}void creat_sem(int nsems) // 创建
{key_t key = ftok(FILE_PATH, 'f');semid = semget(key, nsems, 0777 | IPC_CREAT);if (semid < 0){perror("semget error");exit(-1);}
}
void init_sem(int semid, int semnum, int val) // 初始化指定信号量的值
{semctl(semid, semnum, SETVAL, val);
}
void delete_sem(int semnum) // 删除制定信号量的值
{if (semctl(semid, semnum, IPC_RMID) < 0){perror("semctl delete error");}printf("delete done\n");
}
int main(int argc, char **argv)
{int fd = open("a.txt", O_WRONLY | O_APPEND | O_CREAT, 0655);if (fd == -1){perror("fd error");exit(-1);}creat_sem(1);init_sem(semid, 0, 1);printf("%d\n", semid);pid = fork();if (pid > 0){while (1){lock(semid, 0);write(fd, "helloworld", 10);write(fd, "helloworld", 10);write(fd, "\n", 1);unlock(semid, 0);sleep(1);}}if (pid == 0){signal(SIGINT, handler); // 只能在子进程中注册,在main函数内注册的话,当contrl+c时候,,父子进程都要调用一次handler,也就是删除的函数,则会报错while (1){lock(semid, 0);write(fd, "hhhhhwwwww", 10);write(fd, "hhhhhwwwww", 10);write(fd, "\n", 1);unlock(semid, 0);sleep(1);}}return 0;
}
作业:
答:
sem.h:
#ifndef _MYSEM_H_
#define _MYSEM_H_#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>#define FILE_PATH "./sem_demo"void creat_sem(int *semid, int nsems); // 创建
void init_sem(int semid, int semnum, int val); // 初始化指定信号量的值
void lock(int semid1, int semnum1); // 封装 上锁 函数
void unlock(int semid1, int semnum1); // 封装 解锁 函数
void delete_sem(int semid, int semnum); // 删除制定信号量的值
#endifsem.c:#include "sem.h"void delete_sem(int semid, int semnum) // 删除制定信号量的值
{if (semctl(semid, semnum, IPC_RMID) < 0){perror("semctl delete error");}printf("delete done\n");
}void lock(int semid1, int semnum1) // 封装 上锁 函数
{struct sembuf sembuffer1[1];sembuffer1[0].sem_num = semnum1;sembuffer1[0].sem_op = -1;sembuffer1[0].sem_flg = SEM_UNDO;semop(semid1, sembuffer1, 1);
}void unlock(int semid1, int semnum1) // 封装 解锁 函数
{struct sembuf sembuffer1[1];sembuffer1[0].sem_num = semnum1;sembuffer1[0].sem_op = 1; sembuffer1[0].sem_flg = SEM_UNDO;semop(semid1, sembuffer1, 1);
}void creat_sem(int *semid,int nsems) // 创建
{key_t key = ftok(FILE_PATH, 'f');*semid = semget(key, nsems, 0777 | IPC_CREAT);if (semid < 0){perror("semget error");exit(-1);}
}void init_sem(int semid, int semnum, int val) // 初始化指定信号量的值
{semctl(semid, semnum, SETVAL, val);
}sem_abcd.c:#include "sem.h"
int semid;
int main(int argc, char **argv)
{creat_sem(&semid, 4);init_sem(semid, 0, 1);for (int i = 1; i < 4; ++i){init_sem(semid, i, 0);}pid_t pid1 = fork();if (pid1 > 0){pid_t pid2 = fork();if (pid2 > 0){while (1){lock(semid, 0);printf("A\n");sleep(1);unlock(semid, 1);}}if (pid2 == 0){while (1){lock(semid, 1);printf("B\n");sleep(1);unlock(semid, 2);}}}else if (pid1 == 0){pid_t pid3 = fork();if (pid3 > 0){while (1){lock(semid, 2);printf("C\n");sleep(1);unlock(semid, 3);}}if (pid3 == 0){while (1){lock(semid, 3);printf("D\n");sleep(1);unlock(semid, 0);}}}return 0;
}
google 笔试题: