无名管道pipe是一种半双工的通信方式,不会创建一个实际的文件,相当于只是运行时在linux内核下有一个缓冲区来进行读写数据,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
#include
int pipe(int pipefd[2]);
用pipe函数来创建一个管道,参数只有一个大小为2的一个整形数组,后续的读写操作的文件描述符就是根据这两个数组里的数,fd[0]是读,fd[1]是写。因为是半双工的,所以只能一端读一端写,而在某一端进行了某个操作时,相应的需要关闭掉另一种操作,这里可以举例说明,函数的返回值如果是-1则表示管道创建失败。
#include
#include
#include
#include
int main(){
int fd[2];
pid_t pid;
int data;
if(pipe(fd)==-1){
printf("创建管道失败\n");
}
printf("请输入一个数字,1是父进程发数据,0是子进程发数据\n");
scanf("%d",&data);
pid=fork();
if(pid<0){
printf("创建进程失败\n");
}
else if(pid>0){
printf("这是父进程\n");
if(data==1){
close(fd[0]);
write(fd[1],"message from father",strlen("message from father"));
}
else if(data==0){
close(fd[1]);
char readbuf[20]={0};
read(fd[0],readbuf,sizeof(readbuf));
printf("%s\n",readbuf);
}
}
else {
printf("这是子进程\n");
if(data==0){
close(fd[0]);
write(fd[1],"message from child",strlen("message from child"));
}
else if(data==1){
close(fd[1]);
char readbuf[20]={0};
read(fd[0],readbuf,sizeof(readbuf));
printf("%s\n",readbuf);
}
}
return 0;
}
命名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信,并且是实际的创建了一个文件。
#include
#include
int mkfifo(const char *pathname, mode_t mode);
有名管道使用mkfifo创建,pathname是管道的名字,mode是文件的属性,返回值如果是-1,则表示创建失败,失败的原因有可能会是管道已经存在导致的失败,所以在判断的时候可以加EEXIST来辅助判断是否是其他原因导致的失败,EEXIST是当管道存在时的错误信息,因为是半双工的,所以一边的open是WRONLY,另一边是RDONLY,在没接受到数据之前读端会一直阻塞知道写端发送数据,这里可以举例说明。
①读端
#include
#include
#include
#include
#include
#include
#include
#include
int main(){
if(mkfifo("./pipe",0600)==-1 && errno!=EEXIST){
printf("管道创建失败\n");
perror("why");
}
int fd=open("./pipe",O_RDONLY);
char readBuf[128]={0};
int n_read;
while(1){
n_read=read(fd,readBuf,sizeof(readBuf));
printf("读了%ld大小的数据,内容是%s\n",strlen(readBuf),readBuf);
}
close(fd);
return 0;
}
②写端
#include
#include
#include
#include
#include
#include
#include
#include
int main(){
int fd=open("./pipe",O_WRONLY);
char readBuf[128]={0};
FILE *fp;
int n_write;
while(1){
fp=popen("date","r");
fread(readBuf,128,1,fp);
n_write=write(fd,readBuf,strlen(readBuf));
printf("写了%d大小的数据,内容是%s\n",n_write,readBuf);
pclose(fp);
sleep(3);
}
close(fd);
return 0;
}
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点,而且本质上就是一个存在于内核中的一个链表,每一个节点都是一个信息。
#include
#include
#include
struct msgbuf {
long mtype;
char mtext[128];
};
int msgget(key_t key, int msgflg);
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);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
结构体msgbuf是用来存放信息的,mtype是消息的类型,mtext是存放正文的
①msgget创建消息队列
key值是指某一条队列,可以自己指定,也可以用ftok函数来生成key值。
msgflag为所需要的操作和权限。值可以有 IPC_CREAT | IPC_EXCL(如果不存在key值的消息队列,则创建消息队列,并返回一个消息队列ID。如果存在,则返回-1),IPC_CREAT(如果不存在key值的消息队列,且权限不为0,则创建消息队列,并返回一个消息队列ID。如果存在,则直接返回消息队列ID)。
②msgsend发送消息
msgqid是msgget成功返回的ID值。
msgp:消息的结构体指针
msgsz:消息正文的大小
msgflg:可以选择0和IPC_NOWAIT,选择0时为当消息队列满时会阻塞,而IPC_NOWAIT为当消息队列满时会立即出错
成功返回0,失败返回-1
③msgrcv接收消息
msgid:为msgget成功返回的ID值
msgp:消息的结构体指针
msgsz:消息正文的大小
msgtyp:消息的类型
0:获取消息队列中的第一条消息
> 0:获取类型为 msgtyp 的第一条消息。
< 0: 获取队列中消息类型值小于等于type绝对值的消息,如果这种消息有若干个,则取类型值最小的消息
msgflg:0时,阻塞式接收消息,没有该类型的消息一直阻塞等待
IPC_NOWAIT时,不等待立即获取消息,如果没有返回条件的消息调用立即返回
IPC_EXCEPT时,与msgtype配合使用返回队列中第一个类型不为msgtype的消息
成功返回消息的长度,失败返回-1
④msgctl控制消息队列
msqid:消息队列标识符
cmd:控制指令
IPC_RMID:可以删除消息队列
IPC_STAT:获得msgid的消息队列头数据到buf中
IPC_SET:设置消息队列的属性,要设置的属性需先存储在buf中
buf:消息队列管理结构体,如果不关心一些信息,可以设置为NULL
成功返回0,失败返回-1
这里可以举例说明,这里写的是两个进程都可以收发消息
#include
#include
#include
#include
#include
#include
/* 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;
char mtext[128];
};
int main(){
struct msgbuf revBuf;
struct msgbuf sendBuf={777,"i am fine"};
key_t key;
key=ftok("..",6);
int msgId;
msgId=msgget(key,IPC_CREAT|0777);
if(msgId==-1){
printf("id号获取失败\n");
}
msgrcv(msgId,&revBuf,sizeof(revBuf.mtext),666,0);
printf("接受成功\n");
printf("接收到的消息是%s\n",revBuf.mtext);
msgsnd(msgId,&sendBuf,sizeof(sendBuf.mtext),0);
printf("发送完成\n");
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
#include
#include
#include
#include
#include
#include
/* 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;
char mtext[128];
};
int main(){
struct msgbuf sendBuf={666,"how are you"};
key_t key;
key=ftok("..",6);
int msgId;
msgId=msgget(key,IPC_CREAT|0777);
if(msgId==-1){
printf("id号获取失败\n");
}
msgsnd(msgId,&sendBuf,sizeof(sendBuf.mtext),0);
printf("发送完成\n");
memset(sendBuf.mtext,0,128);
msgrcv(msgId,&sendBuf,sizeof(sendBuf.mtext),777,0);
printf("接受成功\n");
printf("接收到的消息是%s\n",sendBuf.mtext);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
#include
#include
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
①shmget获取共享内存的ID号
key可以由ftok函数来生成
size为需要申请共享内存的大小。在操作系统中,申请内存的最小单位为页,一页是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍
shmflg操作的权限,可以IPC_CREAT创建
成功返回共享内存id,失败返回-1
②shmat:共享内存的映射
shmaddr:如果等于0,则由linux内核自动选择一个可以用的地址来进行映射
shmflg:如果等于零则可读可写,也可以用SHME_RDONLY设置只读
成功返回内存的指针,错误返回-1
③shmdt 解除映射,直接传递内存指针进来
④shctl 共享内存的操作
cmd:操作的宏,一般用来销毁共享内存,用IPC_RMID
buf:共享内存cmd操作后返回的一些信息储存到该结构体中,如果不关心可以用NULL
举例说明
写端
#include
#include
#include
#include
#include
#include
#include
int main(){
key_t key;
char *shmaddr;
key=ftok(".",6);
int cnt=0;
int shmId=shmget(key,4096,IPC_CREAT|0666);
shmaddr=shmat(shmId,0,0);
sleep(2);
while(cnt<5){
shmaddr[cnt]='A'+cnt;
sleep(1);
cnt++;
}
shmdt(shmaddr);
sleep(2);
shmctl(shmId,IPC_RMID,NULL);
return 0;
}
读端
#include
#include
#include
#include
#include
#include
#include
int main(){
key_t key;
char *shmaddr;
key=ftok(".",6);
int cnt=0;
int shmId=shmget(key,4096,0600);
shmaddr=shmat(shmId,0,0);
while(cnt<5){
printf("%s\n",shmaddr);
sleep(1);
cnt++;
}
shmdt(shmaddr);
return 0;
}
信号量和其他IPC结构不同,它是一个计数器,可以用来控制多个进程对临界资源(当该资源被某个进程访问时,其他进程只能等待访问完成)的访问。它常作为一种锁机制,防止某进程正在访问临界资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
#include
int semget(key_t key, int num_sems, int sem_flags);
int semop(int semid, struct sembuf *sops, size_t nsops);
int semctl(int semid, int sem_num, int cmd, ...);
①semget创建或获取一个信号量组
key:键值可用ftok生成
num_sems:信号量组中信号量的个数
sem_flags:可操作的权限,若没有则IPC_CREAT创建
若成功返回信号量集ID,失败返回-1
②semop对信号量组进行操作,可以改变信号量的值
semid:信号量组id
sops:结构体指针,实质是信号量结构体数组指针,结构体内有三个参数
sem_num 信号量的编号,只有一个信号量时,默认为0,多个时需要根据实际情况赋值
sem_op 对信号量的操作,当v操作后也就是访问完资源后+1以提示其他进程可以访问该临界资源了,当 p操作后-1提示其他进程该临界资源有其他进程在访问
sem_flg IPC_NOWAIT和SEM_UNDO,IPC_NOWAIT不等待,一般会用SEM_UNDO,该参数用后,在进程结束之后会自动解除锁
成功返回0,失败返回-1
③semctl:控制信号量的相关信息
有三个或者四个参数,取决于cmd,若有四个参数,第四个参数是一个联合体,sem_num是在信号量组里的哪一个信号量,若要设置信号量的锁,可以使cmd为SETVAL ,然后在联合体里把val的值设置成是否有进程访问临界资源
以下举例说明,写的是父子进程访问资源的例子
#include
#include
#include
#include
#include
#include
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void pGet(int semId){
struct sembuf set;
set.sem_num=0;
set.sem_op=-1;
set.sem_flg=SEM_UNDO;
if(semop(semId, &set, 1)==-1){
printf("操作失败\n");
}
}
void vPut(int semId){
struct sembuf set;
set.sem_num=0;
set.sem_op=1;
set.sem_flg=SEM_UNDO;
if(semop(semId, &set, 1)==-1){
printf("操作失败\n");
}
}
int main(){
key_t key;
key=ftok(".",66);
union semun set;
int semId=semget(key, 1, IPC_CREAT|0666);
if(semId==-1){
printf("获取失败\n");
}
set.val=0;
semctl(semId, 0,SETVAL,set);
int pid=fork();
if(pid>0){
pGet(semId);
printf("父进程\n");
vPut(semId);
}
if(pid==0){
printf("我就算等了三秒,你没有锁也不能访问\n");
sleep(3);
printf("子进程\n");
vPut(semId);
}
if(pid<0){
printf("fork失败\n");
}
return 0;
}
这里初始化时sem_num设置的是0也就是某个进程在访问资源的状态,只有解除此种状态也就是v操作后才能开始运行
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生,也就是接受某种信号,该信号可以携带一些消息给目标进程
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal函数用于对信号的操作,signum是信号编号,可以用kill -l指令来看linux下有哪些信号
kill -编号 pid号 可以对该进程发送信号
handler为函数指针,定义一个该类型的函数,参数是整形也就是信号编号,在此函数中写出当收到对应信号时会做什么操作,这个是信号的捕获,如果想忽略对应的信号,可以把第二个值设置为SIG_IGN,而SIG_KILL,SIG_STOP是不能被捕获以及忽略的,因为如果可以,那么该程序就是没有能够被控制的方法的
举例说明
#include
#include
void handler(int signum){
switch(signum){
case 2:
printf("signum=%d\n",signum);
printf("就不退出\n");
case 9:
printf("signum=%d\n",signum);
printf("就不退出\n");
}
}
int main(){
signal(SIGINT,SIG_IGN);//①
signal(SIGKILL,handler);//②
while(1);
return 0;
}
①不能被捕获 ②程序会被杀死
除了用kill -9 -pid之外还可以自己写一个程序来实现发送信号的功能,使用了kill函数
#include
#include
int kill(pid_t pid, int sig);
pid是目的进程的进程号,sig是信号编号,例子如下
#include
#include
#include
#include
//int kill(pid_t pid, int sig);
int main(int argv,char **argc){
pid_t pid=atoi(argc[2]);
int sig=atoi(argc[1]);
kill(pid,sig);
printf("发送信号成功\n");
return 0;
}
以上是发送信号但不携带消息的,还有可以发送信号时可以携带信息的一套函数
#include
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
①sigaction是对信号处理的函数
signum参数指出要捕获的信号类型
act参数指定新的信号处理方式
oldact参数输出先前信号的处理方式,如果不为NULL的话
②sigaction结构体参数
sa_handler :只带一个int类型的参数的回调函数,效果和signal函数一样
sa_sigaction: int是信号编号,siginfo_t是结构体指针,存放着发送过来的一些数据,void参数一般可用来判断结构体指针中是否为空
sa_mask:用来指定在信号处理函数执行期间需要被屏蔽的信号
sa_flags用于指定信号处理的行为
SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。
sa_restorer:不用使用
③sigqueue:另一进程发送信息时用到的函数
pid为目标进程pid号,sig为要发送的信号编号,value包含了需要发送的内容的联合体
④sigval联合体参数:sival_int就是发送信号时附带的参数
这里举例说明
①接受端
#include
#include
#include
#include
#include
#include
void handler(int signum,siginfo_t *rec,void *text){
printf("信号是%d\n",signum);
if(text!=NULL){ // 判断内容是否为空
printf("发送的内容是:%d\n",rec->si_int);
printf("发送信息的pid号是:%d\n",rec->si_pid);
}
}
int main(){
struct sigaction rec;
printf("进程pid号是:%d\n",getpid());
rec.sa_sigaction=handler;
rec.sa_flags=SA_SIGINFO;
sigaction(SIGUSR1,&rec,NULL);
printf("recive ok\n");
while(1);
return 0;
}
②发送端
#include
#include
#include
#include
int main(int argc,char **argv){
int signum=atoi(argv[1]);
int pid=atoi(argv[2]);
union sigval value;
value.sival_int=100;
sigqueue(pid,signum,value);
printf("send success\n");
return 0;
}