Linux系统编程-进程间通信

目录

一、进程间通信

二、管道通信原理

无名管道

1、特点:

2、函数原型

3、实例:父进程写数据,子进程读数据

命名管道

1、简介

2、特点

3、函数原型

4、实例:创建命名管道

 5、open

6、实例:一个进程写数据,另一个读数据

三、消息队列

 1、简介

2、特点

3、函数原型

4、实例:一个进程发送,另一个接收

5、ftok函数

四、共享内存 

1、函数原型

2、共享内存相关指令

3、实例:创建一个共享内存,进程1写数据进去,进程2读数据出来

五、信号

1、简介

2、信号的处理

3、常见指令

4、信号忽略

5、信号处理函数

6、利用程序发送kill指令

7、sigaction函数-信号量携带信息(检查或修改与指定信号相关联的处理动作)

六、信号量(不涉及数据)

1、函数原型

2、实例:子进程放信号量,父进程取信号量之后再放信号量


一、进程间通信

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

二、管道通信原理

无名管道

1、特点:

  • 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
  • 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
  • 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。不存在磁盘中。

2、函数原型

int pipe(int fd[2]);

参数说明:数组

返回值:若成功返回0,失败返回-1 

当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开

关闭管道时,close(fd[0]);close(fd[1]);

3、实例:父进程写数据,子进程读数据

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main()
{
	int fd[2];//两个文件标识符
	int pid;//进程标识符
	char buf[128];//数据存储区

    //创建管道失败,返回-1
	if(pipe(fd)==-1){
		printf("create pipe failed\n");
	}
	
    //创建子进程
	pid = fork();
    //创建子进程失败
	if(pid<0){
		printf("create child failed\n");
	}
    //父进程
	else if(pid>0){
		printf("this is father\n");
		close(fd[0]);//关闭读端
		write(fd[1],"hello from dad",strlen("hello from dad"));//写数据到管道
		wait();//等待子进程关闭
	}
    //子进程
	else{
		printf("this is child\n");
		close(fd[1]);//关闭写端
		read(fd[0],buf,128);//读数据从管道
		printf("read from dad:%s\n",buf);
		exit(0);//子进程退出
	}
	return 0;
}

命名管道

1、简介

FIFO,是一种文件类型

2、特点

  • FIFO可以在无关的进程间进行通信,而不是像无名管道只能在父子或者兄弟进程间;
  • FIFO有路径名,以一种特殊设备文件形式存在于文件系统中。 

3、函数原型

int mkfifo(const char* pathname,mode_t mode);

参数说明:

pathname:路径名

mode:与open函数的mode相同

FIFO可以使用write、open等一般的文件I/O函数来操作。 

返回值:

成功返回0,失败返回-1

4、实例:创建命名管道

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

int main()
{
	if((mkfifo("./file",0600))==-1 && errno!=EEXIST){
		printf("mkfifo failed\n");
		perror("why");
	}
	return 0;
}

 5、open

当open一个FIFO时,是否设置非阻塞标志(O_NONBLOCK):

①默认不设置,当只读open时,会阻塞,直到有一个进程写打开这个FIFO;同样当只写open时,会阻塞,直到有一个进程读打开这个FIFO;

②设置非阻塞后,只读open立即返回,只写open则出错立即返回-1

6、实例:一个进程写数据,另一个读数据

write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

int main()
{
	char *str ="message from fifo";

	int fd = open("./file",O_WRONLY);//只写
	printf("write success\n");

	write(fd,str,strlen(str));
	
	close(fd);
	return 0;
}

 read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>

int main()
{
	char buf[30] = {0};
    
    //创建管道
	if((mkfifo("./file",0600))==-1 && errno!=EEXIST){
		printf("mkfifo failed\n");
		perror("why");
	}

	int fd = open("./file",O_RDONLY);//只读
	printf("open success\n");

	int readsize = read(fd,buf,30);
	printf("read %d byte from fifo,context:%s\n",readsize,buf);

	close(fd);
	return 0;
}

打开两个终端,先运行read和write其中一个,会阻塞,此时运行另一个后,才会成功

 运行结果:

写端:write success
读端:open success
           read 17 byte from fifo,context:message from fifo

三、消息队列

 1、简介

消息队列就是存放在内核中的消息链表。一个消息队列由一个标识符(即队列ID来标识)。

2、特点

  • 消息具有特定格式
  • 消息队列存在于内核中,不会随着进程的结束而删除
  • 消息队列可以实现消息的随机查询,因为是链表

3、函数原型

 #include <sys/msg.h>
①创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
②添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
③读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
④控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf); 

4、实例:一个进程发送,另一个接收

发送端——send.c

#include <stdio.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>

//结构体{数据类型,数据}
struct msgbuf{
	long mtype;
	char mtext[128];
};

int main()
{
	struct msgbuf sendbuf={888,"this is message from que\n"};//创建数据结构体

    //创建消息队列(key,flag|权限)
	int msgID = msgget(0x1234,IPC_CREAT|0777);
	if(msgID == -1){
		printf("get que failed\n");
	}

    //发送数据向消息队列(队列标识符,结构体指针,数据长度,flag)
	msgsnd(msgID,&sendbuf,strlen(sendbuf.mtext),0);

	return 0;
}

接收端——get.c

#include <stdio.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>

struct msgbuf{
	long mtype;
	char mtext[128];
};

int main()
{
	struct msgbuf readbuf;	

	int msgID = msgget(0x1234,IPC_CREAT|0777);
	if(msgID == -1){
		printf("get que failed\n");
	}

    //读取数据(消息队列标识符,结构体指针,数据长度,数据类型,flag)
	msgrcv(msgID,&readbuf,sizeof(readbuf.mtext),888,0);
	printf("read from que:%s\n",readbuf.mtext);

	return 0;
}

 打开两个终端,先运行get和send其中一个,会阻塞,此时运行另一个后,才会成功

 运行结果:

发送端:
接收端:read from que:this is message from que

5、ftok函数

作用: 将文件的索引节点号取出,前面加上子序号得到key_t的返回值。

如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。

查询文件索引节点号的方法是: ls -i

函数原型:

key_t ftok( const char * fname, int id )

fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:

key_t key;

key = ftok(".", 1); 这样就是将fname设为当前目录。

#include <sys/types.h>

#include <sys/ipc.h>

key_t key;

key = ftok(".",'s');

printf("key=%x\n",key);

int msgID = msgget(key,IPC_CREAT|0777) ;

运行结果:key=7305631a

 6、删除队列

int msgctl(int msqid, int cmd, struct msqid_ds *buf); 

参数说明:

msqid:消息队列标识符

cmd:命令(IPC_RMID-删除)

实例:

msgctl(msgID,IPC_RMID,NULL);

四、共享内存 

共享内存(Shared Memory),指两个或多个进程共享一个指定的存储区。

1、函数原型

包含头文件

#include <sys/ipc.h>

#include <sys/shm.h>
① 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
 int shmget(key_t key, size_t size, int flag);
②连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
 void *shmat(int shm_id, const void *addr, int flag);
③断开与共享内存的连接:成功返回0,失败返回-1
 int shmdt(void *addr); 
④控制共享内存的相关信息:成功返回0,失败返回-1
 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

2、共享内存相关指令

查看所有共享内存:ipcs -m

 删除某一个共享内存:ipcrm -m (shmid)

3、实例:创建一个共享内存,进程1写数据进去,进程2读数据出来

写端:shmw.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>


int main()
{
	int shmid;//共享内存ID
	char *shmaddr;//映射的地址

	key_t key;
	key = ftok(".",1);

	shmid = shmget(key,1024*4,IPC_CREAT|0666);//shmget创建共享内存,返回ID
	if(shmid == -1){
		printf("shmget failed\n");
		exit(-1);
	}

	shmaddr = shmat(shmid,0,0);//连接共享内存到当前进程的地址空间
	printf("shmat ok\n");

	strcpy(shmaddr,"wydhh");//使用strcpy写入数据

	sleep(4);//休眠等待另一个进程读取
	shmdt(shmaddr);//断开与共享内存的连接
	shmctl(shmid,IPC_RMID,0);//删除共享内存

	printf("quit\n");

	return 0;
}

读端:shmr.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>


int main()
{
	int shmid;
	char *shmaddr;

	key_t key;
	key = ftok(".",1);

	shmid = shmget(key,1024*4,0);//读数据,flag置0
	if(shmid == -1){
		printf("shmget failed\n");
		exit(-1);
	}

	shmaddr = shmat(shmid,0,0);
	printf("shmat ok\n");

	printf("data:%s\n",shmaddr);//读数据

	shmdt(shmaddr);//断开连接

	printf("quit\n");

	return 0;
}

运行结果:

写端:shmat ok
           quit

读端:shmat ok
           data:wydhh
           quit

五、信号

在Linux中,信号就是软件中断。信号为Linux提供了一种处理异步事件的方法。比如终端输入ctrl+c来中断程序。

1、简介

每个信号都有名字和编号,以"SIG"开头,例如“SIGKILL”。可以使用kill -l来查看信号的序号和名称。

2、信号的处理

①忽略,有两种不可忽略(SIGKILL和SIGSTOP)

②捕捉,收到某个信号需要做什么

③系统默认,一般来说,系统直接杀死进程

3、常见指令

使用kill 9 PID杀死进程

4、信号忽略

signal(信号名,SIG_IGN);

5、信号处理函数

//本程序修改ctrl+c退出为打印两行说明
#include <signal.h>
#include <stdio.h>

void handler(int signum)//处理函数(信号序号)
{
	printf("get signum=%d\n",signum);
	printf("don't quit\n");
}

int main()
{
	signal(SIGINT,handler);//信号处理函数,(信号名,处理函数)

	while(1);
	return 0;
}

6、利用程序发送kill指令

#include <signal.h>
#include <stdio.h>
#include <sys/types.h>

int main(int argc,char **argv)//信号序号,进程ID
{
	int signum;
	int pid;

	signum = atoi(argv[1]);//ascii转int,信号序号
	pid = atoi(argv[2]);//进程id

	printf("num=%d,pid=%d\n",signum,pid);

	kill(pid,signum);//发送指令
	printf("send kill ok\n");
	return 0;
}

//-----------另一种-------//不用kill函数
char cmd[128] = {0};
sprintf(cmd,"kill -%d %d",signum,pid);//将这一串命令封装成cmd
system(cmd);//使用system函数发送出去

7、sigaction函数-信号量携带信息(检查或修改与指定信号相关联的处理动作

函数原型:

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

参数说明:

signum:指出要捕获的信号类型

act:指定新的信号处理方式

oldact:指向当前的处理器,若无意获取此信息,可以指定为NULL。

struct aigaction结构体:

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}
  • sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
  • sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
  • sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。 

SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

直接整实例

发送端:

#include <stdio.h>
#include <signal.h>

int main(int argc,char **argv)
{
	int signum;//信号序号
	int pid;//进程ID

	signum = atoi(argv[1]);
	pid = atoi(argv[2]);

	union sigval value;
	value.sival_int = 100;//指定数据

	sigqueue(pid,signum,value);//发送数据
	printf("done\n");

	return 0;
}

 接收端:

#include <signal.h>
#include <stdio.h>

void handler(int signum,siginfo_t *info,void *context)//处理函数(信号量序号,内容,内容有无)
{
	printf("get 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("send's pid=%d\n",info->si_pid);
	}

}

int main()
{
	struct sigaction act;//结构体
	printf("pid=%d\n",getpid());//打印当前进程的ID

	act.sa_sigaction = handler;//结构体的sigaction指向信号量处理handler函数
	act.sa_flags = SA_SIGINFO;//结构体的flag

	sigaction(SIGUSR1,&act,NULL);//接收函数,(信号量序号,结构体,空)
	while(1);

	return 0;

}

六、信号量(不涉及数据)

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

临界资源:一次仅允许一个进程使用的资源被称为临界资源

现在有一个房子(临界资源),许多人(进程访问)要进去,门上有锁,门口有钥匙(信号量),每次只能进一个人(一个进程访问);P操作(拿锁);V操作(放锁)

Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

1、函数原型

头文件:#include <sys/sem.h>
①创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);

参数说明:

key:使用ftok获取

num:信号量集中信号量的个数

flag:如果没有就创建,有就调用
②对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);  
③控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);

参数说明:

semid:信号量集id

num:操作第几个数

cmd:很多自行查阅,SETVAL设置信号量初值

联合体,用于semctl初始化

union semun
{
     int  val; /*for SETVAL*/
     struct semid_ds *buf;
     unsigned short  *array;
}

2、实例:子进程放信号量,父进程取信号量之后再放信号量

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

//联合体
union semun{
	int val;//给setval赋值
	struct semid_ds *buf;
	unsigned short *array;
	struct seminfo *__buf;
};
//P操作,取钥匙
void pGetKey(int id){
	struct sembuf set;//实例化联合体命名为set
	
	set.sem_num = 0;
	set.sem_op = -1;//信号量(钥匙)数量的操作,取就要-1
	set.sem_flg = SEM_UNDO;
	
	semop(id,&set,1);
	printf("getkey\n");
}
void vPutKey(int id){
	struct sembuf set;
	
	set.sem_num = 0;
	set.sem_op = 1;//信号量(钥匙)数量的操作,放就要+1
	set.sem_flg = SEM_UNDO;
	
	semop(id,&set,1);
	printf("putkey\n");
}
	
int main(int argc,char const *argv[])
{
	key_t key;
	int semid;

	key = ftok(".",2);

	semid = semget(key,1,IPC_CREAT|0666);
    //1:信号量集合中有一个信号量
    //获取/创建信号量

//-----初始化信号量集-------//
	union semun initsem;
	initsem.val = 0;//信号量集中信号量个数

	semctl(semid,0,SETVAL,initsem);
    //0:操作第0个信号量
    //SETVAL设置信号量值,设置为inisem
//-----初始化信号量集-------//
	
	int pid = fork();
	if(pid >0){
		pGetKey(semid);//拿锁
		printf("this is father\n");
		vPutKey(semid);//放锁
	}
	else if(pid == 0){
		printf("this is child\n");
		vPutKey(semid);//放锁
	}
	else{
		printf("fork error\n");
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值