1.进程通信简介
进程间的通信又叫IPC通信,包括:管道、信号、信号量集、共享内存、消息队列。
都遵循:打开创建PIC、读写IPC、关闭IPC
2.管道
管道分为有名管道和无名管道,无论是什么管道,都是半双工通信-即同一时刻使能收或者发,所以在进程通信的时候要创建两条管道。且管道遵循先入先出的原则。
注意:创建管道需要在创建进程之前。因为你fork以后子进程会拷贝除了父进程pid以外的所有东西(包括虚拟地址空间等),相当于进程运行了两次.
1.无名管道:
无名管道是一种独立的文件系统,它不存在磁盘,也不存在于的文件系统中(没有文件节点)。无名管道只存在于内存中,只能用于父子进程或者兄弟进程之间的通信。
相关API:b
int pipe(int pipefd[2]);
函数描述:创建无名管道,pipefd[0]是读端,pipefd[1]是写端-------------文件描述符-----通过文件IO操作无名管道
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argv,char *argc[])
{
int pipefd[2] = {0};
int pipe_value = 1;
char buff[32]={0};
char buff1[32]={0};
pipe_value = pipe(pipefd);
pid_t pid = fork();
if(pid > 0)
{
printf("父进程\n");
if(pipe_value == 0)
{
printf("创建无名管道成功\n");
ssize_t num = write(pipefd[1],"hello",5);
if(num != 5)
{
perror("write");
}
}
}
else if(pid == 0)
{
sleep(2);//保证先想管道写
printf("子进程\n");
// execl("./exe.out","exe.out",NULL);
read(pipefd[0],buff1,sizeof(buff1));
printf("buff1 = %s\n",buff1);
}
else
{
return 0;
}
return 0;
}
2.有名管道:
有名管道是建立在实际磁盘或文件系统中实际存在的。有名管道就是fifo文件,任何进程通过文件名才对管道进行读写。
相关API:
int mkfifo(const char * pathname,mode_t mode);
函数描述:mkfifo()会依参数 pathname 建立特殊的 FIFO 文件,该文件必须不存在,而参数 mode 为该文件的权限(mode%~umask),因此 umask值也会影响到 FIFO 文件的权限。Mkfifo()建立的 FIFO 文件其他进程都可以用读写一般文件的方式存取。当使用 open()来打开。
int unlink(const char *pathname);
函数描述:删除一个有名管道。
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdlib.h>
int main(int argv,char *argc[])
{
int pipefd[2] = {0};
int pipe_value = 1;
char buff[32]={0};
char buff1[32]={0};
pipe_value = mkfifo("./fifo",0777);
pid_t pid = fork();
if(pid > 0)
{
printf("父进程\n");
if(pipe_value == 0)
{
printf("创建有名管道成功\n");
int w_fd = open("./fifo",O_WRONLY);
ssize_t num = write(w_fd,"hello",5);
if(num != 5)
{
perror("write");
}
}
}
else if(pid == 0)
{
sleep(2);//保证先想管道写
printf("子进程\n");
// execl("./exe.out","exe.out",NULL);
int r_fd = open("./fifo",O_RDONLY);
read(r_fd,buff1,sizeof(buff1));
printf("buff1 = %s\n",buff1);
}
else
{
return 0;
}
return 0;
}
管道注意:
1 . 只有在管道的读端存在时,向管道写入数据才有意义,否则向管道写入数据的进程将收到内核传来的SIGPIPE信号。
2.无名管道在进行读写操作时,如果没有数据可读,read函数会导致进程阻塞。如果写端不存在,read函数将返回0。
3.如果读进程不读取管道缓冲区中的数据,数据会一直存在,当缓冲区(64K)被填满了,写进程执行写操作就会阻塞。
3.信号
当响应的事件产生时,产生对应的信号。
查看信号:kill -l
信号宏存放在系统头文件signal.h里边
常用:(2)SIGINT信号中断(ctrl+c) (3)SIGQUIT信号退出(ctrl+\) (9)SIGKILL信号杀死 (10)SIGUSR1 用户预留信号 (12)SIGUSR2 用户预留信号 (14)SIGALRM 闹钟信号
(18)SIGCONT(继续运行) (19)SIGTOP(暂停信号)
处理思路:信号的处理跟中断类似,内核发送信号(产生中断)-----------用户进程接收信号(接收中断)-----------------------信号服务函数(中断服务函数)
相关API函数:
1)传送信号给指定进程
函数原型:int kill(pid_t pid,int sig);
函数描述:kill()可以用来送参数 sig 指定的信号给参数 pid 指定的进程。参数 pid 有几种情况:
pid>0 将信号传给进程识别码为 pid 的进程。
pid=0 将信号传给和目前进程相同进程组的所有进程
pid=-1 将信号广播传送给系统内所有的进程
pid<0 将信号传给进程组识别码为 pid 绝对值的所有进程
2)signal设置信号处理方式(注册信号)
函数原型:void (*signal(int signum,void(* handler)(int)))(int);
函数描述:signal()会依参数 signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数 handler 指定的函数执行。如果参数 handler 不是函数指针,则必须是下列两个常数之一:SIG_IGN 忽略参数 signum指定的信号。
SIG_DFL 将参数 signum 指定的信号重设为核心预设的信号处理方式。
返回值:返回先前的信号处理函数指针,如果有错误则返回 SIG_ERR(-1)
需要用typedef来规则函数指针
3)alarm设置信号传送闹钟
函数原型:unsigned int alarm(unsigned int seconds);
函数功能:alarm()用来设置信号 SIGALRM 在经过参数 seconds 指定的秒数后传送给目前的进程。如果参数 seconds 为 0,则之前设置的闹钟会被取消,并将剩下的时间返回。
例:alarm(3)---delay(1)---alarm(4)----delay(3)----alarm(0)
首先定义alarm(3),经过1s延时以后,再次调用alarm(4),此时返回秒数是2,经过3s以后再次调用alarm(0),返回秒数是1,同时闹钟被取消
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <fcntl.h>
#include<signal.h>
typedef void(*signal_t)(int);
void fun(int signal)
{
printf("进入信号服务函数%d\n",signal);
alarm(5);
}
int main(int argv,char *argc[])
{
// kill(-1,atoi(argc[1]));
alarm(5);
signal_t p = signal(SIGALRM,fun);
if(p == SIG_ERR)
{
perror("signal");
}
while(1);
return 0;
}
4)setitimer设置信号传送闹钟(双结构体--一个延时--一个定时)
使用时须要引入的头文件:
#include <sys/time.h>
setitimer函数原型:int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
参数:
int which:参数表示类型。可选的值有:
ITIMER_REAL:以系统真实的时间来计算,它送出SIGALRM信号。
ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF:以该进程在用户态下和内核态下所费的时间来计算。它送出SIGPROF信号。
const struct itimerval *new_value:紧接着的new_value和old_value均为itimerval结构体,先看一下itimerval结构体定义:
struct itimerval {
struct timeval it_interval; /* next value */计时间隔
struct timeval it_value; /* current value */延时时长
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */(范围是10^6)
};
itimeval又是由两个timeval结构体组成,timeval包括tv_sec和tv_usec两部分。当中tv_se为秒。tv_usec为微秒(即1/1000000秒)中的new_value参数用来对计时器进行设置,it_interval为计时间隔,it_value为延时时长,以下样例中表示的是在setitimer方法调用成功后,延时1微秒便触发一次SIGALRM信号,以后每隔200毫秒触发一次SIGALRM信号。
settimer工作机制是,先对it_value倒计时,当it_value为零时触发信号。然后重置为it_interval。继续对it_value倒计时。一直这样循环下去。基于此机制。setitimer既能够用来延时运行,也可定时运行。
假如it_value为0是不会触发信号的,所以要能触发信号,it_value得大于0;假设it_interval为零,仅仅会延时。不会定时(也就是说仅仅会触发一次信号)。
old_value参数,通经常使用不上。设置为NULL,它是用来存储上一次setitimer调用时设置的new_value值。
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <fcntl.h>
#include<string.h>
typedef void(*signal_t)(int);
//struct itimerval
//{
// struct timeval it_interval; /* Interval for periodic timer */
// struct timeval it_value; /* Time until next expiration */
//};
//struct timeval
//{
// time_t tv_sec; /* seconds */
// suseconds_t tv_usec; /* microseconds */
//};
void fun(int signal)
{
printf("进入信号服务函数%d\n",signal);
// alarm(5);
}
int main(int argv,char *argc[])
{
struct itimerval new_value,old_value;
bzero(&new_value,sizeof(struct itimerval));
bzero(&old_value,sizeof(struct itimerval));
//设置没500ms触发一次信号
new_value.it_interval.tv_sec = 0;
new_value.it_interval.tv_usec = 500000;
//设置延时时间
new_value.it_value.tv_sec = 1;//秒数
new_value.it_value.tv_usec = 0;//微秒数
int set_value = setitimer(ITIMER_REAL,&new_value,&old_value);
if(set_value != 0)
{
perror("setitimer");
}
signal_t p = signal(SIGALRM,fun);
if(p == SIG_ERR)
{
perror("signal");
}
while(1);
return 0;
}
5)sigaction查询或设置信号处理方式
函数原型:int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
函数功能:sigaction()会依参数 signum 指定的信号编号来设置该信号的处理函数。参数signum可以指定SIGKILL和SIGSTOP以外的所有信号。
int signum:信号值
const struct sigaction *act:
struct sigaction
{
void (*sa_handler) (int); //信号处理函数
sigset_t sa_mask; //掩码 sigemptyset(初始化) sigaddset(加) sigdelset(减)
int sa_flags; //通常为0
void (*sa_restorer) (void); //此参数没有使用
}
sa_mask: 用来设置在处理该信号时暂时将 sa_mask 指定的信号搁置(暂放)。(未决信号)。
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <fcntl.h>
#include<signal.h>
/*
struct sigaction
{
void (*sa_handler) (int); //信号处理函数
sigset_t sa_mask; //掩码 sigemptyset(初始化) sigaddset(加) sigdelset(减)
int sa_flags; //通常为0
void (*sa_restorer) (void); //此参数没有使用
}*/
typedef void(*signal_t)(int);
void fun(int signal)
{
printf("进入信号服务函数%d\n",signal);
sleep(5);
printf("信号函数退出\n");
}
int main(int argv,char *argc[])
{
struct sigaction act,old;
act.sa_handler = fun;
sigemptyset(&act.sa_mask);//初始化信号集合
sigaddset(&act.sa_mask,SIGQUIT);
act.sa_flags = 0;
sigaction(SIGINT,&act,&old);
while(1);
return 0;
}
发送2号信号以后就如中断服务函数,此时发送信号3就被搁置了(中断存在延时)注意:如果处理期间接收一种信号多次,默认处理一次。
4.共享内存
(1)含义:
共享内存就是在两个或者多个进程之间公用的一块内存区域,即同一快物理内存被映射到进程A,B地址空间,进程A会看到进程B数据的更新(反之也行)
共享内存经过了两次拷贝过程: 用户空间(进程)------>内存 内存----->用户空间
管道、消息队列经过四次拷贝过程: 用户空间-------->内核 内核------->内存 内存----------->内核 内核------->用户空间
特性:共享内存直接读写内存,传输数据效率高,覆盖写,使用前需要进行创建。
(2)内存命令
ipcs-m:查看共享内存
ipcs-q:查看消息队列
ipcs-s:查看信号灯
ipcrm -m id号:删除IPC对象
(3)API函数
1).创建共享内存
函数名称:int shmget(key_t key, size_t size, int shmflg);
函数描述:打开或者创建共享内存
函数参数:
key_t key:键值,用于生成shmid,如果是具有亲缘关系的进程中使用,可以填写0或者IPC_PRIVATE,如果是两个不同的进程之间通信,填写一个非0值或者用ftok()去随机生成一个数值
size_t size :共享内存大小
int shmflg :O_CREAT(没有就创建,有救打开)
O_EXCL (检查是否存在)
返回值:成功返回shmid,失败-1
注意:共享内存与管道最主要区别在于,管道只能在两个进程之间通信,共享内存只要保证key值相同,可以在多个进程内通信.
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc, char *argv[])
{
printf("shmid= %d\n",shmget(0x512,512,IPC_CREAT|0777));
return 0;
}
2)映射共享内存
函数名称:void *shmat(int shmid,const void *addr,int shmflg)
函数描述:将共享内存映射到地址空间里
函数参数:
int shmid:标识符
const void *addr:映射进程空间首地址,可以手动映射也可以自动映射(NULL)
int shmflg:SHM_RDONLY(只读) 0(可读可写)
返回值:成功返回映射后的首地址,失败-1
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc, char *argv[])
{
//创建共享内存
int shmid = shmget(0x512,512,IPC_CREAT|0777);
//进行映射
char * p = (char *)shmat(shmid,NULL,0);
//写数据
*p=10;
char * q = (char *)shmat(shmid,NULL,0);
printf("%d\n",*q);
return 0;
}
不同进程之间:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//创建共享内存
int shmid = shmget(0x512,512,IPC_CREAT|0777);
//进行映射
char * p = (char *)shmat(shmid,NULL,0);
//写数据
*p=10;
while(1);
return 0;
}
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//创建共享内存
int shmid = shmget(0x512,512,IPC_CREAT|0777);
char * q = (char *)shmat(shmid,NULL,0);
printf("%d\n",*q);
while(1);
return 0;
}
3)解除共享内存映射(操作共享内存首地址)
函数名称:int shmdt(const void *addr)
函数描述:
函数参数:
返回值:成功0 失败-1
4)删除共享内存(操作id)
函数名称:int shmctl(int shmid,int cmd,struct shmid_ds *buf)
函数描述:删除共享内存
函数参数:
int cmd:共享内存控制命令。
IPC_STAT:查看共享内存的特性。
IPC_SET:设置共享内存的特性。
IPC_RMID:删除共享内存。
struct shmid_ds *buf:共享内存特性结构体(命令附带参数),不需要则指定为NULL。
返回值:成功0,失败-1
注意:删除共享内存之前必须先解除映射
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//创建共享内存
int shmid = shmget(0x512,512,IPC_CREAT|0777);
//进行映射
char * p = (char *)shmat(shmid,NULL,0);
//写数据
*p=10;
//解除映射
shmdt(p);
while(1);
return 0;
}
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//创建共享内存
int shmid = shmget(0x512,512,IPC_CREAT|0777);
char * q = (char *)shmat(shmid,NULL,0);
printf("%d\n",*q);
shmdt(q);
shmctl(shmid,IPC_RMID,NULL);
while(1);
return 0;
}
5)获取共享内存键值
函数名称: key_t ftok(const *patname, int proj_id)
函数描述:让系统为共享内存分配一个键值
函数参数:
const *patnam:创建key值的路径(任意已存在的文件名),保证路径不容易被删除掉。
proj_id:任意ascll 值(取值范围:0~255)
返回值:得到的key键值(正整数)
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//创建共享内存
int shmid = shmget(ftok("./a.out",2),512,IPC_CREAT|0777);
char * q = (char *)shmat(shmid,NULL,0);
printf("%d\n",*q);
shmdt(q);
shmctl(shmid,IPC_RMID,NULL);
while(1);
return 0;
}
5.消息队列
1).含义:是指通信双方发送接收操作均以消息(消息类型+消息内容)为单位,以消息缓冲区为中间介质
消息队列是消息的链表,存放在内核中并由消息队列标识符表示
消息队列与管道区别:管道先进先出,消息队列中同一消息类型先进先出,不同类型的消息读取不一定先进先出。
管道是基于字节流,消息队列是基于消息。
2).API
创建消息队列:
函数原型:int msgget(key_t key, int msgflg);
参数说明:
Key:键值可以自己定义也可以用ftok函数进行分配。
Msgflg:它可以取下面的几个值:
IPC_CREAT :如果消息队列对象不存在,则创建之,否则则进行打开操作;
IPC_EXCL:和IPC_CREAT 一起使用(用”|”连接),如果消息对象不存在则创建之,否则产生一个错误并返回。int msgget(0x666, IPC_CREAT | IPC_EXCL);
返回值:成功执行时,返回消息队列标识值。失败返回-1,errno被设为以下的某个值 ,有时也会返回0,这个时候也是可以正常使用的。
EACCES:指定的消息队列已存在,但调用进程没有权限访问它,而且不拥有CAP_IPC_OWNER权能。
EEXIST:key指定的消息队列已存在,而msgflg中同时指定IPC_CREAT和IPC_EXCL标志。
ENOENT:key指定的消息队列不存在同时msgflg中不指定IPC_CREAT标志
ENOMEM:需要建立消息队列,但内存不足。
ENOSPC:需要建立消息队列,但已达到系统的最大消息队列容量。
消息队列发送:
函数原型: int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数说明:
msqid: 是消息队列对象的标识符(由msgget()函数得到)
msgp: 指向要发送的消息所在的内存
*注意:发送消息的类型结构体,第一个参数必须是long类型,其他参数自己定义
mtype:代表消息类型
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
char mtext1[1]; /* message data */
char mtext2[1]; /* message data */
};
msgsz: 是要发送信息的长度(字节数),可以用以下的公式计算:
msgsz = sizeof(struct mymsgbuf) - sizeof(long);
msgfl:是控制函数行为的标志,可以取以下的值:0堵塞,忽略标志位;IPC_NOWAIT(非堵塞),如果消息队列已满(在消息队列中,例如函数msgsnd(int msqid, const void* msgp, size_t msgsz, int msgflg);这个函数调用的时候,msgsz最大只能为8192,也就是2的16次方8kb。可以看出这里的msgsz大小限制在一个short型。超过这个大小就会出错,),消息将不被写入队列,控制权返回调用函数的线程。如果不指定这个参数,线程将被阻塞直到消息被可以被写入。
返回值:错误时返回-1,可以打印错误信息;正确返回0
消息队列接收:
函数定义:int msgrcv( int msgid , struct msgbuf* msgp , int msgsz , long msgtyp, int msgflg);
参数:
函数的前三个参数和msgsnd()函数中对应的参数的含义是相同的。第四个参数mtype
指定了函数从队列中所取的消息的类型。函数将从队列中搜索类型与之匹配的消息并将 之返回。不过这里有一个例外。如果mtype 的值是零的话,函数将不做类型检查而自动返 回队列中的最旧的消息。第五个参数依然是是控制函数行为的标志,取值可以是:
0,表示忽略;
IPC_NOWAIT,如果消息队列为空,则返回一个ENOMSG,并将控制权交回调用函数
的进程。如果不指定这个参数,那么进程将被阻塞直到函数可以从队列中得到符合条件 的消息为止。如果一个client 正在等待消息的时候队列被删除,EIDRM 就会被返回。如果 进程在阻塞等待过程中收到了系统的中断信号,EINTR 就会被返回。
MSG_NOERROR,如果函数取得的消息长度大于msgsz,将只返回msgsz 长度的信息,
剩下的部分被丢弃了。如果不指定这个参数,E2BIG 将被返回,而消息则留在队列中不 被取出。
当消息从队列内取出后,相应的消息就从队列中删除了。
msgbuf:结构体,定义如下:
struct msgbuf
{
long mtype ; //信息种类
char mtest[x];//信息内容 ,长度由msgsz指定
}
msgtyp: 信息类型。 取值如下:
msgtyp = 0 ,不分类型,直接返回消息队列中的第一项
msgtyp > 0 ,返回第一项 msgtyp与 msgbuf结构体中的mtype相同的信息
msgtyp <0 , 返回第一项 mtype小于等于msgtyp绝对值的信息
msgflg:取值如下:
IPC_NOWAIT ,不阻塞
IPC_NOERROR ,若信息长度超过参数msgsz,则截断信息而不报错
返回值:
成功时返回所获取信息的长度,失败返回-1,错误信息存于error
删除消息队列
函数原型: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
头文件:
参数:
msgqid:消息队列的识别码。
cmd:使用命令:
IPC_STAT读取消息队列的数据结构msqid_ds,并将其存储在b u f指定的地址中。
IPC_SET设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数。
IPC_RMID从系统内核中移走消息队列。(这时 struct msqid_ds *buf:NULL)
调用以后就进行删除
返回值:0成功 1失败
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<stdio.h>
#include<string.h>
typedef struct msgbuf
{
long mtype;
char text[30];
}MSG;
int main(int argv,char *argc[])
{
MSG msgbuff;
MSG msgrec;
int msgid = msgget(ftok("./1.out",55),IPC_CREAT|0777);
if(msgid == -1)
{
perror("msgget");
return 0;
}
msgbuff.mtype = 1;
strcpy(msgbuff.text,"nihao");
int flag_send = msgsnd(msgid,(const void *)&msgbuff,sizeof(MSG)-sizeof(long),0);
if(flag_send != 0)
{
perror("msgsnd");
return 0;
}
//是否强转
int num = msgrcv(msgid,(MSG *)&msgrec,sizeof(MSG)-sizeof(long),1,0);
printf("num = %d\n",num);
printf("msgrcv = %s\n",msgrec.text);
int flag_del = msgctl(msgid,IPC_RMID,NULL);
if(flag_del == 0)
{
printf("删除成功\n");
}
return 0;
}