Linux下C语言编程--进程通信、消息管理

Linux 下的进程通信 (IPC) 

1.POSIX
无名信号量  
2.System V
信号量  
3.System V
消息队列  
4.System V
共享内存  

--------------------------------------------------------------------------------
1
POSIX 无名信号量       如果你学习过操作系统 , 那么肯定熟悉 PV 操作了 .PV 操作是原子操作 . 也就是操作是不可以中断的 , 在一定的时间内 , 只能够有一个进程的代码在 CPU 上面执行 . 在系统当中 , 有时候为了顺利的使用和保护共享资源 , 大家提出了信号的概念 假设我们要使用一台打印机 , 如果在同一时刻有两个进程在向打印机输出 , 那么最终的结果会是什么呢 . 为了处理这种情况 ,POSIX 标准提出了有名信号量和无名信号量的概念 , 由于 Linux 只实现了无名信号量 , 我们在这里就只是介绍无名信号量了 信号量的使用主要是用来保护共享资源 , 使的资源在一个时刻只有一个进程所拥有 . 为此我们可以使用一个信号灯 . 当信号灯的值为某个值的时候 , 就表明此时资源不可以使用 . 否则就表 > 示可以使用 为了提供效率 , 系统提供了下面几个函数  
POSIX
的无名信号量的函数有以下几个

#include

int sem_init(sem_t *sem,int pshared,unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem);

sem_init
创建一个信号灯 , 并初始化其值为 value.pshared 决定了信号量能否在几个进程间共享 . 由于目前 Linux 还没有实现进程间共享信号灯 , 所以这个值只能够取 0. sem_destroy 是用来删除信号灯的 .sem_wait 调用将阻塞进程 , 直到信号灯的值大于 0. 这个函数返回的时候自动的将信号灯的值的件一 .sem_post sem_wait 相反 , 是将信号灯的内容加一同时发出信号唤醒等待的进程 ..sem_trywait sem_wait 相同 , 不过不阻塞的 , 当信号灯的值为 0 的时候返回 EAGAIN, 表示以后重试 .sem_getvalue 得到信号灯的值
由于 Linux 不支持 , 我们没有办法用源程序解释了
这几个函数的使用相当简单的 . 比如我们有一个程序要向一个系统打印机打印两页 . 我们首先创建一个信号灯 , 并使其初始值为 1, 表示我们有一个资源可用 . 然后一个进程调用 sem_wait 由于这个时候信号灯的值为 1, 所以这个函数返回 , 打印机开始打印了 , 同时信号灯的值为 如果第二个进程要打印 , 调用 sem_wait 时候 , 由于信号灯的值为 0, 资源不可用 , 于是被阻塞了 . 当第一个进程打印完成以后 , 调用 sem_post 信号灯的值为 1 , 这个时候系统通知第二个进程 , 于是第二个进程的 sem_wait 返回 . 第二个进程开始打印了
不过我们可以使用线程来解决这个问题的 . 我们会在后面解释什么是线程的 . 编译包含上面这几个函数的程序要加上  -lrt 选贤 , 以连接 librt.so  
2
System V 信号量   为了解决上面哪个问题 , 我们也可以使用 System V 信号量 . 很幸运的是 Linux 实现了 System V 信号量 . 这样我们就可以用实例来解释了 . System V 信号量的函数主要有下面几个

#include 
#include 
#include 

key_t ftok(char *pathname,char proj);
int semget(key_t key,int nsems,int semflg);
int semctl(int semid,int semnum,int cmd,union semun arg);
int semop(int semid,struct sembuf *spos,int nspos);

struct sembuf {
short sem_num; /*
使用那一个信号 */
short sem_op; /*
进行什么操作 */
short sem_flg; /*
操作的标志 */
};


ftok
函数是根据 pathname proj 来创建一个关键字 .semget 创建一个信号量 . 成功时返回信号的 ID,key 是一个关键字 , 可以是用 ftok 创建的也可以是 IPC_PRIVATE 表明由系统选用一个关键字 . nsems 表明我们创建的信号个数 .semflg 是创建的权限标志 , 和我们创建一个文件的标志相同
semctl
对信号量进行一系列的控制 .semid 是要操作的信号标志 ,semnum 是信号的个数 ,cmd 是操作的命令 . 经常用的两个值是 :SETVAL( 设置信号量的值 ) IPC_RMID( 删除信号灯 ).arg 是一个给 cmd 的参数
semop
是对信号进行操作的函数 .semid 是信号标志 ,spos 是一个操作数组表明要进行什么操作 ,nspos 表明数组的个数 如果 sem_op 大于 0, 那么操作将 sem_op 加入到信号量的值中 , 并唤醒等待信号增加的进程 如果为 0, 当信号量的值是 0 的时候 , 函数返回 , 否则阻塞直到信号量的值为 0.  如果小于 0, 函数判断信号量的值加上这个负值 . 如果结果为 0 唤醒等待信号量为 0 的进程 , 如果小与 0 函数阻塞 . 如果大于 0, 那么从信号量里面减去这个值并返回
下面我们一以一个实例来说明这几个函数的使用方法 . 这个程序用标准错误输出来代替我们用的打印机

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PERMS S_IRUSR|S_IWUSR

void init_semaphore_struct(struct sembuf *sem,int semnum,
int semop,int semflg)
{
/*
初始话信号灯结构 */
sem->sem_num=semnum;
sem->sem_op=semop;
sem->sem_flg=semflg;
}

int del_semaphore(int semid)
{
/*
信号灯并不随程序的结束而被删除 , 如果我们没删除的话 ( 1 改为 0)
可以用 ipcs 命令查看到信号灯 , ipcrm 可以删除信号灯的
*/
#if 1
return semctl(semid,0,IPC_RMID);
#endif
}

int main(int argc,char **argv)
{
 char buffer[MAX_CANON],*c;
 int i,n;
 int semid,semop_ret,status;
 pid_t childpid;
 struct sembuf semwait,semsignal;

 if((argc!=2)||((n=atoi(argv[1]))<1))
  {
fprintf(stderr,"Usage:%s number/n/a",argv[0]);
exit(1);
  }
 
/*
使用 IPC_PRIVATE  表示由系统选择一个关键字来创建   */
/*
创建以后信号灯的初始值为 0 */
 if((semid=semget(IPC_PRIVATE,1,PERMS))==-1)
   {
fprintf(stderr,"[%d]:Acess Semaphore Error:%s/n/a",
getpid(),strerror(errno));
exit(1);
   }

/* semwait
是要求资源的操作 (-1) */
init_semaphore_struct(&semwait,0,-1,0);

/* semsignal
是释放资源的操作 (+1) */
init_semaphore_struct(&semsignal,0,1,0);

/*
开始的时候有一个系统资源 ( 一个标准错误输出 ) */
if(semop(semid,&semsignal,1)==-1)
 {
fprintf(stderr,"[%d]:Increment Semaphore Error:%s/n/a",
getpid(),strerror(errno));
if(del_semaphore(semid)==-1)
        fprintf(stderr,"[%d]:Destroy Semaphore Error:%s/n/a",
                getpid(),strerror(errno));
exit(1);
 }

/*
创建一个进程链 */
for(i=0;i
 if(childpid=fork()) break;

sprintf(buffer,"[i=%d]-->[Process=%d]-->[Parent=%d]-->[Child=%d]/n",
i,getpid(),getppid(),childpid);
c=buffer;

/*
这里要求资源 , 进入原子操作 */
while(((semop_ret=semop(semid,&semwait,1))==-1)&&(errno==EINTR));
if(semop_ret==-1)
 {
fprintf(stderr,"[%d]:Decrement Semaphore Error:%s/n/a",
   getpid(),strerror(errno));
 }
else
 {
while(*c!='/0')fputc(*c++,stderr);
/*
原子操作完成 , 赶快释放资源 */
while(((semop_ret=semop(semid,&semsignal,1))==-1)&&(errno==EINTR));
if(semop_ret==-1)
         fprintf(stderr,"[%d]:Increment Semaphore Error:%s/n/a",
                        getpid(),strerror(errno));
 }

/*
不能够在其他进程反问信号灯的时候 , 我们删除了信号灯 */
while((wait(&status)==-1)&&(errno==EINTR));
/*
信号灯只能够被删除一次的 */
if(i==1)
 if(del_semaphore(semid)==-1)
        fprintf(stderr,"[%d]:Destroy Semaphore Error:%s/n/a",
                        getpid(),strerror(errno));
exit(0);


信号灯的主要用途是保护临界资源 ( 在一个时刻只被一个进程所拥有 ). 
3
SystemV 消息队列   为了便于进程之间通信 , 我们可以使用管道通信  SystemV 也提供了一些函数来实现进程的通信 . 这就是消息队列

  #include 
  #include 
  #include 

  int msgget(key_t key,int msgflg);
  int msgsnd(int msgid,struct msgbuf *msgp,int msgsz,int msgflg);
  int msgrcv(int msgid,struct msgbuf *msgp,int msgsz,
long msgtype,int msgflg);
  int msgctl(Int msgid,int cmd,struct msqid_ds *buf);

struct msgbuf {
long msgtype;   /*
消息类型 */
....... /*
其他数据类型 */
}

msgget
函数和 semget 一样 , 返回一个消息队列的标志 .msgctl semctl 是对消息进行控制 . msgsnd msgrcv 函数是用来进行消息通讯的 .msgid 是接受或者发送的消息队列标志 . msgp 是接受或者发送的内容 .msgsz 是消息的大小 结构 msgbuf 包含的内容是至少有一个为 msgtype. 其他的成分是用户定义的 . 对于发送函数 msgflg 指出缓冲区用完时候的操作 . 接受函数指出无消息时候的处理 . 一般为 0.  接收函数 msgtype 指出接收消息时候的操作
如果 msgtype=0, 接收消息队列的第一个消息 . 大于 0 接收队列中消息类型等于这个值的第一个消息 . 小于 0 接收消息队列中小于或者等于 msgtype 绝对值的所有消息中的最小一个消息 我们以一个实例来解释进程通信 . 下面这个程序有 server client 组成 . 先运行服务端后运行客户端
服务端  server.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define   MSG_FILE "server.c"
#define   BUFFER 255
#define   PERM S_IRUSR|S_IWUSR

struct msgtype {
long mtype;
char buffer[BUFFER+1];
};

int main()
{
   struct msgtype msg;
   key_t key;
   int msgid;
   
   if((key=ftok(MSG_FILE,'a'))==-1)
    {
fprintf(stderr,"Creat Key Error:%s/a/n",strerror(errno));
exit(1);
    }
  
  if((msgid=msgget(key,PERM|IPC_CREAT|IPC_EXCL))==-1)
    {
fprintf(stderr,"Creat Message  Error:%s/a/n",strerror(errno));
exit(1);
    }
  
  while(1)
   {
msgrcv(msgid,&msg,sizeof(struct msgtype),1,0);
fprintf(stderr,"Server Receive:%s/n",msg.buffer);
msg.mtype=2;
msgsnd(msgid,&msg,sizeof(struct msgtype),0);
   }
  exit(0);
}


--------------------------------------------------------------------------------

客户端 (client.c)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define   MSG_FILE "server.c"
#define   BUFFER 255
#define   PERM S_IRUSR|S_IWUSR

struct msgtype {
long mtype;
char buffer[BUFFER+1];
};

int main(int argc,char **argv)
{
   struct msgtype msg;
   key_t key;
   int msgid;
   
   if(argc!=2)
    {
fprintf(stderr,"Usage:%s string/n/a",argv[0]);
exit(1);
    }

   if((key=ftok(MSG_FILE,'a'))==-1)
    {
fprintf(stderr,"Creat Key Error:%s/a/n",strerror(errno));
exit(1);
    }
  
  if((msgid=msgget(key,PERM))==-1)
    {
fprintf(stderr,"Creat Message  Error:%s/a/n",strerror(errno));
exit(1);
    }

  msg.mtype=1;
  strncpy(msg.buffer,argv[1],BUFFER);
  msgsnd(msgid,&msg,sizeof(struct msgtype),0); 
  memset(&msg,'/0',sizeof(struct msgtype));
  msgrcv(msgid,&msg,sizeof(struct msgtype),2,0);
  fprintf(stderr,"Client receive:%s/n",msg.buffer);
  exit(0);
}  

注意服务端创建的消息队列最后没有删除 , 我们要使用 ipcrm 命令来删除的
4
SystemV 共享内存   还有一个进程通信的方法是使用共享内存 .SystemV 提供了以下几个函数以实现共享内存

#include 
#include 
#include 

int shmget(key_t key,int size,int shmflg);
void *shmat(int shmid,const void *shmaddr,int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid,int cmd,struct shmid_ds *buf);

shmget
shmctl 没有什么好解释的 .size 是共享内存的大小 . shmat 是用来连接共享内存的 .shmdt 是用来断开共享内存的 . 不要被共享内存词语吓倒 , 共享内存其实很容易实现和使用的 .shmaddr,shmflg 我们只要用 0 代替就可以了 . 在使用一个共享内存之前我们调用 shmat 得到共享内存的开始地址 , 使用结束以后我们使用 shmdt 断开这个内存

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PERM S_IRUSR|S_IWUSR

int main(int argc,char **argv)
{
 
 int shmid;
 char *p_addr,*c_addr;
 if(argc!=2)
  {
fprintf(stderr,"Usage:%s/n/a",argv[0]);
exit(1);
  }

 if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1)
  {
fprintf(stderr,"Create Share Memory Error:%s/n/a",strerror(errno));
exit(1);
  }
 if(fork())
  {
p_addr=shmat(shmid,0,0);
memset(p_addr,'/0',1024);
strncpy(p_addr,argv[1],1024);
exit(0);
  }
 else
  {
c_addr=shmat(shmid,0,0);
printf("Client get %s",c_addr);
exit(0);
  } 


这个程序是父进程将参数写入到共享内存 , 然后子进程把内容读出来 . 最后我们要使用 ipcrm 释放资源的 . 先用 ipcs 找出 ID 然后用 ipcrm shm ID 删除 .   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值