备案 网站名称,个人电商怎么做,写作网站保底和全勤的区别,买域名在哪里买目录Linux进程间通信概述1.管道无名管道(pipe)有名管道(fifo)2.消息队列(msg)消息队列的通信原理消息队列相关api消息队列收发数据键值生成消息队列移除3.共享内存(shm)4.信号(sig)信号概述信号编程#xff08;入门#xff09;信号携带消息#xff08;高级#xff09;5.信号…
目录Linux进程间通信概述1.管道无名管道(pipe)有名管道(fifo)2.消息队列(msg)消息队列的通信原理消息队列相关api消息队列收发数据键值生成消息队列移除3.共享内存(shm)4.信号(sig)信号概述信号编程入门信号携带消息高级5.信号量(sem)P、V操作(类似信号量lock、unlock)6.通信方式总结Linux进程间通信概述
进程间通信IPCInterProcess Communication是指在不同进程之间传播或交换信息。 IPC 的方式通常有管道包括无名管道和命名管道、消息队列、共享内存、信号、信号量、Socket、Streams 等。其中 Socket 和 Streams 支持不同主机上的两个进程 IPC。
1.管道
无名管道(pipe)
管道通常指无名管道是 UNIX 系统IPC最古老的形式。
特点
1、它是半双工的即数据只能在一个方向上流动具有固定的读端fd[0])和写端(fd[1])。
2、它只能用于具有亲缘关系的进程之间的通信也是父子进程或者兄弟进程之间。
3、它可以看成是一种特殊的文件对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件并不属于其他任何文件系统并且只存在于内存中。
4、管道中的数据读走就没了。
原型
1 #include unistd.h
2 int pipe(int fd[2]); // 返回值若成功返回0失败返回-1当一个管道建立时它会创建两个文件描述符fd[0]为读而打开fd[1]为写而打开。要关闭管道只需将这两个文件描述符关闭即可。如下图 例子 创建管道后在父进程中写入在子进程中读
#include stdio.h
#include unistd.h
#include string.h
#include stdlib.h
#include sys/types.h
#include sys/wait.h
int main()
{int fd[2];pid_t pid;char readBuf[128];int nwrite;int nread;// int pipe(int pipefd[2]);if(pipe(fd) -1){printf(创建管道失败\n);}pidfork();//创建子进程if(pid -1){printf(创建子进程失败\n);//1.在父进程写入}else if(pid 0){printf(这是父进程\n);close(fd[0]);//关闭读文件//ssize_t write(int fd, const void *buf, size_t count);nwritewrite(fd[1],hello from father process,strlen(hello from father process));wait(NULL);//2.在子进程读}else{printf(这是子进程\n);close(fd[1]);//ssize_t read(int fd, void *buf, size_t count);nreadread(fd[0],readBuf,128);//如果度端没有内容会阻塞等待printf(来自父进程的写入内容是%s\n,readBuf);exit(0);}return 0;
}注意 管道为半双工通信读和写操作在同一时间内只能进行一个所以在读的时候要关闭写端写的时候关闭读端。 运行结果 有名管道(fifo)
FIFO也称为命名管道它是一种文件类型。
1、特点 FIFO可以在无关的进程之间交换数据与无名管道不同。
FIFO有路径名与之相关联它以一种特殊设备文件形式存在于文件系统中。
2、原型
1 #include sys/stat.h
2 // 返回值成功返回0出错返回-1
3 int mkfifo(const char *pathname, mode_t mode);其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时是否设置非阻塞标志O_NONBLOCK的区别
1、若没有指定O_NONBLOCK默认只读 open 要阻塞到某个其他进程为写而打开此 FIFO参照下面的例子。类似的只写 open 要阻塞到某个其他进程为读而打开它。
2、若指定了O_NONBLOCK则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO其errno置ENXIO。
创建管道例子
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include errno.hint main()
{//int mkfifo(const char *pathname, mode_t mode);if(mkfifo(./file,0600) -1 errno ! EEXIST){printf(mkfifo创建失败\n);perror(why:);}else{if(errno EEXIST){printf(文件已经存在\n);}printf(mkfifo创建成功\n);}return 0;
}使用有名管道通信的例子
read代码
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include string.h
int main()
{int nread;mkfifo(./file,0600);char buf[1024] {0};int fd open(./file,O_RDONLY);//若没有指定O_NONBLOCK默认只读 open 要阻塞到某个其他进程为写而打开此 FIFOprintf(open success\n);while(1){nread read(fd,buf,1024);printf(read %d byte ,neirong:%s\n,nread,buf);}return 0;
}write代码
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include string.h
int main()
{mkfifo(./file,0600);char *str this is a fifo demo!;int fd open(./file,O_WRONLY);printf(open success\n);while(1){write(fd,str,strlen(str));sleep(1);}return 0;
}运行read会阻塞一直到运行write后read才会继续往下执行。
个人感觉很像文件操作不过不用进行lseek等操作内核知道这是管道操作数据读出时管道数据清除。
2.消息队列(msg)
消息队列的通信原理
消息队列是消息的链接表(结构体)存放在内核中。一个消息队列由一个标识符即队列ID来标识。
特点 消息队列是面向记录的其中的消息具有特定的格式以及特定的优先级。
消息队列独立于发送与接收进程。进程终止时消息队列及其内容并不会被删除Linux内核机制进行管理。
消息队列可以实现消息的随机查询链表机制就是这样的,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
消息队列的通信原理理解图
消息队列相关api
1 #include sys/msg.h2 // 创建或打开消息队列成功返回队列ID失败返回-1
3 int msgget(key_t key, int flag);4 // 发送消息成功返回0失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);6 // 接收消息成功返回消息数据的长度失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);8 // 控制消息队列成功返回0失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);在以下两种情况下msgget将创建一个新的消息队列
1、如果没有与键值key相对应的消息队列并且flag中包含了IPC_CREAT标志位。 2、key参数为IPC_PRIVATE。
函数msgrcv在读取消息队列时type参数有下面几种情况
type 0返回队列中的第一个消息 type 0返回队列中消息类型为 type 的第一个消息 type 0返回队列中消息类型值小于或等于 type 绝对值的消息如果有多个则取类型值最小的消息。 可以看出type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。其他的参数解释请自行Google之
消息队列收发数据
readmsg
#include stdio.h
#include sys/types.h
#include sys/ipc.h
#include sys/msg.h
struct msgbuf {long mtype; /* message type, must be 0 */char mtext[128]; /* message data */
};int main()
{struct msgbuf readBuf;//int msgget(key_t key, int msgflg);int msgIdmsgget(0x1234,IPC_CREAT|0777);//如果存在就打开没有则创建权限是可读刻写可执行if(msgId -1){printf(创建失败\n);}//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);msgrcv(msgId,readBuf,sizeof(readBuf.mtext),888,0);//0是阻塞方式printf(读取来自队列的内容%s\n,readBuf.mtext);return 0;
}sendmsg
#include stdio.h
#include sys/types.h
#include sys/ipc.h
#include sys/msg.h
#include string.h
struct msgbuf {long mtype; /* message type, must be 0 */char mtext[128]; /* message data */
};int main()
{struct msgbuf sendBuf{888,this message from que};;//int msgget(key_t key, int msgflg);int msgIdmsgget(0x1234,IPC_CREAT|0777);//如果存在就打开没有则创建权限是可读刻写可执行if(msgId -1){printf(创建失败\n);}//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);msgsnd(msgId,sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞return 0;
}运行结果 先运行readmsg阻塞在那等待发送消息
实现双方消息通信都能发送和接收
readmsg
#include stdio.h
#include sys/types.h
#include sys/ipc.h
#include sys/msg.h
#include string.h
struct msgbuf {long mtype; /* message type, must be 0 */char mtext[128]; /* message data */
};int main()
{struct msgbuf readBuf;struct msgbuf sendBuf{998,thank you que, i have received};//int msgget(key_t key, int msgflg);int msgIdmsgget(0x1234,IPC_CREAT|0777);//如果存在就打开没有则创建权限是可读刻写可执行if(msgId -1){printf(创建失败\n);}//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);msgrcv(msgId,readBuf,sizeof(readBuf.mtext),888,0);//0是非阻塞方式printf(读取来自队列的内容%s\n,readBuf.mtext);msgsnd(msgId,sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf(发送完毕\n);return 0;
}sendmsg
#include stdio.h
#include sys/types.h
#include sys/ipc.h
#include sys/msg.h
#include string.h
struct msgbuf {long mtype; /* message type, must be 0 */char mtext[128]; /* message data */
};int main()
{struct msgbuf sendBuf{888,this message from que};struct msgbuf readBuf;//int msgget(key_t key, int msgflg);int msgIdmsgget(0x1234,IPC_CREAT|0777);//如果存在就打开没有则创建权限是可读刻写可执行if(msgId -1){printf(创建失败\n);}//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);msgsnd(msgId,sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf(发送完毕\n);msgrcv(msgId,readBuf,sizeof(readBuf.mtext),998,0);//0是非阻塞方式printf(读取来自队列的内容%s\n,readBuf.mtext);return 0;
}键值生成
readmsg
#include stdio.h
#include sys/types.h
#include sys/ipc.h
#include sys/msg.h
#include string.h
struct msgbuf {long mtype; /* message type, must be 0 */char mtext[128]; /* message data */
};int main()
{struct msgbuf readBuf;struct msgbuf sendBuf{998,thank you que, i have received};//key_t ftok(const char *pathname, int proj_id);key_t key;keyftok(.,z);//.当前路径 proj_id典型的用法是将一个ASCII码作为proj_id随便取值都可以 内核这两者组合出一个键值printf(key %x\n,key);//用16进制输出//int msgget(key_t key, int msgflg);int msgIdmsgget(key,IPC_CREAT|0777);//如果存在就打开没有则创建权限是可读刻写可执行if(msgId -1){printf(创建失败\n);}//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);msgrcv(msgId,readBuf,sizeof(readBuf.mtext),888,0);//0是非阻塞方式printf(读取来自队列的内容%s\n,readBuf.mtext);msgsnd(msgId,sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf(发送完毕\n);return 0;
}sendmsg
#include stdio.h
#include sys/types.h
#include sys/ipc.h
#include sys/msg.h
#include string.h
struct msgbuf {long mtype; /* message type, must be 0 */char mtext[128]; /* message data */
};int main()
{struct msgbuf sendBuf{888,this message from que};struct msgbuf readBuf;key_t key;keyftok(.,z);//.当前路径proj_id典型的用法是将一个ASCII码作为proj_idprintf(key %x\n,key);//用16进制输出//int msgget(key_t key, int msgflg);int msgIdmsgget(key,IPC_CREAT|0777);//如果存在就打开没有则创建权限是可读刻写可执行if(msgId -1){printf(创建失败\n);}//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);msgsnd(msgId,sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf(发送完毕\n);msgrcv(msgId,readBuf,sizeof(readBuf.mtext),998,0);//0是非阻塞方式printf(读取来自队列的内容%s\n,readBuf.mtext);return 0;
}消息队列移除
int msgctl(int msqid, int cmd, struct msqid_ds *buf);struct msqid_ds *buf这里我们一般写NULL CMD类型参照下图箭头指的就是i用的最多的把消息队列生成的链表在内核里移除 例如 readmsg
#include stdio.h
#include sys/types.h
#include sys/ipc.h
#include sys/msg.h
#include string.h
struct msgbuf {long mtype; /* message type, must be 0 */char mtext[128]; /* message data */
};int main()
{struct msgbuf readBuf;struct msgbuf sendBuf{998,thank you que, i have received};//key_t ftok(const char *pathname, int proj_id);key_t key;keyftok(.,z);//.当前路径proj_id典型的用法是将一个ASCII码作为proj_idprintf(key %x\n,key);//用16进制输出//int msgget(key_t key, int msgflg);int msgIdmsgget(key,IPC_CREAT|0777);//如果存在就打开没有则创建权限是可读刻写可执行if(msgId -1){printf(创建失败\n);}//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);msgrcv(msgId,readBuf,sizeof(readBuf.mtext),888,0);//0是非阻塞方式printf(读取来自队列的内容%s\n,readBuf.mtext);msgsnd(msgId,sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf(发送完毕\n);msgctl(msgId,IPC_RMID,NULL);return 0;
}sendmsg
#include stdio.h
#include sys/types.h
#include sys/ipc.h
#include sys/msg.h
#include string.h
struct msgbuf {long mtype; /* message type, must be 0 */char mtext[128]; /* message data */
};int main()
{struct msgbuf sendBuf{888,this message from que};struct msgbuf readBuf;key_t key;keyftok(.,z);//.当前路径proj_id典型的用法是将一个ASCII码作为proj_idprintf(key %x\n,key);//用16进制输出//int msgget(key_t key, int msgflg);int msgIdmsgget(key,IPC_CREAT|0777);//如果存在就打开没有则创建权限是可读刻写可执行if(msgId -1){printf(创建失败\n);}//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);msgsnd(msgId,sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞printf(发送完毕\n);msgrcv(msgId,readBuf,sizeof(readBuf.mtext),998,0);//0是非阻塞方式printf(读取来自队列的内容%s\n,readBuf.mtext);//int msgctl(int msqid, int cmd, struct msqid_ds *buf);msgctl(msgId,IPC_RMID,NULL);return 0;
}3.共享内存(shm)
共享内存Shared Memory指两个或多个进程共享一个给定的存储区。
特点 共享内存是最快的一种 IPC因为进程是直接对内存进行存取。 因为多个进程可以同时操作所以需要进行同步。
信号量共享内存通常结合在一起使用最后一节讲解信号量用来同步对共享内存的访问。
和消息队列的区别
消息队列就像两个人聊天一方要将要说的话写在一张纸上放入箱子里另外一个人去箱子里取出来阅读。这个箱子消息队列不会自行销毁要调用msgctl才可以。
共享内存就好像两个学生在上课的时候由于不能说话就只好拿一个本子来聊天这个本子就相当于共享的内存双方可以同时看到纸上的内容。比消息队列效率高。调用shmctl删除共享内存。 相关api原型
1 #include sys/shm.h2 // 创建或获取一个共享内存成功返回共享内存ID失败返回-1
3 int shmget(key_t key, size_t size, int flag);4 // 连接共享内存到当前进程的地址空间也叫挂载、映射成功返回指向共享内存的指针失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);6 // 断开与共享内存的连接成功返回0失败返回-1
//注意这并不是从系统中删除该共享内存只是当前进程不能再访问该共享内存而已。
7 int shmdt(void *addr); 8 // 控制共享内存的相关信息成功返回0失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf); 参数说明 key为ftok生成的键值 size为共享内存的长度以字节为单位 当用shmget函数创建一段共享内存时必须指定其 size而如果引用一个已存在的共享内存则将 size 指定为0 。 flag为所需要的操作和权限可以用来创建一个共享存储空间并返回一个标识符或者获得一个共享标识符。 ----flag的值为IPC_CREAT如果不存在key值的共享存储空间且权限不为0则创建共享存储空间并返回一个共享存储标识符。如果存在则直接返回共享存储标识符。 ----flag的值为 IPC_CREAT |IPC_EXCL如果不存在key值的共享存储空间且权限不为0则创建共享存储空间并返回一个共享存储标识符。如果存在则产生错误。 cmd 常用的是IPC_RMID从系统中删除该共享内存 例子 创建共享内存并写入数据
#include stdio.h
#include sys/ipc.h
#include sys/shm.h
#include stdlib.h
#include string.h
#include unistd.hint main()
{key_t key;char *shmaddr;// key_t ftok(const char *pathname, int proj_id);keyftok(.,2);printf(key %x\n,key);//int shmget(key_t key, size_t size, int shmflg);//创建或获取一个共享内存成功返回共享内存ID失败返回-1int shmIdshmget(key,1024*4,IPC_CREAT|0666);//共享内存大小必须以字节为单位if(shmId -1){printf(创建共享内存失败\n);exit(-1);//异常退出}//void *shmat(int shmid, const void *shmaddr, int shmflg);//连接共享内存到当前进程的地址空间成功返回指向共享内存的指针失败返回-1shmaddrshmat(shmId,0,0);//挂载映射如果引用一个已存在的共享内存上方改革创建则将 size 指定为0 printf(shmat ok\n);strcpy(shmaddr,hello sharemessage);sleep(5);//int shmdt(const void *shmaddr);// 断开与共享内存的连接shmdt(shmaddr);//int shmctl(int shmid, int cmd, struct shmid_ds *buf);//控制共享内存的相关信息成功返回0失败返回-1shmctl(shmId,IPC_RMID,0);//IPC_RMID删除消息队列printf(退出\n);return 0;
}读共享内存的内容
#include stdio.h
#include sys/ipc.h
#include sys/shm.h
#include stdlib.hint main()
{key_t key;char *shmaddr;// key_t ftok(const char *pathname, int proj_id);keyftok(.,2);printf(key %x\n,key);//int shmget(key_t key, size_t size, int shmflg);//创建或获取一个共享内存成功返回共享内存ID失败返回-1int shmIdshmget(key,1024*4,0);//只要打开就行不必创建if(shmId -1){printf(创建共享内存失败\n);exit(-1);//异常退出}//void *shmat(int shmid, const void *shmaddr, int shmflg);//连接共享内存到当前进程的地址空间成功返回指向共享内存的指针失败返回-1shmaddrshmat(shmId,0,0);//挂载映射如果引用一个已存在的共享内存,则将 size 指定为0 printf(shmat ok\n);printf(内容是%s\n,shmaddr);//int shmdt(const void *shmaddr);//断开与共享内存的连接shmdt(shmaddr);return 0;
}4.信号(sig)
本节参照博文https://www.jianshu.com/p/f445bfeea40a
信号概述
1.信号对Linux来说就是软中断与单片机的硬件中断串口类似。如在linux中输入 ctrlc 来停止一个程序
2.信号的名字与编号可在linux中通过 kill -l 查询Linux系统一共有64个信号编号1-64。不存在0信号0信号有特殊的应用在系统级的应用中被占用 部分信号的说明
2)SIGINTctrlc 终止信号
3)SIGQUITctrl\ 终止信号
20)SIGTSTP:ctrlz 暂停信号
26)SIGALRM闹钟信号 收到此信号后定时结束结束进程
17)SIGCHLD子进程状态改变父进程收到信号
9)SIGKILL杀死信号
3.信号处理的三种方式忽略捕捉和默认动作
1、忽略就跟字面意思一样忽略掉它注意SIGKILL,SIGSTOP不能被忽略
2、捕捉就是一些信号处理的函数然后让这个函数告诉内核当信号产生时内核调用该函数实现某种信号的处理
3、默认动作每个信号都有其对应的默认的处理动作当触发了某种信号系统就会立刻去执行。
4.信号的使用 其实对于常用的 kill 命令就是一个发送信号的工具kill -9 PID或者使用命令kill -SIGKILL PID 二者作用一样来杀死进程。比如我在后台运行了一个 top 工具通过 ps 命令可以查看他的 PID通过 kill -9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称可以发现9对应的是 9) SIGKILL正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。 对于信号来说最大的意义不是为了杀死信号而是实现一些异步通讯即信号处理方式第二种–捕捉的手段那么如何来自定义信号的处理函数呢
信号编程入门 信号处理函数的注册(信号绑定)signal (入门),sigaction(高级可携带信号) 信号处理发送函数 kill 入门, sigqueue高级可携带信号 信号绑定
#include stdio.h
#include signal.h// typedef定义了一种类型sighandler_t//typedef void (*sighandler_t)(int);//函数指针(指向函数的指针)无返回值传入参数为一个int型//sighandler_t signal(int signum, sighandler_t handler);
// 返回这种类型 带有_t表示结构体 函数指针变量
void handler(int signum){switch(signum){case 2:printf(get SIGINT,signum%d\n,signum);break;case 9:printf(get SIGKILL,signum%d\n,signum);break;case 10:printf(get SIGUSR1,signum%d\n,signum);break;}printf(never quit\n);
}int main(){//函数指针 指向函数signal(SIGINT,handler);//捕捉信号 SIGINT是ctrl c的指令signal(SIGKILL,handler);signal(SIGUSR1,handler);for(;;);//等于while(1);return 0;
}编译运行在键盘上输入 ctrlc结果为 信号发送杀死程序的信号 SIGKILL
#include stdio.h
#include signal.h
#include sys/types.h
#include stdlib.h//typedef void (*sighandler_t)(int);//sighandler_t signal(int signum, sighandler_t handler);int main(int argc,char **argv)
{int signum;int pid;char cmd[128]{0};signumatoi(argv[1]);//将字符串str转换成一个整数并返回结果pidatoi(argv[2]);printf(signum%d pid%d\n,signum,pid);// int kill(pid_t pid, int sig);//方法一//kill(pid,signum); 发送信号x//方法二 system调用脚本sprintf(cmd,kill -%d %d,signum,pid);system(cmd);printf(发送指令成功\n);return 0;
} 运行结果 信号的忽略
signal(SIGINT,SIG_IGN)//可以忽略掉ctrl c 的信号信号携带消息高级
信号携带消息思路
发信号
1.用什么发 sigqueue() 2.怎么发消息
#include signal.h//发给谁 发什么信号(比如上面讲的信号编号9 10等)
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval { //发送的消息接收端也是用的这个联合体 int sival_int;//发送整型void *sival_ptr;//发送字符串};收信号
1.用什么绑定函数以及收到信号如何处理动作sigaction() 2.如何读出消息
#include signal.h
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//sigactio(),会依照参数signum指定的信号编号来设置该信号的处理函数
//const struct sigaction *act,你要做什么
//struct sigaction *oldact是否备份不备份用NULLstruct sigaction { 凡是带有_t说明是个结构体void (*sa_handler)(int); //信号处理程序不接受额外数据和signal()的参数handler一样了 SIG_IGN 为忽略SIG_DFL 为默认动作void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序能够接受额外数据和sigqueue配合使用sigset_t sa_mask;//阻塞关键字的信号集(默认阻塞)可以再调用捕捉函数之前把信号添加到信号阻塞字信号捕捉函数返回之前恢复为原先的值。int sa_flags;//配置为SA_SIGINFO这个宏表示能够接受数据};
//回调函数句柄sa_handler、sa_sigaction只能任选其一siginfo_t {int si_signo; /* Signal number */int si_errno; /* An errno value */int si_code; /* Signal code */int si_trapno; /* Trap number that causedhardware-generated signal(unused on most architectures) */pid_t si_pid; /* Sending process ID */谁发的uid_t si_uid; /* Real user ID of sending process */int si_status; /* Exit value or signal */clock_t si_utime; /* User time consumed */clock_t si_stime; /* System time consumed */sigval_t si_value; /* Signal value */接收的数据 是个联合体 信号发送函数使用这个联合体int si_int; /* POSIX.1b signal */数据void *si_ptr; /* POSIX.1b signal */int si_overrun; /* Timer overrun count; POSIX.1b timers */int si_timerid; /* Timer ID; POSIX.1b timers */void *si_addr; /* Memory location which caused fault */int si_band; /* Band event */int si_fd; /* File descriptor */
}信号携带消息编程实现
收信号nicesignal.c
#include stdio.h
#include signal.h
#include sys/types.h
#include unistd.h
/*struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);};*/void handler(int signum, siginfo_t *info, void *context)//info里面的参数可以通过man手册查询
{printf(signum%d\n,signum);if(context ! NULL){printf(get data%d\n,info-si_int);printf(get data%d\n,info-si_value.sival_int);//和上面一样换了种方式printf(发送者的pid%d\n,info-si_pid);}
}int main()
{struct sigaction act;printf(pid%d\n,getpid());act.sa_sigactionhandler;act.sa_flagsSA_SIGINFO;//能够获取到信息信息//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);sigaction(SIGUSR1,act,NULL);//第三个是参数是备份的这边写NULL SIGUSR1对应的编号为10printf(22);while(1);return 0;
}发信号niceSendSig.c
#include stdio.h
#include signal.h
#include sys/types.h
#include unistd.h
#include stdlib.h
int main(int argc,char **argv)
{int signum;int pid;signumatoi(argv[1]);//atoi把字符串转化为整形pidatoi(argv[2]);//接收进程的pid号union sigval value;value.sival_int100;//发送一个整型数100的消息/*union sigval {int sival_int;void *sival_ptr; 发送字符的话把地址传进来};*///int sigqueue(pid_t pid, int sig, const union sigval value);sigqueue(pid,signum,value);printf(我的pid是:%d\n,getpid());printf(发送完毕\n);return 0;
}5.信号量(sem) 1.信号量
信号量Semaphore是一个计数器用于实现进程间的互斥与同步不用于存储进程间的通信数据
2.特点
(1).用于进程间同步若要在进程间传递数据需要结合共享内存
(2).信号量是基于PV操作程序对信号量是原子操作
所谓原子操作就是“不可中断的一个或一系列操作”,也就是不会被线程调度机制打断的操作(3).对信号量的操作不仅限于对信号的1-1可以是任意数视频没讲
P、V操作(类似信号量lock、unlock)
可以这样理解一间房间临界资源的门前有一个盒子盒子里有钥匙信号量一个人拿了钥匙P操作开了门并走进了房间且门外还有人等着得等进去的人出来放钥匙V操作这个人才能拿钥匙P操作进入房间 多道程序系统中存在许多进程它们共享各种资源然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源如输入机、打印机、磁带机等。 4.相关api 最简单的信号量是只能取 0 和 1 的变量这也是信号量最常见的一种形式叫做二值信号量Binary Semaphore。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作而不是在一个单一的二值信号量上进行操作。
1 #include sys/sem.h
2 // 创建或获取一个信号量组若成功返回信号量集ID失败返回-1
3 int semget(key_t key, int num_sems, int sem_flags);
4 // 对信号量组进行操作改变信号量的值成功返回0失败返回-1
5 int semop(int semid, struct sembuf semoparray[], size_t numops);
6 // 控制信号量的相关信息
7 int semctl(int semid, int sem_num, int cmd, ...);例子 目前是没有锁的状态父进程想拿锁在这边等待然后子进程有锁直接放回父进程得到锁后再运行。相当于不使用前面学习的wait方式保证子进程先运行因为没有锁父进程卡在拿锁那边。
#include stdio.h
#include sys/types.h
#include sys/ipc.h
#include sys/sem.h
#include stdlib.h
#include unistd.h//semctl函数第三个参数cmd的宏要求后面定义一个联合体
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 pGetKey(int semid)//p操作“拿锁”
{struct sembuf set;/*sops[0].sem_num 0; //Operate on semaphore 0 sops[0].sem_op 0; // Wait for value to equal 0 sops[0].sem_flg 0;*/set.sem_num0;//信号量编号 操作第几个信号量锁 这里就一个set.sem_op-1;//拿锁set.sem_flgSEM_UNDO;//If an operation specifies SEM_UNDO, it will be automatically undone when the process terminates.//SEM_UNDO设置为当进程截止的时候取消对锁的操作//int semop(int semid, struct sembuf *sops, size_t nsops); 对信号量组进行操作改变信号量的值成功返回0失败返回-1semop(semid,set,1);//1代表第二个参数的个数printf(拿锁\n);
}void vPutBackKey(int semid)//v操作“放回锁”
{struct sembuf set;/*sops[0].sem_num 0; //Operate on semaphore 0 sops[0].sem_op 0; // Wait for value to equal 0 sops[0].sem_flg 0;*/set.sem_num0;set.sem_op1;//放回锁set.sem_flgSEM_UNDO;//If an operation specifies SEM_UNDO, it will be automatically undone when the process terminates.//int semop(int semid, struct sembuf semoparray[], size_t numops); 对信号量组进行操作改变信号量的值成功返回0失败返回-1semop(semid,set,1);printf(放回锁\n);
}int main(int argc, char const *argv[])
{key_t key;int semid;keyftok(.,2);union semun initsem;initsem.val0;//初始是没有锁状态//int semget(key_t key, int nsems, int semflg);创建或获取一个信号量组若成功返回信号量集ID失败返回-1semidsemget(key,1,IPC_CREAT|0666);//1:信号量组中有几个信号量 这边选择1个IPC_CREAT|0666如果有就获取没有就创建信号量组权限可读可写可执行//int semctl(int semid, int sem_num, int cmd, ...); 控制信号量的相关信息semctl(semid,0,SETVAL,initsem);//初始化信号量int sem_num是代表操作第几个信号量我这边写0代表操作第一个信号量和数组一样 第一个从0开始多个信号量要构造数组//cmd可以通过man手册差相关的宏SETVAL设置信号量的初值,设置为initsem(联合体),里面有1把琐int pid fork();//创建子进程if(pid 0){//去拿锁pGetKey(semid);//拿锁 P操作printf(这是父进程\n);vPutBackKey(semid);//放回锁 semctl(semid,0,IPC_RMID);//销毁锁 }else if(pid 0){printf(这是子进程\n);vPutBackKey(semid);//放锁 V操作}else{printf(创建失败\n);}return 0;
}锁初始化为0子进程先运行运行结束放锁1父进程拿锁-1然后放锁1最后销毁锁。
6.通信方式总结
1.管道无名管道速度慢容量有限只有父子进程能通讯
2.FIFO有名管道任何进程间都能通讯但速度慢
3.消息队列容量受到系统限制且要注意第一次读的时候要考虑上一次没有读完数据的问题
4.信号量不能传递数据只能用来同步P操作、V操作
5.共享内存区能够很容易控制容量速度快但要保持同步和信号量结合使用比如一个进程在写的时候另一个进程要注意读写的问题相当于线程中的线程安全。当然共享内存区同样可以用作线程间通讯不过没这个必要线程间本来就已经共享了同一进程内的一块内存