在linux下有过种进程间通信的方法:半双工管道,FIFO(命名管道),消息队列,信号量,共享内存,socket等
半双工管道
管道式Linux系统中最古老的进程间通信机制,这里所说的管道是指无名管道(PIPE),它可用于具有亲缘关系进程间的通信.有名管道(FIFO)克服了管道没有名字的限制,因此,除了具有管道所有具有的功能外,它还允许无亲缘关系进程间的通信.Linux的管道主要包括两种:无名管道和有名管道,本文主要介绍这两种管道.
把一个进程连接到另一个进程的一个数据流称为"管道".比如:ls|more.这条命令的作用是列出当前目录下的所有文件盒子目录,如果内容超过了一页则自动进行分页.符号"|"就是shell为ls和more命令简历的一条管道,它将ls的输出直接送进了more的输入.
无名管道特点
#include
#include
#include
#include
#include
int main(void)
{
int result = -1;
int fd[2],nbytes;
pid_t pid;
char string[] = "1111111111111111";
char readbuffer[40];
memset(readbuffer,0,40);
int *write_fd = &fd[1];
int *read_fd = &fd[0];
//建立管道
result = pipe(fd);
if(result == -1)
{
//返回-1失败
printf("建立管道失败\n");
return -2;
}
pid = fork();//分叉进程,返回两次:0表示子进程;>0表示父进程
if(pid == -1)
{
//返回-1表示分叉进程失败
printf("分叉进程失败\n");
return -1;
}
if(pid == 0)
{
//0表示子进程
close(*read_fd);//关闭管道读那端
//写数据
write(*write_fd,string,strlen(string));
return 0;
}
else
{
//表示父进程
close(*write_fd);//关闭写端
nbytes = read(*read_fd,readbuffer,sizeof(readbuffer));
printf("读取字节总数:%d %s\n",nbytes,readbuffer);
}
return 0;
}
#include
#include
#include
#include
#include
#define K 1024
#define WRITELEN (128*K)
int main(void)
{
int result = -1;
int fd[2],nbytes;
pid_t pid;
char string[WRITELEN] = "你好,管道";
char readbuffer[10*K];
memset(readbuffer,0,10*K);
int *write_fd = &fd[1];
int *read_fd = &fd[0];
//建立管道
result = pipe(fd);
if(result == -1)
{
//返回-1失败
printf("建立管道失败\n");
return -2;
}
pid = fork();//分叉进程,返回两次:0表示子进程;>0表示父进程
if(pid == -1)
{
//返回-1表示分叉进程失败
printf("分叉进程失败\n");
return -1;
}
if(pid == 0)
{
//0表示子进程
close(*read_fd);//关闭管道读那端
//写数据
int writeSize = WRITELEN;
result = 0;
while(writeSize>=0)
{
result = write(*write_fd,string,writeSize);
printf("result = %d\n",result);
if(result>0)
{
writeSize -= result;
printf("写入%d字节数据,剩余%d字节数据\n",result,writeSize);
}
else
{
sleep(10);
break;
}
}
return 0;
}
else
{
//表示父进程
close(*write_fd);//关闭写端
while(1)
{
memset(readbuffer,0,sizeof(readbuffer));
nbytes = read(*read_fd,readbuffer,sizeof(readbuffer));
if(nbytes <= 0)
{
printf("数据读取完毕\n");
break;
}
printf("读取字节总数:%d 内容为:%s\n",nbytes,readbuffer);
}
}
return 0;
}
ftok原型如下:
key_t ftok( char * fname, int id )
#include "unpipc.h"
int main(int argc, char **argv)
{
struct stat stat;
if (argc != 2)
err_quit("usage: ftok ");
Stat(argv[1], &stat);
printf("st_dev: %lx, st_ino: %lx, key: %x/n",
(u_long) stat.st_dev,(u_long) stat.st_ino,
Ftok(argv[1], 0x57));
exit(0);
}
程序运行结果:
[cbs@linux svipc]$ ./ftok /tmp/mysql.sock
st_dev: 802, st_ino: 34219, key: 57024219
使用ftok()需要注意的问题:
信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况,信号量是一个特殊的变量,并且只有两个操作可以改变其值:等待(wait)与信号(signal)。
因为在Linux与UNIX编程中,"wait"与"signal"已经具有特殊的意义了(暂不知这特殊意义是啥),所以原始概念:
用于等待(wait)的P(信号量变量) ;
用于信号(signal)的V(信号量变量) ;
这两字母来自等待(passeren:通过,如同临界区前的检测点)与信号(vrjgeven:指定或释放,如同释放临界区的控制权)的荷兰语。
P操作:负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。
操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;
V操作:负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。
操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。
补充:查看共享信息的内存的命令是ipcs [-m|-s|-q] (全部的话是ipcs -a) ;查看共享信息的内存的命令是ipcs [-m|-s|-q]。
函数原型:int semget(key_t key,int nsems,int semflg);
功能描述: 创建一个新的信号量集,或者存取一个已经存在的信号量集。
当调用semget创建一个信号量时,他的相应的semid_ds结构被初始化。ipc_perm中各个量被设置为相应
值:
sem_nsems被设置为nsems所示的值;
sem_otime被设置为0;
sem_ctime被设置为当前时间
参数介绍:
返回值说明:
每个信号量都有一些相关值:
原型:int semctl(int semid,int semnum,int cmd,union semun ctl_arg);
参数介绍:
标准的IPC函数 (注意在头文件 |
IPC_STAT 把状态信息放入ctl_arg.stat中 IPC_SET 用ctl_arg.stat中的值设置所有权/许可权 IPC_RMID 从系统中删除信号量集合 |
单信号量操作 (下面这些宏与sem_num指定的信号量合semctl返回值相关) |
GETVAL 返回信号量的值(也就是semval) SETVAL 把信号量的值写入ctl_arg.val中 GETPID 返回sempid值 GETNCNT 返回semncnt(参考上面内容) GETZCNT 返回semzcnt(参考上面内容) |
全信号量操作 |
GETALL 把所有信号量的semvals值写入ctl_arg.array SETALL 用ctl_arg.array中的值设置所有信号量的semvals |
union semun {
int val; //执行SETVAL命令时使用
struct semid_ds *buf; //在IPC_STAT/IPC_SET命令中使用
unsigned short *array; //使用GETALL/SETALL命令时使用的指针
}
功能:smctl函数依据command参数会返回不同的值。它的一个重要用途是为信号量赋初值,因为进程无法直接对信号量的值进行修改。
在 Linux 下,PV 操作通过调用semop函数来实现,也只有它能对PV进行操作
调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);
返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops大于最大的ops数目)
EACCESS(权限不够)
EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行)
EFAULT(sops指向的地址无效)
EIDRM(信号量集已经删除)
EINTR(当睡眠时接收到其他信号)
EINVAL(信号量集不存在,或者semid无效)
ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构)
ERANGE(信号量值超出范围)
参数介绍:
第一个参数semid 是信号量集合标识符,它可能是从前一次的semget调用中获得的。
第二个参数是一个sembuf结构的数组,每个 sembuf 结构体对应一个特定信号的操作,sembuf结构在,
struct sembuf{
usign short sem_num;/*信号量索引*/
short sem_op;/*要执行的操作*/
short sem_flg;/*操作标志*/
}
sem_num 存放集合中某一信号量的索引,如果集合中只包含一个元素,则sem_num的值只能为0。
----------------------------------------------------------------------------------------------
Sem_op取得值为一个有符号整数,该整数实际给定了semop函数将完成的功能。包括三种情况:
如果sem_op是负数,那么信号量将减去它的值,对应于p()操作。这和信号量控制的资源有关。如果没有使用IPC_NOWAIT,那么调用进程将进入睡眠状态,直到信号量控制的资源可以使用为止。
如果sem_op是正数,则信号量加上它的值。对应于v()操作。这也就是进程释放信号量控制的资源。
最后,如果sem_op是0,那么调用进程将调用sleep(),直到信号量的值为0。这在一个进程等待完全空闲的资源时使用。
----------------------------------------------------------------------------------------------
sem_flag是用来告诉系统当进程退出时自动还原操作,它维护着一个整型变量semadj(信号灯的计数器),可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。若使用SEM_UNDO标志,则操作系统将自动释放该进程持有的信号量,从而使得另外一个进程可以继续工作。若没有这个标志,另外进程将P操作永远阻塞。因此,一般建议使用SEM_UNDo标志。
第三个参数是sembuf组成的数组中索引。参数sops指向由sembuf组成的数组,结构数组中的一员。
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shm_id, int command, struct shmid_ds *buf);
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
#include
#include
#include
#include
#include
static char msg[] = "你好,共享内存~~~\n";
typedef int sem_t;
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
}arg;
//创建信号量
int CreateSem(key_t key,int value)
{
union semun sem;//信号量结构体
sem_t semid;
sem.val = value;
semid = semget(key,1,IPC_CREAT|0666);//获得信号量的ID
if(semid == -1)
{
printf("获得信号量失败\n");
return -1;
}
semctl(semid,0,SETVAL,sem);
return semid;
}
//利用semop()构建基本的P操作
int Sem_V(sem_t semid)
{
struct sembuf sops = {0,1,SEM_UNDO};//对信号量0进行加1操作
return semop(semid,&sops,1);
}
//利用semop()构建基本的V操作
int Sem_P(sem_t semid)
{
struct sembuf sops = {0,-1,SEM_UNDO};//对信号量0进行减1操作
return semop(semid,&sops,1);
}
//销毁信号量
void DestroySem(sem_t semid)
{
union semun sem;//信号量结构体
sem.val = 0;
semctl(semid,0,IPC_RMID,sem);//命令IPC_RMID将给定的信号量销毁
}
int main(void)
{
key_t key;
int semid,shmid;
char i,*shms,*shmc;
struct semid_ds buf;
int value = 0;
char buffer[80];
pid_t p;
struct stat stat_info;
char path[256];
memset(path,0,256);
strcpy(path,"/etc/environment");
//sprintf(path, "/etc/environment", (char*)getenv("HOME"));
printf("path = %s\n",path);
if(stat(path,&stat_info)!=0)
{
printf("Error\n");
return -1;
}
key=ftok(path, 'a');//生成键值
printf("key = %d\n",key);
shmid = shmget(key,1024,IPC_CREAT|0604);//获得共享内存,大小1024
printf("共享内存地址为:0x%08x\n",shmid);
semid = CreateSem(key,0);//建立信号量
p = fork();
if(p>0)
{
//父进程
shms = (char*)shmat(shmid,0,0);//挂载共享内存
//Sem_V(semid);//减小信号量
memset(shms,0,1024);
memcpy(shms,msg,strlen(msg)+1);//复制内容
printf("11:信号量值=%d\n",semctl(semid,0,GETVAL));
sleep(10);
printf("12:信号量值=%d\n",semctl(semid,0,GETVAL));
Sem_V(semid);//获得共享内存信号量=======V操作1
shmdt(shms);//摘除共享内存
Sem_P(semid);//减小信号量=======P操作1 会等待子进程中V操作2
DestroySem(semid);//销毁信号量
printf("父进程End\n");
}
else if(p==0)
{
//子进程
printf("21:信号量值=%d\n",semctl(semid,0,GETVAL));
Sem_P(semid);//减小信号量=======P操作2 会等待父进程中V操作1
printf("22:信号量值=%d\n",semctl(semid,0,GETVAL));
shmc = (char*)shmat(shmid,0,0);//挂载共享内存
printf("共享内存的值为:%s\n",shmc);
shmdt(shmc);//摘除共享内存
printf("子进程End\n");
Sem_V(semid);//=======V操作2
return 0;
}
return 0;
}