目录
一、IPC通讯机制
1)传统的通讯机制:
2)systemV 的通讯机制:
3)跨主机的通讯机制:
1、无名管道
1)无名管道的概念
2)无名管道的函数
3)无名管道通讯(亲缘进程)
2、有名管道
1)有名管道的概念
2)有名管道的函数
3)有名管道通讯(无亲缘进程)
3、信号
1)信号的概念
2)了解信号
3)信号的处理方式
4)signal(模拟中断)
练习:捕获信号 2)SIGHUP也就是ctrl c
练习:捕获信号 13)SIGPIPE 管道破裂
练习:使用signal回收僵尸进程
5)kill(给指定进程发送指定信号)
6)raise(给自己发送信号)
7)alarm(秒级定时器)
4、消息队列
1)消息队列原理
2)消息队列的指令(ipcs)
3)ftok(创建密钥)
4)stat(查看文件路径及属性)
5)msgget(创建消息队列)
6)msgsnd(发送信息)
7)msgrcv(接收信息)
8)msgctl(删除、获取、修改队列信息)
一、IPC通讯机制
IPC:是指(inter process communction)进程间通讯机制
进程之间通讯可以通过文件实现,但是进程之间不清楚是什么时候开始通讯,因此实时操作性差。
由此 引出 IPC 通讯机制,他是通过内核空间共享实现,多个用户空间独立的进程之间通讯
1)传统的通讯机制:
1. 无名管道文件 pipo
2. 有名管道文件 fifo
3. 信号 signal
2)systemV 的通讯机制:
1. 消息队列 messge queue
2. 共享内存 shard memory
3. 信号灯集 semphore
3)跨主机的通讯机制:
嵌套字 socket
1、无名管道
1)无名管道的概念
1. 无名管道本质上是一个文件(管道文件 p ),但是存储在内核中,普通文件存储在硬盘上
2. 管道文件是特殊文件,只可以使用文件IO函数操作(open、write、read、close),lseek不允许使用
3. 管道需要满足队列的思想(先进先出),管道中的数据是一次性的,读取数据后删除数据
4. 管道属于半双工通讯机制
5. 当管道的读写端同时关闭,此时管道的内存自动释放
6. 管道的大小位:64K
7. 无名管道只适用于有亲缘关系的进程间的通讯
8. 如果打开写端,关闭读端,写入字符会发生管道破裂(SIGPIPE)
2)无名管道的函数
格式:#include <unistd.h>int pipe(int pipefd[2]);
功能:创建无名管道
参数:int pipefd[2]:存储管道两端的文件描述符pipefd[0]:读端的文件描述符pipefd[1]:写端的文件描述符
返回值:成功返回0,失败返回-1,跟新errno
3)无名管道通讯(亲缘进程)
#include <25051head.h>
int main(int argc, const char *argv[]){//创建管道//pipefd[0]:读 pipefd[1]:写int pipefd[2];if(-1==pipe(pipefd)){ERRLOG("pipe error");}//无名管道用于存在亲缘关系之间的进程通信,所以需要创建父子进程pid_t pid=fork();if(pid==-1){ERRLOG("fork error");}else if(0==pid) //子进程{close(pipefd[1]);//读char buf[128]="";while(1){memset(buf,0,sizeof(buf));ssize_t res=read(pipefd[0],buf,sizeof(buf)-1);if(res==0){printf("读取到文件的结尾..\n");break;}else if(res==-1){ERRLOG("read error");}if(strcmp(buf,"quit")==0){printf("子进程退出..\n");break;}//读取成功printf("buf=[%s]\n",buf);}close(pipefd[0]);}else if(pid>0) //父进程{close(pipefd[0]);//写while(1){ char buf[128]="";fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1]='\0';//把buf写入到管道文件中write(pipefd[1],buf,strlen(buf));if(strcmp(buf,"quit")==0){printf("父进程退出..\n");break;}}close(pipefd[1]);}return 0;
}
2、有名管道
1)有名管道的概念
有名管道和无名管道特点一致,只有一点不同:有名管道可用于无亲缘关系的进程间通讯
2)有名管道的函数
格式:#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);功能:创建有名管道
参数:const char *pathname:创建有名管道的文件名以及路径mode_t mode:有名管道文件的权限 0664 0777
返回值:成功返回0,失败返回-1.跟新errno指令:mkfifo 路径及文件名
3)有名管道通讯(无亲缘进程)
3、信号
1)信号的概念
信号是软件层对中断的一种模拟
信号是异步通讯模式
异步是指任务和任务之间没有联系,按照CPU的时间片轮询机制访问
2)了解信号
信号可以通过指令 kill - l 查看
1 ~ 31:标准信号,当多次触发同一信号,内核只处理一次
34~ 64:实时信号,当多次触发同一信号是,会把信号存储到队列中,按照队列思想,逐个处理
编号 | 信号名 | 含义 | 快捷方式/触发方式 |
1 | SIGHUP | 终端挂断(如SSH连接断开、守护进程重载配置) | kill -1 或系统事件触发 |
2 | SIGINT | 终端中断请求(用户主动终止进程) | ctrl c |
3 | SIGQUIT | 终端退出请求(类似SIGINT,生成核心转储用于调试) | Ctrl+\ |
4 | SIGILL | 非法指令(执行无效的CPU指令) | 程序错误或硬件异常触发 |
5 | SIGTRAP | 断点陷阱(调试器捕获断点或单步执行) | 调试器触发 |
6 | SIGABRT | 程序异常终止(由abort()函数触发) | assert()失败或 abort()调用 |
7 | SIGBUS | 总线错误(非法内存访问,如未对齐的内存操作) | 硬件或内存错误触发 |
8 | SIGFPE | 浮点异常(如除以零、溢出) | 算术运算错误触发 |
9 | SIGKILL | 强制终止进程(不可捕获或忽略) | kill -9 |
10 | SIGUSR1 | 用户自定义信号(用途由程序定义,如重开日志) | kill -10 |
11 | SIGSEGV | 段错误(非法内存访问,如空指针解引用) | 内存访问错误触发 |
12 | SIGUSR2 | 用户自定义信号2(用途由程序定义) | kill -12 |
13 | SIGPIPE | 管道破裂(向无读端的管道写数据) | 管道操作错误触发 |
14 | SIGALRM | 定时器超时(由alarm()或setitimer()设置) | 定时器到期触发 |
15 | SIGTERM | 终止请求(允许程序优雅退出,默认kill命令发送的信号) | kill -15 或 kill (无参数) |
16 | SIGSTKFLT | 协处理器栈错误(少见,部分系统未实现) | 硬件协处理器错误触发 |
17 | SIGCHLD | 子进程状态变更(子进程终止或暂停) | 子进程事件触发 |
18 | SIGCONT | 继续执行进程(恢复被暂停的进程) | fg 命令或 kill -18 |
19 | SIGSTOP | 暂停进程(不可捕获或忽略) | Ctrl+Z (部分系统行为) |
20 | SIGTSTP | 终端暂停请求(后台进程尝试终端输入/输出) | Ctrl+Z |
23 | SIGURG | 紧急数据到达(如TCP带外数据) | 网络数据包触发 |
24 | SIGXCPU | 超出CPU时间限制(资源限制触发) | setrlimit() 设置超限触发 |
25 | SIGXFSZ | 超出文件大小限制(如写入超过 ulimit 限制的文件) | 文件操作超限触发 |
26 | SIGVTALRM | 虚拟定时器超时(基于进程的虚拟时间) | setitimer(ITIMER_VIRTUAL) 触发 |
27 | SIGPROF | 性能分析定时器超时(统计CPU时间) | setitimer(ITIMER_PROF) 触发 |
28 | SIGWINCH | 终端窗口大小变化(如调整终端窗口) | 终端尺寸变更事件触发 |
29 | SIGIO | 异步I/O事件(文件描述符准备就绪) | I/O事件触发(需配置fcntl()) |
30 | SIGPWR | 电源故障(系统关机前通知进程) | UPS或电源管理事件触发 |
31 | SIGSYS | 无效系统调用(执行不存在或参数错误的系统调用) | 非法系统调用触发 |
34 | SIGRTMIN | 实时信号起始编号(自定义用途,范围: SIGRTMIN ~ SIGRTMAX,通常34~64) | kill -34 或更高编号 |
3)信号的处理方式
信号屏蔽:在执行信号处理时,连续触发同一个信号,该信号只处理一次,这个行为我们称之为信号屏蔽,但是可以触发其他信号。
1. 执行默认操作:当触发信号时,执行默认的信号处理函数
2. 忽略信号:当触发信号时,忽略该信号不做处理
3. 捕获信号:当触发信号时,执行我们想让他执行的函数
4)signal(模拟中断)
格式:#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);功能:信号处理,接收到指定信号,并选择处理方式(执行默认函数,忽略,捕获)
参数:int signum:指定信号,接收到该信号后,执行操作(可以填信号编号,也可以填信号(大写字母))sighandler_t handler: 有以下指定参数SIG_IGN: 忽略信号SIG_DFL: 执行默认操作填函数名(函数首地址): 触发执行该函数注意:9)SIGKILL 和 19)SIGSTOP 不允许捕获和忽略返回值:成功返回上一个信号处理,失败返回SIG_ERR 更新 errno
练习:捕获信号 2)SIGHUP也就是ctrl c
练习:捕获信号 13)SIGPIPE 管道破裂
练习:使用signal回收僵尸进程
5)kill(给指定进程发送指定信号)
格式:#include <signal.h>int kill(pid_t pid, int sig);功能:发送信号给进程或者进程组
参数:pid_t pid:>0: 把信号发送给进程等于pid的进程=0:把信号发送给当前进程组下的所有有权发送的进程=-1:把信号发送给系统的所有进程,除了没有权限发送的init进程<0:但不是-1. 先取绝对值,进程组id,把信号发送给改进程组下有权发送的进程int sig:信号的编号 或者 宏0:会做错误检测,不发送信号
返回值:成功返回0,失败返回-1.跟新errno
6)raise(给自己发送信号)
格式:#include <signal.h>int raise(int sig);功能:给调用者进程发送信号(自杀)
参数:int sig表示发送的信号编号 或在宏
返回值:成功返回0,失败返回非0
7)alarm(秒级定时器)
格式:#include <unistd.h>unsigned int alarm(unsigned int seconds);完全等价于:alarm--->kill(getpid(),14) 14) SIGALRM
功能:发送一个时钟信号
参数:unsigned int seconds:几秒闹钟响
返回值:返回上一次闹钟没有触发的剩余秒数,如果没有则返回0
可以将alarm放在执行函数内,重复触发alarm。
4、消息队列
1)消息队列原理
消息队列在内核中申请一片空间,把信息打包成结点存储在内核缓冲区中,进程通过访问内核缓冲区中结点实现通讯
1. 把数据打包成结点(数据类型、数据内容)
2. 需满足队列思想(先进先出),即使按类型读取数据,也要满足队列思想
3. 在读取消息队列的数据后,数据默认会删除
4. 消息队列属于全双工通讯机制
5. 消息队列不会随着进程的结束而结束,需要手动删除 或者 电脑重启
2)消息队列的指令(ipcs)
ipcs 同时查看消息队列、共享内存段、信号量数组
ipcs -q 只查看消息队列
ipcs -q msgid 删除消息队列
3)ftok(创建密钥)
格式:#include <sys/types.h>#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);功能:把pathname 和proj_id转换为秘钥的,被msgget shmget semget使用
参数:const char *pathname:路径以及文件名 (随便但是需要存在)int proj_id:可以写整数,字符(随便)
返回值:成功返回秘钥,失败返回-1,跟新errno,注意秘钥和stat有关 key=proj_id(低8位)+设备号(低8位)+inode(低16位)
4)stat(查看文件路径及属性)
#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>int stat(const char *pathname, struct stat *statbuf);int lstat(int fd, struct stat *statbuf);
功能:stat查看文件以及路径的属性的,stat不可以查看连接文件,lstat函数可以查看连接文件
参数:const char *pathname:查看的文件以及路径struct stat *statbuf:该结构体指针重中存储 执行文件的属性信息struct stat {dev_t st_dev; /* 设备号 */ino_t st_ino; /* inode号 */mode_t st_mode; /* 文件类型和权限 */nlink_t st_nlink; /* 硬连接数 */uid_t st_uid; /* 用户id */gid_t st_gid; /* 组id */dev_t st_rdev; /* 设备id*/off_t st_size; /* 总字节大小 */blksize_t st_blksize; /* Block size for filesystem I/O */blkcnt_t st_blocks; /* Number of 512B blocks allocated *//* Since Linux 2.6, the kernel supports nanosecondprecision for the following timestamp fields.For the details before Linux 2.6, see NOTES. */struct timespec st_atim; /* 最后一次访问的时间 */struct timespec st_mtim; /* 最后一次修改的事件 */struct timespec st_ctim; /* 最后一次状态值修改的之间 */#define st_atime st_atim.tv_sec /* Backward compatibility */#define st_mtime st_mtim.tv_sec#define st_ctime st_ctim.tv_sec};
5)msgget(创建消息队列)
格式:#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>int msgget(key_t key, int msgflg);功能:创建消息队列的
参数:
key_t key:秘钥,ftok函数的返回值
int msgflg:实际怎么IPC_CREAT | 0664 或者 IPC_CREAT|IPC_EXCL|0664IPC_CREAT:如果消息队列不存在,则创建消息队列,存在不会报错IPC_CREAT|IPC_EXCL:如果细队列存在则报错EEXIST,不存在则创建
返回值:成功返回消息队列id,失败返回-1,。跟新errno
6)msgsnd(发送信息)
格式: #include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);功能:发送消息包的
参数:int msqid:消息队列idconst void *msgp: 发送的消息包,例如下面的结构体struct msgbuf {long mtype; /* 消息的类型, 必须> 0 */char mtext[1]; /* 消息的内容 */};//发送一个字符串struct A {long a; /* 消息的类型, 必须> 0 */char str[128]; /* 消息的内容 */};//发送一个整数struct B {long b; /* 消息的类型, 必须> 0 */int num; /* 消息的内容 */};//发送一个学生的信息struct V {long c; /* 消息的类型, 必须> 0 */char name[128]; /* 消息的内容 */int age;float high;};size_t msgsz:消息内容的细节大小 sizeof(struct msgbuf)-sizeof(long)int msgflg :0:阻塞 当消息满后则阻塞IPC_NOWAIT:非阻塞函数,当消息队列满不会阻塞,但是报错EAGAIN
返回值:成功返回0,失败返回-1
7)msgrcv(接收信息)
格式:#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:接收消息队列的数据包
参数:int msqid:消息队列idconst void *msgp: 发送的消息包,例如下面的结构体struct msgbuf {long mtype; /* 消息的类型, 必须> 0 */char mtext[1]; /* 消息的内容 */};//发送一个字符串struct A {long a; /* 消息的类型, 必须> 0 */char str[128]; /* 消息的内容 */};//发送一个整数struct B {long b; /* 消息的类型, 必须> 0 */int num; /* 消息的内容 */};//发送一个学生的信息struct V {long c; /* 消息的类型, 必须> 0 */char name[128]; /* 消息的内容 */int age;float high;};size_t msgsz:消息内容的细节大小 sizeof(struct msgbuf)-sizeof(long)long msgtyp:指定消息队列的类型=0:读取消息队列的第一条信息 >0: 读取类型等于msgtyp的第一条信息如果msgflag指定MSG_EXCEPT,读取不等于msgtype的第一条信息<0:读取小于等于msgtype绝对值的第一条信息int msgflg :0:阻塞 当消息满后则阻塞IPC_NOWAIT:非阻塞函数,当消息队列满不会阻塞,但是报错EAGAIN返回值:成功返回消息内容的字节大小,失败返回-1 ,跟新errno1:aaa 2:bbb 6:cccc 2:dddd 4:eeee 3:ffff
msgtyp=0
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 0,0); //aaa
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 0,0); //bbb
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 0,0); //ccc
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 0,0); //ddd
msgtype>0
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 2,0);//bbb
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 2,0);//dddd
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 2,0);//阻塞
msgtype>0 msgflg指定MSG_EXCEPT
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 2,0|MSG_EXCEPT); //aaa
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 2,0|MSG_EXCEPT); //ccc
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 2,0|MSG_EXCEPT); //eeee
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 2,0|MSG_EXCEPT); //ffff
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), 2,0|MSG_EXCEPT); //阻塞
msgtype<0
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), -4,0); //aaa
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), -4,0); //ccc
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), -4,0); //ddd
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), -4,0); //fff
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), -4,0); //eeee
msgrcv(msqid, &buf, sizeof(buf)-sizeof(long), -4,0); //阻塞
8)msgctl(删除、获取、修改队列信息)
格式:#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:消息队列控制(删除,获取信息,修改消息队列的信息)
参数:int msqid:消息的idint cmd:IPC_STAT:获取消息队列内核数据的IPC_SET:修改消息队列数据IPC_RMID:删除消息队列 ,第三个填 NULLstruct msqid_ds *buf,该指针存储获取以及修改对应内核的数据struct msqid_ds {struct ipc_perm msg_perm; /* Ownership and permissions */time_t msg_stime; /* Time of last msgsnd(2) */time_t msg_rtime; /* Time of last msgrcv(2) */time_t msg_ctime; /* Time of last change */unsigned long __msg_cbytes; /* Current number of bytes inqueue (nonstandard) */msgqnum_t msg_qnum; /* Current number of messagesin queue */msglen_t msg_qbytes; /* Maximum number of bytesallowed in queue */pid_t msg_lspid; /* PID of last msgsnd(2) */pid_t msg_lrpid; /* PID of last msgrcv(2) */};struct ipc_perm {key_t __key; /* Key supplied to msgget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */};
返回值:
如果指定IPC_STAT IPC_SET IPC_RMID 成功返回0,失败返回-1,跟新errno例如:删除消息队列
msgctl(msgid,IPC_RMID,NULL)
例如:获取消息队列的权限
struct msqid_ds buf;
msgctl(msgid,IPC_STAT,&buf);
printf("权限:%ld\n",buf.msg_perm.mode);
例如:修改消息队列的权限0777
1.先获取原来的属性
struct msqid_ds buf;
msgctl(msgid,IPC_STAT,&buf);
2.只修改权限
buf.msg_perm.mode=0777;
msgctl(msgid,IPC_SET,&buf);
printf("权限:%ld\n",buf.msg_perm.mode);
练习: 从信息队列中读取字符