1.管道 pipe 和 FIFO
-
管道 通常指无名管道 半双工 只能用于有亲缘关系(父子进程、兄弟进程)的进程之间通讯
-
FIFO 命名管道 用于无关进程之间的数据交换 创建之后可以用open等文件io函数操作 open时可以用设置非阻塞标志(O_NONBLOCK )(没意义) FIFO时在读或者写文件都行 两种管道都是半双工机制,只能一端读一端写
-
无名管道使用
// fd[0]为读而打开,fd[1]为写而打开。为父子进程通讯 #include<stdio.h> #include<unistd.h> int main() { int fd[2]; // 两个文件描述符 pid_t pid; char buff[20]; if(pipe(fd) < 0) // 创建管道 printf("Create Pipe Error!\n"); if((pid = fork()) < 0) // 创建子进程 { printf("Fork Error!\n"); } else if(pid > 0) // 父进程 { close(fd[0]); // 关闭读端 write(fd[1], "hello world\n", 12); } else { close(fd[1]); // 关闭写端 read(fd[0], buff, 20); printf("%s", buff); } return 0; }
-
FIFO 定义读和写文件
write_fifo.c#include<stdio.h> #include<stdlib.h> // exit #include<fcntl.h> // O_WRONLY #include<sys/stat.h> #include<time.h> // time int main() { int fd; int n, i; char buf[1024]; time_t tp; printf("I am %d process.\n", getpid()); // 说明进程ID if((fd = open("fifo1", O_WRONLY)) < 0) // 以写打开一个FIFO { perror("Open FIFO Failed"); exit(1); } for(i=0; i<10; ++i) { time(&tp); // 取系统当前时间 n=sprintf(buf,"Process %d's time is %s",getpid(),ctime(&tp)); printf("Send message: %s", buf); // 打印 if(write(fd, buf, n+1) < 0) // 写入到FIFO中 { perror("Write FIFO Failed"); close(fd); exit(1); } sleep(1); // 休眠1秒 } close(fd); // 关闭FIFO文件 return 0; }
read_fifo.c
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<fcntl.h> #include<sys/stat.h> int main() { int fd; int len; char buf[1024]; if(mkfifo("fifo1", 0666) < 0 && errno!=EEXIST) // 创建FIFO管道 perror("Create FIFO Failed"); if((fd = open("fifo1", O_RDONLY)) < 0) // 以读打开FIFO { perror("Open FIFO Failed"); exit(1); } while((len = read(fd, buf, 1024)) > 0) // 读取FIFO管道 printf("Read message: %s", buf); close(fd); // 关闭FIFO文件 return 0; }
2.消息队列 massage
关心如何加消息and如何读取消息到消息队列
消息队列本质是带有序列的单链表,链表由Linux内核管理。链表的节点即为一条消息。从链表中读取消息之后消息不回被删除。
消息队列一个进程可以边读边写,两个A和B进程需使用同一个队列通讯
消息队列可以对链表中的消息进行条件筛选,没有读取次序要求,根据读函数msgrcv
的第三个参数long msgtype
msg_server.c 服务端
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
// 用于创建一个唯一的key
#define MSG_FILE "." /// 表示当前文件夹
// 消息结构
struct msg_form {
long mtype;
char mtext[256];
};
int main()
{
int msqid;
key_t key;
struct msg_form msg;
// 获取key值,第一个参数表示哪个文件夹 "." 当前文件夹 第二个参数表示id是子序号,使用8bits(1-255)
if((key = ftok(MSG_FILE,'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 打印key值
printf("Message Queue - Server key is: %d.\n", key);
// 创建消息队列
if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
{
perror("msgget error");
exit(1);
}
// 打印消息队列ID及进程ID
printf("My msqid is: %d.\n", msqid);
printf("My pid is: %d.\n", getpid());
// 循环读取消息,客户端进程边读边写
for(;;)
{
// 256指定mtext的大小,第三参表示消息类型mtype,第四参表示设置阻塞
msgrcv(msqid, &msg, 256, 888, 0);// 返回类型为888的第一个消息
printf("Server: receive msg.mtext is: %s.\n", msg.mtext);
printf("Server: receive msg.mtype is: %d.\n", msg.mtype);
msg.mtype = 999; // 客户端接收的消息类型
sprintf(msg.mtext, "hello, I'm server %d", getpid());
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
// 第三参,消息数据的长度
}
return 0;
}
msg_client.c 客户端
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
// 用于创建一个唯一的key
#define MSG_FILE "."
// 消息结构
struct msg_form {
long mtype;
char mtext[256];
};
int main()
{
int msqid;
key_t key;
struct msg_form msg;
// 获取key值
if ((key = ftok(MSG_FILE, 'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 打印key值
printf("Message Queue - Client key is: %d.\n", key);
// 打开消息队列
if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
{
perror("msgget error");
exit(1);
}
// 打印消息队列ID及进程ID
printf("My msqid is: %d.\n", msqid);
printf("My pid is: %d.\n", getpid());
// 添加消息,类型为888
msg.mtype = 888;
sprintf(msg.mtext, "hello, I'm client %d", getpid());
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
// 读取类型为999的消息
msgrcv(msqid, &msg, 256, 999, 0);
printf("Client: receive msg.mtext is: %s.\n", msg.mtext);
printf("Client: receive msg.mtype is: %d.\n", msg.mtype);
return 0;
}
3.共享内存 shared memory
无名管道和命名管道都是半双工通讯方式,而共享内存是全双工。
两个人通过纸条传送消息
第一种通讯:A写数据在纸条上,纸条放在桌子上,B拿走纸条读取数据(管道通讯)
第二种通讯:A把写有数据的纸条1放在桌子上,B可以直接看到(读取),纸条留在桌子上不拿走;B把写有数据的纸条2放在桌子上,A可以直接看到(读取),纸条不拿走(消息队列)
第三种通讯:桌子上有一张白纸,A和B都能在纸张上写数据以及看到纸张上的内容,纸张一直放在桌子上(共享内存)
步骤:a.创建共享内存 b.映射内存到进程A和进程B c.读写数据进行数据交换 d.断开映射 e.删除共享内存
案例:
-
在共享内存写数据
#include <sys/ipc.h> #include <sys/shm.h> #include <stdlib.h> #include <stdio.h> #include <string.h> //int shmget(key_t key, size_t size, int shmflg); int main() { int shmid; char *shmaddr; key_t key; key = ftok(".",1); // 第二个参数1024*4 共享内存的大小单位M,第三个参数表示创建共享内存 shmid = shmget(key,1024*4,IPC_CREAT|0666); // 写的时候创建,读的时候默认flg为0 if(shmid == -1){ printf("shmget failed!\n"); exit(-1); } // 第二参,内核自动安排内存,第三参,0表示映射的内存可读可写。可以改为只读/只写 shmaddr = shmat(shmid,0,0); // 将共享内存和进程链接,链接到指针shmaddr printf("shmat ok\n"); strcpy(shmaddr,"chenlichen"); // 复制字符串到shmaddr(指针) sleep(5); // 等待读取数据 shmdt(shmaddr); // 断开映射 shmctl(shmid, IPC_RMID, 0); // 关闭共享内存 printf("quit\n"); return 0; }
-
从共享内存读数据
#include <sys/ipc.h> #include <sys/shm.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int shmid; char *shmaddr; key_t key; key = ftok(".",1); // 创建共享内存,第三参,0直接获取共享内存 shmid = shmget(key,1024*4,0); if(shmid == -1){ printf("shmget noOk\n"); exit(-1); } // 映射 shmaddr = shmat(shmid,0,0); // 读数据 printf("data: %s\n:",shmaddr); // 关闭映射 shmdt(shmaddr); // 退出 printf("quit\n"); return 0; }
-
如果共享内存没有删除 通过下述指令查看并删除
两个指令
ipcs -m
和ipcs -m id号
4.信号signal
传送门:Linux信号(signal)
-
本质是主进程在运行时收到子进程的信号通知后,在主进程中进行操作函数。对Linux而言 信号本质是软中断。
对信号而言最大的意义是实现异步通讯手段(捕捉信号)案例:
- 一个人在看电视(主进程),有人敲门(信号1),有人给他打电话(信号2)
- 这个人先处理那个信号呢?(信号优先权)
- 听到敲门声一定会给那个人开门吗?(捕捉信号,可以先问对方是谁在开门)
- 这个人既不理会敲门人又忽略电话铃声呢?(忽略信号)
- 听到手机铃声通常就第一时间拿起手机接听(默认动作) man 7 signal
-
案例,捕捉键盘的Ctrl + C(对应sigint信号),进行其他操作 (默认动作杀死程序的进程)
参考:关于linux中的Ctrl+C,Ctrl+Z,Ctrl+D- 初级信号处理
#include <signal.h> #include <stdio.h> // typedef void (*sighandler_t)(int); // sighandler_t signal(int signum, sighandler_t handler); // 重新定向的函数,参数为信号编号 void handler(int signum){ printf("get signum=%d\n",signum); switch(signum){ case 2: printf("SIGINT\n"); break; case 9: printf("SIGKILL\n"); break; case 10: printf("SIGUSR1\n"); break; } printf("never quit\n"); } int main(){ signal(SIGINT, SIG_IGN); // 忽略SIGINT信号,对应Ctrl+C指令 signal(SIGKILL, handler); // 对于SIGKILL信号,无法捕捉忽略重新定向 signal(SIGUSR1, handler); // 捕捉SIGUSR1信号,对应指令kill -10 进程号 while(1); return 0; }
通过另一个程序发送指令
#include <signal.h> #include <stdio.h> #include <sys/types.h> int main(int argc ,char **argv){ int signum; int pid; char cmd[128]={0}; signum = atoi(argv[1]); // atoi将字符串转为整数 pid = atoi(argv[2]); printf("num=%d,pid=%d\n", signum, pid); // kill(pid, signum); // 默认kill发指令 sprintf(cmd,"kill -%d %d", signum, pid); // 将sprintf组合成 kill -9 4656 类型指令存到cmd中 system(cmd); // 函数system将cmd指令发 printf("send signal ok"); return 0; }
入门级信号处理kill发送指令,signal绑定指令;
入门信号处理忽略了信号携带的信息。(只敲门不讲话)
函数atoi的使用
函数sprintf的使用
函数system的使用-
高级信号处理
信号发送函数用到的结构体
#include <signal.h> // // 收发数据的结构体 int sigqueue(pid_t pid, int sig, const union sigval value); union sigval { int sival_int; // 直接将整型数据传到这里进行收发 void *sival_ptr; // 数据存好之后,将该指针指向这里 *********** };
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); // 信号编号 // 新绑定的信号的行为 // 原有的信号的行为 struct sigaction { //4个参数 回调函数句柄sa_handler、sa_sigaction只能任选其一 void (*sa_handler)(int); // 不处理消息 void (*sa_sigaction)(int, siginfo_t *, void *); // 信号处理程序,能够接受额外数据和sigqueue配合使用(3个参数 ) sigset_t sa_mask; // 处理信号可以阻塞 int sa_flags; // 指定为SA_SIGINFO表示能够接受数据 };
高级信号处理可以用信号携带信息。(边敲门边讲话)
注意两点:①发信号(用什么发?怎么放入消息?) & ②收信号(来信号怎么处理?如何读出信号里的消息?)
sigaction
来信号之后处理函数 读出结构体中的参数即为读消息.(定义结构体sigaction)
sigqueue
发信号搜索man手册中的字段:
/字段
字段代表要搜的内容其中自定义函数
handler
中的sig_info
结构体siginfo_t { //****发送数据进程的pid pid_t si_pid; /* Sending process ID */ // ****接收的数据存在于这里 =================== sigval_t si_value; /* Signal value */ int si_int; /* POSIX.1b signal */ =================== // 还有很多数据没有展示 常用的只有以上三个 }
请留意两个用于传输数据的变量:
sigval_t si_value;
/* Signal value / 和int si_int;
/ POSIX.1b signal */
实际上:- siginfo_t 结构体中(sigval_t) si_value就是sigqueue函数中传入的第三个参数sigval
- siginfo_t 结构体中(int) si_int就是从sigqueue函数中传入的第三个参数sigval.sival_int中获得
- siginfo_t 结构体中si_ptr就是从sigqueue函数中传入的第三个参数sigval.sival_ptr中获得
案例:
信号发送进程#include <stdio.h> #include <signal.h> int main(int argc, char **argv) // 传入的第一个参数为信号的编号,第二个为要发的进程的pid { int signum; int pid; signum = atoi(argv[1]); // 发送哪种信号? 信号编号 pid = atoi(argv[2]); // 给哪个进程发消息 // 定义要发送的内容 int a = 10; union sigval value; // value.sival_int = 100; // strcpy(value.sival_ptr,"vale"); value.sival_ptr=&a; // 这里直接把发送的内容,让指针指向这里 // 用sigqueue函数发送内容到响应的进程(pid) // int sigqueue(pid_t pid, int sig, const union sigval value); sigqueue(pid,signum,value); printf("%d,done\n",getpid()); // 表示发送完毕 return 0; }
信号接收进程
#include <signal.h> #include <stdio.h> // int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); void handler(int signum , siginfo_t *info, void *context) // 信号处理函数 { printf("get signum %d\n",signum); // 信号编号 if(context != NULL){ // 防止没有数据产生段错误 printf("from:%d\n",info->si_pid); // 打印发送进程pid // printf("get data=%d\n",info->si_int); // 读 // printf("get data=%d\n",info->si_value.sival_int); printf("get data=%d\n",*(int *)(info->si_value.sival_ptr)); // 打印数据 } } int main() { struct sigaction act; printf("pid = %d\n",getpid()); act.sa_sigaction = handler; // 设置操作函数 act.sa_flags = SA_SIGINFO; // 设置能接受数据 sigaction(SIGUSR1,&act,NULL); // 绑定siaction while(1); return 0; }
5.信号量 Semaphore
传送门:进程间通讯介绍IPC,阅读信号量部分
本质是一间房子被锁住,从钥匙箱子拿钥匙去开门,一把锁可以有很多钥匙
- pv操作案例
#include<stdio.h> #include<stdlib.h> #include<sys/sem.h> // 联合体,用于semctl初始化 union semun { int val; /*for SETVAL*/ // 相当于钥匙的数量 struct semid_ds *buf; unsigned short *array; }; // 初始化信号量 //param:value锁有几把钥匙 int init_sem(int sem_id, int value) { union semun tmp; tmp.val = value; // 参数0是操作第一个信号量(从0开始),用SETVAL设置信号量的值,值为tmp if(semctl(sem_id, 0, SETVAL, tmp) == -1) // 初始化信号量,并判断是否成功 { perror("Init Semaphore Error"); return -1; } return 0; } // P操作: // 若信号量值为1,获取资源并将信号量值-1 // 若信号量值为0,进程挂起等待 int sem_p(int sem_id) { struct sembuf sbuf; sbuf.sem_num = 0; /*序号*/ sbuf.sem_op = -1; /*P操作*/ sbuf.sem_flg = SEM_UNDO; if(semop(sem_id, &sbuf, 1) == -1) { perror("P operation Error"); return -1; } return 0; } // V操作: // 释放资源并将信号量值+1 // 如果有进程正在挂起等待,则唤醒它们 int sem_v(int sem_id) { struct sembuf sbuf; sbuf.sem_num = 0; /*序号*/ sbuf.sem_op = 1; /*V操作*/ sbuf.sem_flg = SEM_UNDO; if(semop(sem_id, &sbuf, 1) == -1) { perror("V operation Error"); return -1; } return 0; } // 删除信号量集 int del_sem(int sem_id) { union semun tmp; if(semctl(sem_id, 0, IPC_RMID, tmp) == -1) { perror("Delete Semaphore Error"); return -1; } return 0; } int main() { int sem_id; // 信号量集ID key_t key; pid_t pid; // 获取key值 if((key = ftok(".", 'z')) < 0) { perror("ftok error"); exit(1); } // 创建信号量集,其中只有一个信号量(一把锁) if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1) { perror("semget error"); exit(1); } // 初始化:初值设为0资源被占用,初始化0把钥匙 init_sem(sem_id, 0); if((pid = fork()) == -1) perror("Fork Error"); else if(pid == 0) /*子进程*/ { sleep(2); printf("Process child: pid=%d\n", getpid()); sem_v(sem_id); /*释放资源*/ } else /*父进程*/ { // 这里直接p操作会阻塞,因为初始化的时候没有钥匙,子进程正常运行 sem_p(sem_id); /*等待资源*/ printf("Process father: pid=%d\n", getpid()); sem_v(sem_id); /*释放资源*/ del_sem(sem_id); /*删除信号量集*/ } return 0; }
测试可以多次p 和 v操作,多次v操作锁箱里的钥匙会增加,但是无论如何如果没有钥匙的时候p操作会阻塞