目录
fifow.c 将内容写入管道文件mkfifo中去(mkfifo 根目录下)
一、概念
进程和进程之间的信息交换
二、常用通信方式
常用 | 过时 |
无名管道(pipe) | System V IPC |
有名管道 (fifo) | 共享内存(share memory) |
⭐信号(signal) | 消息队列(message queue) |
共享内存(mmap) | 信号灯集(semaphore set) |
套接字(socket) | 共享内存(share memory) |
三、无名管道(单向)
无名管道创建-pipe:
#include <unistd.h> //头文件
int pipe(int pfd[2]);
创建时会返回两个文件描述符,分别用于读写管道
父子进程创建管道示例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(){
int pfd[2];
int re;
char buf[20]={0};
pid_t pid;
//make pipe 创建管道
re = pipe(pfd);
if(re<0){
perror("pipe");//返回创建管道错误信息
return 0;
}
//make process 创建进程
pid = fork();
if(pid<0){
perror("fork"); //返回创建进程错误信息
return 0;
}else if(pid==0){ //child 子进程
while(1){
strcpy(buf,"hahahahahahaa");
//写入 pfd【1】为写描述符
write(pfd[1],buf,strlen(buf));
sleep(1); //休眠1秒钟
}
}else{
//father 父进程
while(1){
//读取 pfd【0】为读描述符
re = read(pfd[0],buf,20);
if(re>0){
printf("read pipe=%s\n",buf);
}
}
}
}
无名管道注意事项:
1.只能用于亲缘关系的进程间通信(父子进程,兄弟进程)
2.管道通信是单向的,一端读,一端写(程序实现设计好)
3.数据不能自己读自己写
4.管道可以用于大于2个进程共享
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(){
int pfd[2];
int i;
int re;
char buf[40]={0};
pid_t pid;
//make pipe 创建管道
re = pipe(pfd);
if(re<0){
perror("pipe");
return 0;
}
//输出 读输出符 写输出符
printf("%d,%d\n",pfd[0],pfd[1]);
//循环 创建两个子进程
for(i=0;i<2;i++){
pid = fork();
if(pid<0){
perror("fork"); //打印进程创建错误信息
return 0;
}else if(pid>0){ //父进程
}else{
break; //不创建子进程
}
}
if(i==2){ //主进程
close(pfd[1]); //关闭写端
while(1){
memset(buf,0,40); //清除 buf中的数据
re=read(pfd[0],buf,40) ;//打开读端
if(re>0){
printf("%s\n",buf);
}
}
return 0;
}
if(i==1){ //子进程1
close(pfd[0]); //关闭读端
while(1){
strcpy(buf,"this is 2 process");
write(pfd[1],buf,strlen(buf)); //打开写端
usleep(930000);
}
return 0;
}
if(i==0){ //子进程2
close(pfd[0]); //关闭读端
while(1){
strcpy(buf,"this is 1 process");
write(pfd[1],buf,strlen(buf)); //打开写端
sleep(1);
}
return 0;
}
}
无名管道的读写特性:
1.读管道:pfd[0]
· 管道中有数据,read返回实际读到的字节数
· 管道写端被全部关闭,read返回0
写端没有全部被关闭,但也不写,read阻塞等待
2.写管道:pfd[1]
· 管道读端全部被关闭,进程异常终止 close(pfd[1]) close(pfd[0])
· 管道读端没有全部关闭:
管道已满,write阻塞
管道未满, write将数据写入 并返回实际写入的字节数
四、有名管道(命名管道)
创建有名管道mkfifo:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
打开管道文件:
文件IO方式打开
open(const char *path, O_RDONLY);//1
open(const char *path, O_RDONLY | O_NONBLOCK);//2 非阻塞
open(const char *path, O_WRONLY);//3
open(const char *path, O_WRONLY | O_NONBLOCK);//4 非阻塞
两无关进程通信代码案例:
fifow.c 将内容写入管道文件mkfifo中去(mkfifo 根目录下)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
int main(){
//创建有名管道
int re;
int fd;
re = mkfifo("/myfifo",0666);
if(re<0){
perror("mkfifo");
// return 0;
}
//打开管道文件
fd = open("/myfifo",O_WRONLY); // 只写方式打开
if(fd<0){
perror("open");
return 0;
}
//将信息写道管道里面去
while(1){
fgets(buf,32,stdin);//从键盘输入读取
write(fd,buf,strlen(buf));
}
}
fifor.c 输出管道中信息
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
int main(){
int fd;
int re;
//打开管道文件
fd = open("/myfifo",O_RDONLY); // 只读方式打开
if(fd<0){
perror("open");
return 0;
}
//将管道信息读出
while(1){
read(fd,buf,32);
if(re>0){
printf("read fifo=%s\n",buf);
}else if(re==0){
exit(0);
}
}
}
有名管道特点:
1有名管道可以使非亲缘的两个进程互相通信
2通过路径名来操作,在文件系统中可见,但内容存放在内存中
3 文件IO来操作有名管道
4 遵循先进先出规则
5 不支持leek操作
6 单工读写
五、内存映射
概念:
- 共享内存可以通过mmap()映射普通文件
- 使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write。
mmap()的优点:
用户空间和内核空间的高效交互方式
函数定义:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
- 功能:创建共享内存映射
- 函数返回值:成功返回创建的映射区首地址,失败返回MAP_FAILED (((void *) -1) ),设置errno值
- 参数说明:
- addr:指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。
- length:必须>0。映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。
- prot:指定共享内存的访问权限。可取如下几个值的可选:PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行), PROT_NONE(不可访问)。
- flags:由以下几个常值指定: 映射区权限MAP_SHARED(共享的) MAP_PRIVATE(私有的), MAP_FIXED(表示必须使用 start 参数作为开始地 址,如果失败不进行修正),其中,MAP_SHARED , MAP_PRIVATE必选其一,而 MAP_FIXED 则不推荐使 用。MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)
- fd:表示要映射的文件句柄。如果匿名映射写-1。
- offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。
注意事项:
(1)创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。
(2)当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护),如果不满足报非法参数(Invalid argument)错误。
当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。
(3)映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。任然可以读 写
(4)用于映射的文件大小必须>0,当映射文件大小为0时,指定非0大小创建映射区,访问映射地址会报总线错误,指定0大小创建映射区,报非法参数错误(Invalid argument)
(5)文件偏移量必须为0或者4K的整数倍(不是会报非法参数Invalid argument错误).
(6)映射大小可以大于文件大小,但只能访问文件page的内存地址,否则报总线错误,超出映射的内存大小报段错误。
mmap()映射的种类:
- 文件映射:文件映射将一个文件的一部分直接映射到调用mmap()进程的虚拟内存中去。当一个文件被映射之后,就可以通过对该内存区域中的字节进行操作,从而实现对文件的读写操作。这种映射被称为基于文件的映射或内存映射文件。
- 匿名映射:匿名映射没有对应的文件。可以把它看成是一个内容被初始化为0的虚拟文件的映射。
文件映射:
案例一:映射文件中的内容
#include <stdio.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(){
void* addr;
int fd;
fd = open("test",O_RDWR);
if(fd<0){
perror("open");
return 0;
}
addr = mmap(NULL, 1000, PROT_READ|PROT_WRITE ,MAP_SHARED, fd, 0);
if (addr == MAP_FAILED){
perror("mmap");
return 0;
}
// memcpy(addr,"abcdef",7);
printf("read=%s\n",(char*)addr);
}
mmap建立映射空间将test文本中的内容打印出来
案例二:两个进程之间的通信
mmap_w.c 写入:
#include <stdio.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(){
void* addr;
int fd;
int i =0;
fd = open("test",O_RDWR);
if(fd<0){
perror("open");
return 0;
}
// int len = lseek(fd,0,SEEK_END);
addr = mmap(NULL, 2048, PROT_READ|PROT_WRITE , MAP_SHARED, fd, 0);
if (addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd);
while(i<2048){
memcpy((addr+i),"a",1);
i++;
sleep(1);
}
// printf("read=%s\n",(char*)addr);
}
mmap_r.c 读取
#include <stdio.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(){
void* addr;
int fd;
fd = open("test",O_RDWR);
if(fd<0){
perror("open");
return 0;
}
int len = lseek(fd,0,SEEK_END); //返回文件大小
addr = mmap(NULL, len, PROT_READ|PROT_WRITE , MAP_SHARED, fd, 0);
if (addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd);
// memcpy(addr,"abcdef",10);
while(1){
printf("read=%s\n",(char*)addr);
sleep(1);
}
}
memcpy函数语法: 
匿名映射 MAP_ANONYMOUS
案例三:父子间进程通讯
#include <stdio.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
int main(){
void* addr;
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE , MAP_SHARED| MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED){
perror("mmap");
return 0;
}
//创建进程
pid_t pid;
pid = fork();
if(pid<0){
perror("fork");
return 0;
}
//父进程
else if(pid>0){
memcpy(addr,"1234567890",10);
wait(NULL);
}
//子进程
else{
sleep(1);
printf("read father val=%s\n",(char*)addr);
}
}
释放内存映射munmap函数
int munmap(void *addr, size_t length);
返回值:成功返回0,失败返回-1,并设置errno值。
函数参数:
addr:调用mmap函数成功返回的映射区首地址
length:映射区大小(即:mmap函数的第二个参数)
六、System V 共享内存
System-V IPC:进程间通讯
共享内存
直接访问内核,避免了应用程序从内核空间访问到用户空间
system V 共享内存使用步骤:
1 ftok 生成key
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
}
2 创建打开共享内存shmget 返回内存id号
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
shmid = shmget(key,512,IPC_CREAT|0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
}
3 共享内存映射 - shmat
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
shmid = shmget(key,512,IPC_CREAT|0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
//-------------------------------------
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
strcpy(buf,"hello world");
}
代码汇总:
写入共享内存: shm_w.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main(){
key_t key;
int shmid;
char *buf;
//生成key
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
//创建共享内存 返回 id号
shmid = shmget(key,512,IPC_CREAT|0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
//共享内存映射
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
//buf 是地址 写入内容
strcpy(buf,"hello world");
}
读出共享内存: shm_r.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
//不用再创建
shmid = shmget(key,512,0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
//读取
printf("share mem=%s\n",buf);
while(1){
sleep(1);
}
//撤销映射
shmdt(buf);
//删除
shmctl(shmid, IPC_RMID, NULL);
}
共享内存撤销映射 - shmdt
共享内存控制 - shmctl
七、信号机制
概念:
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式;
linux内核通过信号通知用户进程,所有信号的产生及处理全部都是由内核完成的,不同的信号类型代表不同的事件;
linux对早期的unix信号机制进行了扩展
信号的产生:(软中断)
信号处理方式:
- 缺省方式
- 忽略信号
- 捕捉信号
常用信号:
信号名 | 含义 | 默认操作 |
SIGHUP | 该信号在用户终端关闭时产生,通常是发给和该 终端关联的会话内的所有进程 | 终止 |
SIGINT | 该信号在用户键入INTR字符(Ctrl-C)时产生,内 核发送此信号送到当前终端的所有前台进程 | 终止 |
SIGQUIT | 该信号和SIGINT类似,但由QUIT字符(通常是 Ctrl-\)来产生 | 终止 |
SIGILL | 该信号在一个进程企图执行一条非法指令时产生 | 终止 |
SIGSEV | 该信号在非法访问内存时产生,如野指针、缓 冲区溢出 | 终止 |
SIGPIPE | 当进程往一个没有读端的管道中写入时产生,代 表“管道断裂” | 终止 |
信号名 | 含义 | 默认操作 |
SIGKILL | 该信号用来结束进程,并且不能被捕捉和忽略 | 终止 |
SIGSTOP | 该信号用于暂停进程,并且不能被捕捉和忽略 | 暂停进程 |
SIGTSTP | 该信号用于暂停进程,用户可键入SUSP字符( 通常是Ctrl-Z)发出这个信号 | 暂停进程 |
SIGCONT | 该信号让进程进入运行态 | 继续运行 |
SIGALRM | 该信号用于通知进程定时器时间已到 | 终止 |
SIGUSR1/2 | 该信号保留给用户程序使用 | 终止 |
SIGCHLD | 是子进程状态改变发给父进程的。 |
信号命令kill/killal:
信号的函数:
定时器函数alarm/pause
int alarm (unsigned int seconds);
● 成功时返回上个定时器的剩余时间,失败时返回EOF
● seconds定时器的时间
● 一个进程中只能设定一个定时器,时间到产生SIGALRM
int pause(void);
暂停
● 进程一直阻塞,直到被信号中断
● 被信号终端后返回-1,errno为EINTR
信号的捕捉
过程:
- 定义新的信号的执行函数handle。
- 使用signal/sigaction函数,把自定义的handle和指定的信号相关联。
signal函数
头文件: typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:捕捉信号执行自定义函数
返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR(写入新的行为,返回旧的行为)
signum 要设置的信号类型
handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;
sigaction函数:
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
结构体:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
参数:
signum:处理的信号
act,oldact: 处理信号的新行为和旧的行为,是一个sigaction结构体。
sigaction结构体成员定义如下:
sa_handler:是一个函数指针,其含义与 signal 函数中的信号处理函数类似
sa_sigaction:另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。
sa_flags参考值如下:
SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数
SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
re_restorer:是一个已经废弃的数据域
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>
typedef void (*sighandler_t)(int);
sighandler_t oldact;
void handle(int sig){
printf("i cath the SIGINT \n");
// signal(SIGINT,oldact);
}
int main(){
//填结构体内容
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
//清空内容
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
while(1){
sleep(1);
}
}
定时器的实现
unsigned int alarm(unsigned int seconds);
功能:定时发送SIGALRM给当前进程
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
功能:定时的发送alarm信号
参数:int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
功能:定时的发送alarm信号
参数:
which:
· ITIMER_REAL:以逝去时间递减。发送SIGALRM信号
· ITIMER_VIRTUAL: 计算进程(用户模式)执行的时间。发送SIGVTALRM信号
· ITIMER_PROF: 进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计算时间。发送SIGPROF信号
new_value: 负责设定 timout 时间
old_value: 存放旧的timeout值,一般指定为NULL
struct itimerval {
struct timeval it_interval; // 闹钟触发周期
struct timeval it_value; // 闹钟触发时间
};
struct timeval { //结构体里的结构体
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>
#include <sys/time.h>
typedef void (*sighandler_t)(int);
sighandler_t oldact;
void handle(int sig){
if(sig == SIGINT)
{
printf("i cath the SIGINT \n");
}
else if (sig==SIGALRM)
{
printf("second timer \n");
// alarm(1);
}
// signal(SIGINT,oldact);
}
int main(){
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
// alarm(1);
//
struct itimerval timevalue;
//触发周期
timevalue.it_interval.tv_sec = 1;
timevalue.it_interval.tv_usec = 0;
//触发时间
timevalue.it_value.tv_sec = 5;
timevalue.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &timevalue, NULL);
sigaction(SIGALRM,&act,NULL);
while(1){
// sleep(1);
}
}
SIGCHLD回收子进程
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
void handle(int sig){
wait(NULL);
printf("Get sig = %d\n",sig);
}
int main(){
pid_t pid;
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
pid = fork();
if(pid>0){
//wait(NULL); 父进程
sigaction(SIGCHLD,&act,NULL);
while(1){
printf("this is father process\n");
sleep(1);
}
} //子进程
else if(pid == 0){
sleep(3);
exit(0);
}
}
信号集、信号屏蔽和阻塞
有时候不希望在接到信号时就立即停止当前执行,去处理信号,同时也不希望忽略该信号,而是延时一段时间去调用信号处理函数。这种情况可以通过阻塞信号实现。
信号的阻塞概念:信号的”阻塞“是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。
信号的状态:
- 信号递达(Delivery ):实际信号执行的处理过程(3种状态:忽略,执行默认动作,捕获)
- 信号未决(Pending):从产生到递达之间的状态
信号集操作函数
sigset_t set; 自定义信号集。 是一个32bit 64bit 128bit的数组。
sigemptyset(sigset_t *set); 清空信号集
sigfillset(sigset_t *set); 全部置1
sigaddset(sigset_t *set, int signum); 将一个信号添加到集合中
sigdelset(sigset_t *set, int signum); 将一个信号从集合中移除
sigismember(const sigset_t *set,int signum); 判断一个信号是否在集合中。
设定对信号集内的信号的处理方式(阻塞或不阻塞)
八、消息队列
概念:
进程通讯的一种手段。
- 消息队列是System V IPC对象的一种
- 消息队列由消息队列ID来唯一标识
- 消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息
- 消息队列可以按照类型来发送、接受消息
消息队列的使用:
- 打开/创建消息队列 msgget
- 向消息队列发送消息 msgsnd
- 从消息队列接收消息 msgrcv
- 控制消息队列 msgctl
消息格式:
发送端:
- 申请Key
- 打开/创建消息队列 msgget
- 向消息队列发送消息 msgsnd
接收端:
- 打开/创建消息队列 msgget
- 从消息队列接受消息 msgrcv
- 控制(删除)消息队列 msgctl
九、信号灯/信号量(semaphore)
概念:
- 信号量代表一类资源,其值表示系统中该资源的数量
- 信号量是一个受保护的变量,只能通过三种操作来访问
-
- 初始化
- P操作(申请资源)- 消费者 -1
- V操作(释放资源)- 生产者 +1
三种信号灯:
- Posix 有名信号灯
- Posix 无名信号灯 (linux只支持线程同步)
- System V 信号灯
Posix 有名信号灯和无名信号灯使用:
未完待续