Linux环境:C编程进程通信

12 篇文章 0 订阅
1 篇文章 0 订阅

管道通信

标准流管道popen

标准流管道函数:

    FILE* popen(const char* command, const char* open_mode);//打开管道流文件
    int pclose(FILE* fp);//关闭管道流文件

popen函数:通过command参数拉起一个新的进程,并创建一个管道文件,如果open_mode是"r",则调用程序就可以通过该管道文件读取被调用程序的标准输出;反之如果open_mode是“w”,则被调用程序可以通过标准输入从管道文件读取数据,调用程序通过向管道文件写数据,实现通信。
pclose函数:用 popen 启动的进程结束时,我们可以用 pclose 函数关闭与之关联的文
件流

无名管道pipe

函数原型:
int pipe(int *fd[2]);
函数机制
创建一个无名管道,如果成功,fds[0]存放可读的文件描述符,fds[1]存放
可写文件描述符,并且函数返回 0,否则返回-1。
注意

  • 由于返回了一对文件描述符,所以想要用无名管道实现进程通信,两个进程必须拥有相同的进程空间,即存在继承关系。
  • 生成的无名管道是特殊文件,可以用 read、write 等,只能在内存存在
    样例:
int main(int args,char *argv[])
{
    int fd[2];
    int ret = pipe(fd);
    RET_CHECK(ret,-1,"pipe")
    char buf[128]={0};
    if(fork()>0)
    {
        read(fd[0],buf,sizeof(buf));
        printf("%s\n",buf);
        wait(NULL);
        close(fd[0]);
        return 0;
    }
    else
    {
         write(fd[1],"hello",6);
         close(fd[1]);
         exit(0);
    }
}
命名管道’fifo’

创建FIFO文件函数原型:

int mkfifo(const char *pathname, mode_t mode);
  • 参数 pathname 为要创建的 FIFO 文件的全路径名;
  • 参数 mode 为文件访问权限
  • 如果创建成功,则返回 0,否则-1。

删除FIFO文件的函数原型为:

int unlink(const char *pathname)

如何使用管道见《linux环境:C编程文件操作》

共享内存通信

共享内存原理

ystem V机制下的共享内存本质是一段特殊的内存区域,进程间需要共享的数据被放在该共享内存区域中,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中

共享内存允许一个或多个进程通过同时出现在它们的虚拟地址空间的内存进行通信,而这块虚拟内存的页面被每个共享进程的页表条目所引用,同时并不需要在所有进程的虚拟内存都有相同的地址。进程对象对于共享内存的访问通过 key(键)来控制,同时通过 key 进行访问权限的检查

进程对象对于共享内存的访问通过 key(键)来控制,同时通过 key 进行访问权限的检查

使用共享内存步骤:

① 开辟一块共享内存shmget

② 允许本进程使用共某块共享内存shmat

③ 写入/读取

删除共享内存步骤

①禁止本进程使用这块共享内存shmdt

②删除这块共享内存shmctl或者命令行下ipcrm

内存共享不提供同步机制,需要自行实现,一般通过信号量实现同步。

ftok函数

函数 ftok 用于创建一个关键字,可以用该关键字关联一个共享内存段,相当于命名。

key_t ftok(const char *pathname, int proj_id);
  • 参数 pathname 为一个全路径文件名,并且该文件必须可访问。
  • 参数 proj_id 通常传入一非 0 字符
  • 通过 pathnameproj_id 组合可以创建唯一的 key如果调用成功,返回该关键字,否则返回-1。
shmget函数

int shmget(key_t key, int size, int shmflg);
函数解析
用于创建或打开一共享内存段

  • 参数 key 内存标识,如果事先已经存在一个与指定关键字关联的共享内存段,则直接返回该内存段的标识,表示打开,如果不存在,则创建一个新的共享内存段。key 的值既可以用 ftok 函数产生,也可以是IPC_PRIVATE(用于创建一个只属于创建进程的共享内存,主要用于父子通信),表示总是创建新的共享内存段;
  • 参数 size 指定共享内存段的大小,以字节为单位;
  • 参数 shmflg 是掩码合成值,可以是访问权限码与(IPC_CREAT 或 IPC_EXCL)的合成。IPC_CREAT 表示如果不存在该内存段,则创建它。IPC_EXCL 表示如果该内存段存在,则函数返回失败结果(-1)。如果调用成功,返回内存段标识,否则返回-1
shmat函数

void *shmat(int shmid, const void *shmaddr, int shmflg);
函数解析
将共享内存段映射到进程空间的某一地址。

  • 参数 shmid 是共享内存段的标识 通常应该是 shmget的成功返回值
  • 参数 shmaddr 指定的是共享内存连接到当前进程中的地址位置。通常是 NULL,表示让系统来选择共享内存出现的地址。
  • 参数 shmflg 是一组位标识,通常为 0 即可。
  • 调用成功,返回映射后的进程空间的首地址,否则返回(char *)-1。
shmdt 函数

int shmdt(const void *shmaddr);
函数解析
用于将共享内存段与进程空间分离

  • 参数 shmaddr 通常为 shmat 的成功返回值。
  • 函数成功返回 0,失败时返回-1.注意,将共享内存分离并没删除它,只是使得该共享内存对当前进程不在可用。
shmctl函数

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数解析
用于控制共享内存段:

  • 参数 shmid 是共享内存段标识 通常应该是 shmget 的成功返回值
  • 参数 cmd 是对共享内存段的操作方式,可选为 IPC_STAT,IPC_SET,IPC_RMID。通常为 IPC_RMID,表示删除共享内存段。
  • 参数 buf 是表示共享内存段的信息结构体数据,通常为NULL。
  • 有进程连接,执行返回 0,标记删除成功,最后一个进程解除连接后,共享内存真正被删除

信号量通信

一般通过信号量实现进程同步

semget函数

int semget(key_t key,int nsems,int flag);
用于创建一个信号量集合,返回一个标识id,失败返回-1

  • 参数 key 是唯一标识一个信号量的关键字,如果为IPC_PRIVATE(值为 0,创建一个只有创建者进程才可以访问的信号量,通常用于父子进程之间;非 0 值的 key(可以通过 ftok 函数获得)表示创建一个可以被多个进程共享的信号量;
  • 参数 nsems 指定需要使用的信号量数目。如果是创建新集合,则必须指定 nsems。如果引用一个现存的集合,则将 nsems 指定为 0
  • 参数 flag 是权限标志。还可以与键值 IPC_CREAT按位或操作,打开现有或创建一个新的信号量。可以通过 IPC_CREAT 和 IPC_EXCL标志的联合使用确保自己将创建出一个新的独一无二的信号量来,如果该信号量已经存在,就会返回一个错误。
semop函数

int semop(int semid,struct sembuf *sops,size_t num_sops);
用于改变信号量对象中各个信号量的状态。返回值:成功时,返回 0;失败时,返回-1.

  • 参数 semid 是由 semget 返回的信号量标识符。
  • 参数 sops 是指向一个描述信号量的结构体数组指针。
    该结构体包含:
    • 当前信号量的序号sem_num,
    • 对信号量执行的操作sem_op,
    • 信号量操作的属性标志sem_flag:如果为0,表示正常操作,如果为IPC_WAIT,使对信号量的操作是非阻塞的。即指定了该标志,调用线程在信号量的值不满足条件的情况下不会被阻塞,而是直接返回-1,并将errno设置为EAGAIN。如果为SEM_UNDO,那么将维护进程对信号量的调整值,以便进程结束时恢复信号量的状态。
      struct sembuf  
      {  
    	unsigned short int sem_num;   /* 信号量的序号从0~num_sops-1 */  
    	short int sem_op;            /* 对信号量的操作,>0, 0, <0 */  
       short int sem_flg;            /* 操作标识:0, IPC_WAIT, SEM_UNDO */  
      }; 
    
  • 参数num_sops表示信号量数组的大小
semctl函数

int semctl(int semid, int semnum, int cmd, …);
信号量控制函数:
semid 标识的信号量集上,或者该集合的第semnum 个信号量上执行 cmd 指定的控制命令

  • cmd类型如下:

    • IPC_RMID(立即删除信号集,唤醒所有被阻塞的进程)
    • GETVAL(根据 semun 返回信号量的值,从 0 开始,第一个信号量编号为 0)
    • SETVAL(根据 semun 设定信号的值,从 0 开始,第一个信号量编号为 0)
    • GETALL(获取所有信号量的值,第二个参数为 0,将所有信号的值存入semun.array中)
    • SETALL(将所有 semun.array 的值设定到信号集中,第二个参数为 0)
  • 根据 cmd 不同,该函数有三个或四个参数。当有四个参数时,第四个参数的类型是 union semun。调用程序 必须按照下面方式定义这个联合体:

    union semun { 
         int              val;               // SETVAL使用的值   
         struct semid_ds *buf;  // IPC_STAT、IPC_SET 使用缓存区
         unsigned short  *array;  // GETALL,、SETALL 使用的数组 
         struct seminfo  *__buf;  // IPC_INFO(Linux特有) 使用缓存区 
    }; 
    

    其中semid_ds结构体定义如下:

    struct semid_ds { 
       struct ipc_perm sem_perm;   // 所有者和权限
       time_t          sem_otime;           // 上次执行 semop 的时间  
       time_t          sem_ctime;           // 上次更新时间 
       unsigned short  sem_nsems;   // 在信号量集合里的索引
     };
    

    ipc_perm定义如下:

    struct ipc_perm { 
       key_t          __key;    // 提供给 semget()的键 
       uid_t          uid;      // 所有者有效 UID  
       gid_t          gid;      // 所有者有效 GID 
       uid_t          cuid;     // 创建者有效 UID 
       gid_t          cgid;     // 创建者有效 GID
       unsigned short mode;     // 权限 
       unsigned short __seq;    // 序列号
    }; 
    
信号量应用之生产者——消费者模型
#include <fun.h>
int main(int args,char *argv[])
{
	//定义2个信号量,一个统计产品数量,一个统计仓库容量
    int sid = semget(IPC_PRIVATE,2,0600|IPC_CREAT);
    RET_CHECK(sid,-1,"semget");
    //定义两组信号量pv操作结构体
    struct sembuf p1,v1,p2,v2;
    p1.sem_op = -1;
    v1.sem_op = 1;
    p1.sem_num = 0;
    v1.sem_num = 0;
    p1.sem_flg = SEM_UNDO;
    v1.sem_flg = SEM_UNDO;
    
    p2.sem_op = -1;
    p2.sem_num = 1;
    p2.sem_flg =SEM_UNDO;
    v2.sem_num = 1;
    v2.sem_op =1;
    v2.sem_flg =SEM_UNDO;

    short arr[2]={0,10};
    //初始化信号量。0为初始库存,10为仓库初始容量。
    int  ret=semctl(sid,1,SETALL,arr);
    RET_CHECK(ret,-1,"semctl");

    printf("现有产品数量:%d,尚可存储产品数量:%d\n",semctl(sid,0,GETVAL),semctl(sid,1,GETVAL));
    //父进程负责生产
    if(fork()>0)
    {
        printf("I am a producer!\n");
        while(1)
        {
            semop(sid,&p2,1);
            printf("一个产品已生产!\n");
            semop(sid,&v1,1);
            if(semctl(sid,0,GETVAL)>1)
            {
                printf("there are now %d products.\n",semctl(sid,0,GETVAL));
            }
            else
             {
                printf("there is now %d product.\n",semctl(sid,0,GETVAL));
            }
            sleep(1);
        }
        wait(NULL);
        return 0;
    }
    //子进程负责消费
    else
    {
        printf("I am a consumer!\n");
        while(1)
        {
            semop(sid,&p1,1);
             printf("一个产品已消费\n");
            semop(sid,&v2,1);
            sleep(2);
        }
    }
    return 0;
}

消息队列

消息队列与 FIFO 很相似,都是一个队列结构,都可以有多个进程往队列里面写信息,
多个进程从队列中读取信息。但 FIFO 需要读、写的两端事先都打开,才能够开始信息传递工作。而消息队列可以事先往队列中写信息,需要时再打开读取信息。但是,消息队列先打开读,仍然会阻塞,因为此时没有消息可读。

msgget函数

函数原型:
int msgget(key_t key, int msgflg);

函数原理:

  • 函数 msgget 创建和访问一个消息队列
  • 参数 key 是唯一标识一个消息队列的关键字,如果为 IPC_PRIVATE(值为 0),用创建一个只有创建者进程才可以访问的消息队列,可以用于父子间通信;非 0 值的 key(可以通过 ftok 函数获得)表示创建一个可以被多个进程共享的消息队列
  • 参数 msgflg 指明队列的访问权限和创建标志,创建标志的可选值为 IPC_CREATIPC_EXCL 如果单独指定 IPC_CREAT,msgget 要么返回新创建的消息队列 id,要么返回具有相同 key 值的消息队列 id;如果 IPC_EXCLIPC_CREAT 同时指明,则要么创建新的消息队列,要么当队列存在时,调用失败并返回-1。
msgsnd函数和msgrcv函数

函数原型:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//消息发送函数

  • 该函数向一个队列中发送消息
  • 参数msqid指明消息队列的id标识
  • 参数msgp消息结构体指针struct msgbuf*,结构体定义如下:
    struct msgbuf {
               long mtype;       /* 消息类型,正整数,由用户指定*/
               char mtext[1];    /* 消息内容,可以通过重写结构体来自定义 */
           };
    
  • 参数msgsz自定义消息结构体中的消息体大小
  • 参数msgflg可以为 0(通常为 0)或 IPC_NOWAIT,如果设置 IPC_NOWAIT,则msgsndmsgrcv 都不会阻塞,此时如果队列满并调用 msgsnd 或队列空时调用msgrcv将返回错误;

函数原型:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);//消息接收函数

  • 参数 msqid为消息队列标识id
  • 参数msgp为消息结构体struct msgbuf指针
  • 参数msgz标识消息的长度
  • 参数msgtyp为接收消息类型:
    • msgtyp=0,接收第一个消息
    • msgtyp>0,接收第一个类型为msgtyp的消息
    • msgtyp<0, 接收类型小于或等于 msgtyp 绝对值的第 1 个最低类型消息
  • 参数msgflg可以为 0(通常为 0)或 IPC_NOWAIT,如果设置 IPC_NOWAIT,则msgsndmsgrcv 都不会阻塞,此时如果队列满并调用 msgsnd 或队列空时调用msgrcv将返回错误;
msgctl函数

函数原型:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

  • 函数 msgctl 是消息队列的控制函数,常用来删除消息队列。
  • 参数 msqid 是由 msgget 返回的消息队列标识符。
  • 参数 cmd 通常为 IPC_RMID 表示删除消息队列。
  • 参数 buf 通常为 NULL 即可
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值