C语言实现进程间通信
在linux系统中的进程间通信方式有7种。原始的通信方式:无名管道 有名管道 信号,system V的系统上引入如下三种方式:消息队列 共享内存 信号灯,通过套接字进行本地进程间通信:BSD
在没有学习进程间通信的时候,两个进程可以通过文件进程通信。但是在使用文件通信的时候谁先执行谁后执行无法确定,所以这种通信方式有问题。在linux系统中的进程间通信方式有7种。
原始的通信方式:无名管道 有名管道 信号
system V的系统上引入如下三种方式:消息队列 共享内存 信号灯
通过套接字进行本地进程间通信:BSD
一、原始的通信方式
1.无名管道
①无名管道的原理
无名管道只能用于具备亲缘关系的进程间通信,无名管道是一个半双工的通信方式。
单工: A----->B
半双工:在同一时间点内只能一端发另外一端收 A------>B B----->A
全双工:在同一时刻内可以双向发送和接收 A<------->B
管道最大是64K,无名管道不能使用lseek定位光标。
②无名管道读写端特点
读端存在,写管道 :有多少写多少,直到管道写满(64K)
读端不存在,写管道:读端不存在写管道没有意义,管道破裂,操作系统给进程发送一个SIGPIPE,杀死进程
写端存在,读管道 :有多少读多少,如果管道里没有数据,读阻塞等待
写端不存在,读管道:有多少读多少,如果管道里没有数据,立即ansh
③用到的函数
int pipe(int pipefd[2]);
功能:创建一个无名管道
#include <unistd.h>
参数:
@pipefd:返回的是读写段的文件描述符
返回值:成功返回0,失败返回-1置位错误码
示例:
#include <head.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int pipefd[2];
char buf[1024] = {0};
pid_t pid;
//1.创建管道
if(pipe(pipefd) == -1)
PRINT_ERR("pipe create error");
//2.创建进程
pid = fork();
if(pid == -1){
PRINT_ERR("fork error");
}else if(pid == 0 ){
close(pipefd[0]); //关闭读取端
//1.子进程
while(1){
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';
write(pipefd[1],buf,strlen(buf));
if(strncmp(buf,"quit",4)==0)
break;
}
printf("子进程退出了\n");
close(pipefd[1]);
exit(EXIT_SUCCESS);
}else{
close(pipefd[1]); //关闭写端口
//2.父进程
while(1){
memset(buf,0,sizeof(buf)); //bzero
read(pipefd[0],buf,sizeof(buf));
if(strncmp(buf,"quit",4)==0)
break;
printf("parent buf = %s\n",buf);
}
printf("父进程退出了\n");
close(pipefd[0]);
wait(NULL);
}
return 0;
}
//head.h
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PRINT_ERR(msg) do{ \
perror(msg);\
return -1;\
}while(0)
#endif
————————————————
版权声明:本文为CSDN博主「zhangts318」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhangts318/article/details/123973935
2.有名管道
有名管道可以用于任意进程间的通讯,不一定是亲缘关系的进程。有名管道的大小也是64K,也不能使用lseek函数。
①有名管道通信的特点
读不存在写管道:在打开的位置阻塞
写不存在读管道:在打开的位置阻塞
先存在读,然后关掉读,写管道:管道破裂,收到SIGPIPE信号
先存在写,然后关掉写,读管道:读端立即返回
如果读写都正常存在:有多少写多少,直到写满64K为止,有多少读多少,没有数据就阻塞
②用到的函数
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道
#include <sys/types.h>
#include <sys/stat.h>
参数:
@pathname:路径及有名管道的文件
管道文件不是在磁盘上存储的,管道文件是在内存上存储的。
管道文件的大小永远都是0,不管有没有向里面写数据,只起到标识作用
@mode:管道文件的权限
返回值:成功返回0,失败返回-1置位错误码
//当文件创建成功之后,可以通过open read write close进行通信
示例:
//创建管道
#include <head.h>
#define FIFO "./fifo1"
int main(int argc, char const *argv[])
{
char buf[20] = {0};
if (mkfifo(FIFO, 0664) == -1)
PRINT_ERR("make fifo error");
while (1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if (strncmp(buf, "quit", 4) == 0)
break;
}
printf("销毁管道文件..\n");
system("rm ./fifo1");
return 0;
}
//发送方
#include <head.h>
#define FIFO "./fifo1"
int main(int argc, char const *argv[])
{
int fd;
char buf[1024] = {0};
if((fd = open(FIFO,O_WRONLY))==-1){
PRINT_ERR("open error");
}
while(1){
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
write(fd,buf,strlen(buf));
if (strncmp(buf, "quit", 4) == 0)
break;
}
close(fd);
return 0;
}
//接收方
#include <head.h>
#include <strings.h>
#define FIFO "./fifo1"
int main(int argc, char const *argv[])
{
int fd;
char buf[1024] = {0};
if((fd = open(FIFO,O_RDONLY))==-1){
PRINT_ERR("open error");
}
while(1){
bzero(buf,sizeof(buf));
read(fd,buf,sizeof(buf));
if (strncmp(buf, "quit", 4) == 0)
break;
printf("buf = %s\n",buf);
}
close(fd);
return 0;
}
3.信号
信号是linux内核中一种软件模拟硬件中断的机制,如果没有linux内核就没有信号,但是会存在中断。用户可以给进程发信号,进程可以给进程发信号,内核可以给进程发信号。进程收到信号的处理方式默认,忽略,捕捉。
①查看Linux中的信号:kill -l
常用的信号:
注:在所有的信号中只有SIGKILL和SIGSTOP不能被捕捉也不能被忽略,其他的信号如果是默认处理的,大部分都是杀死进程。
②用到的函数:signal/kill/raise/alarm
typedef void (*sighandler_t)(int); //信号处理函数的原型
void handle(int signo)
{
//信号处理函数的原型
}
sighandler_t signal(int signum, sighandler_t handler);
功能:注册信号处理函数
#include <signal.h>
参数:
@signum:信号号
@handler:处理函数
SIG_IGN :忽略
SIG_DFL :默认
handle :捕捉
返回值:成功返回handler,失败返回SIG_ERR,置位错误码
示例:
#include <head.h>
#include <signal.h>
void handle(int signo)
{
if (signo == SIGINT)
{
printf("收到了ctrl+c\n");
}
}
int main(int argc, char const *argv[])
{
// 1.默认处理,杀死程序
// if(SIG_ERR == signal(SIGINT,SIG_DFL))
// PRINT_ERR("signal error");
// 2.忽略处理,ctrl+c没有任何反应
// if (SIG_ERR == signal(SIGINT, SIG_IGN))
// PRINT_ERR("signal error");
// 3.捕捉
if (SIG_ERR == signal(SIGINT, handle))
PRINT_ERR("signal error");
while (1);
return 0;
}
int kill(pid_t pid, int sig);
功能:给指定的进程发信号
#include <sys/types.h>
#include <signal.h>
参数:
@pid:进程号
pid>0:给指定pid进程发信号
pid=0: 给当前进程组内的进程发信号
pid = -1 :给所有的进程发信号(必须有权限)
pid <-1 :将pid取反,将信号发给取反之后对应的进程组
@sig:信号号
返回值:成功返回0,失败返回-1置位错误码
int raise(int sig);
功能:给当前进程发信号
#include <sys/types.h>
#include <signal.h>
参数:
@sig:信号号
返回值:成功返回0,失败返回非0
示例:
#include <head.h>
#include <sys/wait.h>
#include <signal.h>
void handle(int signo)
{
printf("get SIGUSR1 signal from child\n");
waitpid(-1,NULL,WNOHANG);
raise(SIGKILL);
}
int main(int argc, char const *argv[])
{
pid_t pid;
pid = fork();
if(pid < 0){
PRINT_ERR("fork error");
}else if(pid ==0){
sleep(5);
printf("子进程退出了\n");
kill(getppid(),SIGUSR1);
}else{
signal(SIGUSR1,handle);
while(1);
}
return 0;
}
闹钟信号alarm:
unsigned int alarm(unsigned int seconds);
功能:在seconds后发送闹钟信号
#include <unistd.h>
参数:
@seconds:倒计时的秒钟
如果在倒计时到0前又重新给seconds赋值,重新计数seconds秒
如果第一次就是seconds赋值为0,就不会发SIGALRM信号了
返回值:返回上一次倒计时为0前的剩余的秒钟,如果是0表示之前没有调闹钟的函数
示例:
#include <head.h>
#include <signal.h>
void handle(int signo)
{
printf("自动出牌了=t\n");
alarm(4); //如果第一次都不输入任何的字符,可以在自动出牌后开启下一次计时
}
int main(int argc, char const *argv[])
{
if (SIG_ERR == signal(SIGALRM, handle))
PRINT_ERR("signal error");
alarm(4);//倒计时+kill(getpid(),SIGALRM)
char ch;
while(1){
ch = getchar();
getchar();
printf("手动出牌是=%c\n",ch);
alarm(4);
}
return 0;
}
二、system V IPC进程间通讯
消息队列 共享内存 信号量
查看相关的命令:ipcs 查看所有的信息(消息队列,共享内存,信号量)
ipcs -q 消息队列
ipcs -m 共享内存
ipcs -s 信号量(信号灯)(semphore)
删除相关的命令: ipcrm -q/-m/-s ID 删除
1.消息队列
①消息队列的原理
②用到的函数:ftok/msgget/msgsnd/msgrcv/msgctl
头文件均为:#include <sys/ipc.h>
#include <sys/msg.h>
key_t ftok(const char *pathname, int proj_id);
功能:获得key值
参数:
@pathname 已经存在的文件路径
@proj_id 获取这个整数的低8bit (key = proj_id,inode,st_dev)
返回值:成功返回 key值,失败返回-1
第二种方法:将key值指定为IPC_PRIVATE ,当IPC对象在亲缘关系进程通信的时候
int msgget(key_t key, int msgflg);
功能:创建IPC对象
参数:
@key IPC_PRIVATE 或 ftok
@msgflg IPC_CREAT | 0666 或 IPC_CREAT | IPC_EXCL | 0666 (判断IPC对象是否存在)
返回值:成功返回ID,失败返回-1
注意:如果对应key值的IPC对象不存在,则创建,如果存在,直接返回IPC对象的ID
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:发送消息
参数:
@msqid 消息队列ID
@msgp 需要发送的消息存放的地址
@msgsz 消息正文的大小
@msgflg 0:阻塞的方式发送 IPC_NOWAIT:非阻塞方式调用
返回值:成功返回0,失败返回-1 (队列的大小限制MSGMNB 16384)
消息结构体定义:
typedef struct{
long msg_type; //消息类型必须在第一个位置,
char mtxt[1024];
...
}msg_t;
正文大小:sizeof(msg_t) - sizeof(long)
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:接收消息
参数:
@msqid 消息队列ID
@msgp 存放接收到的消息
@msgsz 正文大小
@msgtyp 消息类型 , 0: 总是从消息队列中提取第一个消息
@msgflg 0:阻塞的方式接收 IPC_NOWAIT:非阻塞方式调用
返回值:成功返回 接收消息正文大小,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:删除消息队列
参数:
@msgqid 消息队列
@cmd IPC_RMID(删除消息队列) IPC_SET(设置消息队列的属性信息) IPC_STAT(获取消息队列属性信息)
@buf 存放消息队列属性
返回值:成功返回0,失败返回-1
示例:
//发送方
#include <head.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct
{
long msg_type; //消息类型必须在第一个位置,
char name[20];
int age;
char sex;
} msg_t;
int main(int argc, char const *argv[])
{
key_t key;
int msgid;
// 1.获取key
if ((key = ftok("/home/linux", 'y')) == -1)
PRINT_ERR("ftok error");
// 2获取消息队列
if ((msgid = msgget(key, IPC_CREAT | 0664)) == -1)
PRINT_ERR("msg get error");
msg_t m1 = {1, "zhangsan", 50, 'm'};
msgsnd(msgid, &m1, sizeof(msg_t) - sizeof(long), 0);
msg_t m2 = {2, "lisi", 20, 'm'};
msgsnd(msgid, &m2, sizeof(msg_t) - sizeof(long), 0);
msg_t m3 = {3, "wangwu", 18, 'w'};
msgsnd(msgid, &m3, sizeof(msg_t) - sizeof(long), 0);
return 0;
}
//接收方
#include <head.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct
{
long msg_type; //消息类型必须在第一个位置,
char name[20];
int age;
char sex;
} msg_t;
int main(int argc, char const *argv[])
{
key_t key;
int msgid;
// 1.获取key
if ((key = ftok("/home/linux", 'y')) == -1)
PRINT_ERR("ftok error");
// 2获取消息队列
if ((msgid = msgget(key, IPC_CREAT | 0664)) == -1)
PRINT_ERR("msg get error");
msg_t msg;
while(1){
bzero(&msg,sizeof(msg_t));
msgrcv(msgid,&msg,sizeof(msg_t)-sizeof(long),2,0);
printf("name =%s,age = %d,sex = %c\n",
msg.name,msg.age,msg.sex);
}
return 0;
}
2.共享内存
①共享内存内部原理
②用到的函数:shmget/shmat/shmdt/shmctl
头文件均为:#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:获取共享内存段的ID,创建共享内存
参数:
@key IPC_PRIVATE 或 ftok()
@size 申请的共享内存段大小 [4k的倍数]
@shmflg IPC_CREAT | 0666 或 IPC_CREAT | IPC_EXCL
返回值:成功返回ID,失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射共享内存到用户空间
参数:
@shmid 共享内存段ID
@shmaddr NULL:系统自动完成映射
@shmflg SHM_RDONLY:只读 0:读写
返回值:成功返回映射后的地址,失败返回(void *)-1
int shmdt(const void *shmaddr);
功能:撤销映射
参数:
@shmaddr 共享内存映射的地址
注意:当一个进程结束的时候,它映射共享内存,会自动撤销映射
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//shmctl(shmid,IPC_RMID,NULL);
功能:根据命令控制共享内存,删除IPC对象
参数:
@shmid 共享内存段的ID
@cmd IPC_STAT[获取属性],IPC_SET[设置属性],IPC_RMID[删除IPC对象]
@buf 保存属性
返回值:成功返回0,失败返回 -1
注意:当我们调用shmctl删除共享内存的时候,并不会立即删除。只有当共享内存映射次数为0,才会删除共享内存对象
示例:
//发送方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc, char const *argv[])
{
key_t key;
int shmid;
char * addr;
// 1.获取key
if ((key = ftok("/home/linux", 'y')) == -1)
PRINT_ERR("ftok error");
// 2获取共享内存
if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)
PRINT_ERR("shm get error");
addr = shmat(shmid,NULL,0);
if(addr == (void *)-1){
fprintf(stderr,"shm at error");
return -1;
}
while(1){
fgets(addr,4096,stdin);
addr[strlen(addr)-1]='\0';
}
//撤销映射
shmdt(addr);
}
//接收方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc, char const *argv[])
{
key_t key;
int shmid;
char * addr;
// 1.获取key
if ((key = ftok("/home/linux", 'y')) == -1)
PRINT_ERR("ftok error");
// 2获取共享内存
if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)
PRINT_ERR("shm get error");
addr = shmat(shmid,NULL,0);
if(addr == (void *)-1){
fprintf(stderr,"shm at error");
return -1;
}
while(1){
sleep(3);
printf("addr = %s\n",addr);
}
//撤销映射
shmdt(addr);
}
3.进程的同步:信号量(信号灯)
POSIX 线程中的同步用的是无名信号量
进程间的同步使用的是IPC 对象[信号灯集]
信号灯集:信号灯集合,每一个信号灯都可以用来表示一类资源,其值表示资源的个数
用到的函数:sema_init/P/V(自行封装的,源代码如下)
//源代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <head.h>
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) */
};
int init_sem_value(int sem_id,int sem_num,int value)
{
union semun sem_val;
sem_val.val = value;
if(semctl(sem_id,sem_num,SETVAL,sem_val) < 0)
{
PRINT_ERR("semctl error");
}
return 0;
}
int sema_init(int num)
{
key_t key;
int semid;
// 1.获取key
if ((key = ftok("/home", 'u')) == -1)
PRINT_ERR("ftok error");
// 2获取共享内存
if((semid = semget(key,num,IPC_CREAT|0664))==-1)
PRINT_ERR("semget error");
init_sem_value(semid,0,0);
init_sem_value(semid,1,1);
return semid;
}
//P申请资源-1
int P(int sem_id,int sem_num)
{
struct sembuf sem;
sem.sem_num = sem_num;
sem.sem_op = -1;
sem.sem_flg = 0;
if(semop(sem_id,&sem,1) < 0)
{
PRINT_ERR("semp error");
}
}
//V操作释放资源+1
int V(int sem_id,int sem_num)
{
struct sembuf sem;
sem.sem_num = sem_num;
sem.sem_op = 1;
sem.sem_flg = 0;
if(semop(sem_id,&sem,1) < 0)
{
PRINT_ERR("semv error");
}
}
//函数声明
#ifndef __SEM_H__
#define __SEM_H__
int sema_init(int num);
int P(int sem_id,int sem_num);
int V(int sem_id,int sem_num);
#endif
示例:
//发送方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "sem.h"
int main(int argc, char const *argv[])
{
key_t key;
int shmid;
int semid;
char * addr;
//0.初始化信号灯
semid = sema_init(2);
// 1.获取key
if ((key = ftok("/home/linux", 'y')) == -1)
PRINT_ERR("ftok error");
// 2获取共享内存
if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)
PRINT_ERR("shm get error");
addr = shmat(shmid,NULL,0);
if(addr == (void *)-1){
fprintf(stderr,"shm at error");
return -1;
}
while(1){
P(semid,1);
fgets(addr,4096,stdin);
addr[strlen(addr)-1]='\0';
V(semid,0);
}
//撤销映射
shmdt(addr);
}
//接收方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "sem.h"
int main(int argc, char const *argv[])
{
key_t key;
int shmid,semid;
char * addr;
semid = sema_init(2);
// 1.获取key
if ((key = ftok("/home/linux", 'y')) == -1)
PRINT_ERR("ftok error");
// 2获取共享内存
if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)
PRINT_ERR("shm get error");
addr = shmat(shmid,NULL,0);
if(addr == (void *)-1){
fprintf(stderr,"shm at error");
return -1;
}
while(1){
P(semid,0);
printf("addr = %s\n",addr);
V(semid,1);
}
//撤销映射
shmdt(addr);
}
更多推荐
所有评论(0)