LinuxIPC,FIFO和shm


偶尔发现了调试函数需要的一个功能:根据反馈的信息,调整一些简单的参数。有两个要求:1、不中断程序,又不会每次都需要暂停等待输入;2、不需要重新编译,程序运行。这样需求基本上就是多进程、监听加IPC就ok。

首先想到的是使用环境变量。基本使用的是getenv()函数,获得相应的环境变量即可。在使用过程中,发现根本就不可能。因为环境变量是用户级别的,而每个console都相当于一个用户,不能进行环境变量沟通。

随后看到了IPC这个概念,遂查了一下,发现有管道、socket、信号、共享内存、消息队列等。

用到的管道和共享内存都是基于共享内存的。不得不说linux设计的太聪明了,膜拜这种抽象能力。程序中的文件抽象起来就是一块内存嘛,而文件又天生有共享的功能,伟大啊。

首先就是用shm,因为号称速度快。是在/dev/shm/下新建一个文件,这个文件夹是完全存在于内存中(有时会用到swap)的。不会有太多磁盘IO,所以会比较快。问题在于需要使用锁,才能保证读写的原子性。甚至是读到不完整的数据。所以不爱啊。主要就是shmget()、shmat()、shmctl()和shmdt()。锁则使用很相似的semget()、semctl()、semop()等。效果不错但是逻辑很复杂。

PV操作:

 

int p(int semid){
	struct sembuf op;
	op.sem_num = 0;
	op.sem_op = -1;
	return semop(semid, &op, 1);
}

int v(int semid){
	struct sembuf op;
	op.sem_num = 0;
	op.sem_op = 1;
	return semop(semid, &op, 1);
}

Sender:

 

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>
#include"statics.h"
#include"pv.h"

int main(){
	int shmid, semid;
	key_t key;
	message *msg;
	if((key = ftok(PATH,ID))==-1){
		printf("ftok error");
		return -1;
	}

	if((shmid = shmget(key, 4096, IPC_CREAT)) == -1){
		printf("shm error");
		return -1;
	}

	if((msg = shmat(shmid,NULL,0)) == (message*)-1){
		printf("shmat error");
		return -1;
	}
	
	if((semid = semget(key, 1, IPC_CREAT)) == -1){
		printf("sem error");
		return -1;
	}

	union semun option;
	option.val = 1;
	semctl(semid, 0, SETVAL, option);

	p(semid);
	msg->type = 0;
	msg->length = 1;
	((int*)msg->data)[0] = 2;
	v(semid);
	
	shmdt(msg);
	return 0;
}

Reciever:

 

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>
#include"statics.h"
#include"pv.h"

int main(){
	int shmid, semid;
	key_t key;
	message *msg;
	if((key = ftok(PATH,ID))==-1){
		perror("ftok error");
		return -1;
	}

	if((shmid = shmget(key, 4096, IPC_CREAT)) == -1){
		perror("shm error");
		return -1;
	}

	if((msg = shmat(shmid,NULL,0)) == (message*)-1){
		perror("shmat error");
		return -1;
	}
	
	if((semid = semget(key, 1, IPC_CREAT)) == -1){
		perror("sem error");
		return -1;
	}

	union semun option;
	option.val = 1;
	semctl(semid, 0, SETVAL, option);

	while(1){
		p(semid);
		if(0 == msg->type){
			int *arr = (int*)msg->data;
			if(msg->length == 1){
				int val = arr[0];
				if(2 == val)
					break;
			}
		}
		v(semid);

		sleep(1);

		printf(".");
	}
	
	shmdt(msg);

	printf("exit\n");
	return 0;
}
还存在另一个问题,就是sem的释放时机和位置,因为双方的存在时间不一定,所以会有问题。

随后就是使用命名管道,命名管道是一个可见的文件,在运行结束后,管道是空的,文件大小也是0,。应该也是纯内存行为。命名管道主要是mkfifo()、open()、close()、read()、write()了,是文件的使用模式。虽然操作没有原子性(可能会写一部分,再写一部分数据),但是文件的锁机制应该是生效的。不会有读入旧数据的危险。使用起来也没显简单太多了。

Sender:

 

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

int main(){
	message msg;
	int file, count;

	if((mkfifo(PATH, O_CREAT) < 0)&&(EEXIST != errno)){
		perror("can't create fifo");
		return -1;		
	}

	file = open(PATH, O_WRONLY, 0);
	if(-1 == file){
		perror("file wrong");	
	}
	msg.type = 0;
	msg.length = 1;
	((int*)msg.data)[0] = 2;

	count = write(file, &msg, sizeof(msg));
	if(-1 == count){
		perror("can't write");
	}
	printf("count: %d\n", count);

	close(file);
	return 0;
}

Reciever:

 

 

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

int main(){
	message msg;
	int file, count;

	if((mkfifo(PATH, O_CREAT) < 0)&&(EEXIST != errno)){
		perror("can't create fifo");
		return -1;		
	}

	file = open(PATH, O_RDONLY|O_NONBLOCK, 0);


	while(1){
		sleep(1);

		printf(".");

		count = read(file, &msg, sizeof(msg));
		if(-1 == count && EAGAIN == errno){
			continue;
		}else{
			if(0 == msg.type){
			int *arr = (int*)msg.data;
			if(msg.length == 1){
				int val = arr[0];
				if(2 == val)
					break;
			}
		}
		}

	}

	printf("exit\n");
	return 0;
}
问题又在于文件系统中会真的出现一个管道文件,大小比较美好。

过程中遇到了一些很小又有点意思的问题:

0.fork()函数,这个函数复制了栈的内容,也就是说把自己运行的空间复制了,导致会产生一次调用了两个函数的效果。很有意思,所以会返回两个值0子线程,val(>0)主线程。很有意思。而且整个的运行方式和MPI极像,终于明白MPI为什么会有那种不合理设计了。

1.printf()不输出,中间使用了printf('.')表征接受端的运行情况。但是基本不会有输出,直到进程结束。原因是printf是按行flush的,填满一行的点时间太长,所以会看不到一个点。解决方法有两个:使用stderr输出;fflush(stdout)强制flush。

2.头文件在make的时候多次include,出现多次定义的情况。解决办法是:在头文件中仅声明extern变量,另建一个c文件,专门定义变量。不需要加extern,但注意const等修饰要一致。

明天做一下速度测试,还没想好方法。思考ing...

相关内容

    暂无相关文章