UNIX环境C语言编程(12)-进程间通信

1、管道

先思考一下:到目前为止,你了解几种进程间交换信息的方法?
管道是 Unix IPC 的最古老形式,所有 Unix 系统都支持,有 2 个限制:
1 、历史上,管道是半双工的,即数据只能向一个方向流动
2 、管道只能在有共同祖先的进程间使用
尽管如此,管道仍然是最常用的 IPC 形式
#include < unistd.h >
int pipe ( int fd [2]);  # 创建管道, fd [0] 用来读, fd [1] 用来写

 

有一个参数需要注意:如果多个进程同时写入同一个管道,为避免数据的交叠,单个 write 调用写入的数据长度必须 <= 某个值
POSIX 标准规定这个值至少是 512 ,大多 Unix 的实现都远大于这个值
一个简单的管道例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    int     n, fd[2];
    char    line[64];

    pid_t   pid;

    if( pipe(fd) < 0 )
    {
        perror("pipe error");
        exit(0);
    }

    if( (pid = fork()) < 0 )
    {
        perror("fork error");
        exit(0);
    }
    else if( pid > 0 )   /* parent */
    {
        close(fd[0]);
        write(fd[1], "hello world\n", 12);
    }
    else                 /* child */
    {
        close(fd[1]);
        n = read(fd[0], line, 64);
        write(STDOUT_FILENO, line, n);
    }

    exit(0);
}

/* 更有意思的实现是借助于重定向 */

信号 一章,借助于信号机制实现父子进程间同步
如何借助于管道实现父子进程间同步?
static int  pfd1[2], pfd2[2];

void TELL_WAIT(void)
{
    if( pipe(pfd1) < 0 || pipe(pfd2) < 0 )
    {
        perror("pipe error");
        exit(0);
    }
}

void TELL_PARENT(pid_t pid)
{
    if( write(pfd2[1], "c", 1) != 1 )
    {
        perror("write error");
        exit(0);
    }
}

void WAIT_PARENT(void)
{
    char    c;

    if( read(pfd1[0], &c, 1) != 1 )
    {
        perror("read error");
        exit(0);
    }

    if( c != 'p' )
    {
        perror("WAIT_PARENT: incorrect data");
        exit(0);
    }
}

void TELL_CHILD(pid_t pid)
{
    if( write(pfd1[1], "p", 1) != 1 )
    {
        perror("write error");
        exit(0);
    }
}

void WAIT_CHILD(void)
{
    char    c;

    if( read(pfd2[0], &c, 1) != 1 )
    {
        perror("read error");
        exit(0);
    }

    if( c != 'c' )
    {
        perror("WAIT_CHILD: incorrect data");
        exit(0);
    }
}

练习题: 如何使用管道、 fork 、重定向、及 bc 命令,实现算数运算?
这种技术称为 协作进程 Coprocesses

 

2、popenpclose函数

#include < stdio.h >
FILE * popen ( const char * cmdstring , const char *type);  # fork ,然后执行 cmdstring
int pclose (FILE * fp );  # 关闭文件指针,等待进程结束, 必须与 popen 配对使用
管道的常见用途是:创建一个连接到另一个进程的管道,然后读其输出或给它输入
popen 的目的是简化这种管道操作
fp = popen ( cmdstring , "r")
fp = popen ( cmdstring , "w")
cmdstring 的执行方式是: sh -c cmdstring
即借助于 shell 执行,所以 cmdstring 中可以指定任何 shell 可以识别的元素
思考: 如何通过 popen 实现 bc 命令的调用?

 

3、FIFO(命名管道)

与普通管道相比,借助于 FIFO ,不相关的进程之间也可以交换数据
#include <sys/stat.h>
int mkfifo (const char *pathname, mode_t mode);  # 创建命名管道
用处 1 ,在 shell 的多个管道线之间交换数据:
mkfifo fifo1  # 创建命名管道( mkfifo 同样是一个命令的名字)
prog3 < fifo1 &
prog1 < infile | tee fifo1 | prog2
用处 2 ,用于客户机 / 服务器模型中,实现 client server 的通信

 

4、XSI IPC

三种类型的 IPC (消息队列、共享内存、信号灯)有很多相似之处,共性如下:
1 、标识符与键值
标识符与文件描述符的身份类似,是非负整数
KEY (键值),与文件名称的身份类似,区别在于键值是整数
键值可以指定为 IPC_PRIVATE ,将总是创建一个新的 IPC 结构
可以通过 ftok 生成一个键值(并不保证唯一) key_t ftok ( const char *path, int id);
2 、权限结构
每个 IPC 对应一个 ipc_perm 结构,定义属主及权限
3 、参数限制
4 、优缺点
它们是整个系统范围的,进程结束后它们可能仍然残留,
不能使用操作文件的那些函数操作,不得不引入了 10 多个专门的函数

 

5、消息队列

通过 msgget 创建 / 获取一个消息队列,使用 msgsnd 向队列中添加消息,使用 msgrcv 从队列中收取(同时删除)消息
每条消息有一个 long 整数形式的消息类型,一个非负长度,及相应的数据
消息队列的读取不限于先进先出,可以根据类型收取
1 、创建一个新的或获取一个现有的队列
#include <sys/ msg.h >
int msgget ( key_t key, int flag);
返回 msqid 用于后续操作
2 、消息队列的控制操作
int msgctl ( int msqid , int cmd , struct msqid_ds * buf );
可以执行 3 种控制操作, cmd 分别对应 IPC_STAT IPC_SET IPC_RMID
struct msqid_ds 包含权限、队列容量上限等信息

3、消息发送

int msgsnd ( int msqid , const void * ptr , size_t nbytes , int flag);
第二个参数的定义可以参考
struct {
    long  mtype /* positive message type */
    char  mtext [ 512 ];}  /* message data, of length nbytes */
如果 flag 指定为 IPC_NOWAIT ,消息发送 / 接收的行为是非阻塞的
4 、消息接收
ssize_t msgrcv ( int msqid , void * ptr , size_t nbytes , long type, int flag);
如果 flag 指定为 MSG_NOERROR ,超长消息( > nbytes )将被截断,剩余部分丢弃;
否则,超长消息的接收将返回错误
type == 0 ,读取队列中的第一条消息
type > 0 ,读取指定类型的第一条消息
type < 0 ,读取类型 <=|type| 的第一条消息
/* msg.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define   KEYTEST    0x123000

struct myinfo
{
    long type;
    char mtext[64];
};

int main(void)
{
    int  qid;
    struct myinfo qbuf;

    // create the msg queue
    if( (qid = msgget(KEYTEST, IPC_CREAT | IPC_EXCL | 0666)) < 0 )
    {
        perror("msgget");
        exit(0);
    }

    // msgsnd
    qbuf.type = 3;
    strcpy(qbuf.mtext, "This is a test message");

    // notice the length 4
    if( msgsnd(qid, &qbuf, 4, 0) < 0 )
    {
        perror("msgsnd");
        exit(0);
    }
}

/* msg1.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define   KEYTEST    0x123000

struct myinfo
{
    long type;
    char mtext[64];
};

int main(void)
{
    int  qid;
    struct myinfo qbuf;

    // create the msg queue
    if( (qid = msgget(KEYTEST, 0)) < 0 )
    {
        perror("msgget");
        exit(0);
    }

    // msgrcv
    memset(&qbuf, 0, sizeof(qbuf));

    if( msgrcv(qid, &qbuf, sizeof(qbuf.mtext), 0, 0) < 0 )
    {
        perror("msgrcv");
        exit(0);
    }

    printf("type=%d, msg=[%s]\n", qbuf.type, qbuf.mtext);
}

/* msg2.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define   KEYTEST    0x123000

int main(void)
{
    int  qid;

    // create the msg queue
    if( (qid = msgget(KEYTEST, 0)) < 0 )
    {
        perror("msgget");
        exit(0);
    }

    // msgctl, remove the queue
    if( msgctl(qid, IPC_RMID, 0) < 0 )
    {
        perror("msgctl");
        exit(0);
    }
}

6、 共享内存
共享内存允许多个进程共享一块内存区域,是最快速的 IPC 机制
1 、创建 / 获取一段共享内存
#include <sys/shm.h >
int shmget ( key_t key, size_t size, int flag);  # 创建或获取一段内存
2 、控制操作
int shmctl ( int shmid , int cmd , struct shmid_ds * buf );
3 、连接共享内存
void * shmat ( int shmid , const void * addr , int flag);
参数 addr 通常指定为 0 ,让系统自行选择起始地址
如果 flag 包含 SHM_RDONLY ,这段内存将是只读的
4 、断开连接, int shmdt (void * addr );
共享内存段被连接到进程空间的哪个地址?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>

#define ARRAY_SIZE  40000
#define MALLOC_SIZE 100000
#define SHM_SIZE    100000
#define SHM_MODE    0600    /* user read/write */

char    array[ARRAY_SIZE];  /* uninitialized data = bss */

int main(void)
{
    int     shmid;
    char    *ptr, *shmptr;

    printf("array[] from %lx to %lx\n", (unsigned long)&array[0],
      (unsigned long)&array[ARRAY_SIZE]);
    printf("stack around %lx\n", (unsigned long)&shmid);

    if( (ptr = malloc(MALLOC_SIZE)) == NULL )
    {
        perror("malloc");
        exit(0);
    }
    printf("malloced from %lx to %lx\n", (unsigned long)ptr,
      (unsigned long)ptr + MALLOC_SIZE);

    if( (shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) < 0 )
    {
        perror("shmget");
        exit(0);
    }
    if( (shmptr = shmat(shmid, 0, 0)) == (void *)-1 )
    {
        perror("shmat");
        exit(0);
    }
    printf("shared memory attached from %lx to %lx\n",
      (unsigned long)shmptr, (unsigned long)shmptr + SHM_SIZE);

    if( shmctl(shmid, IPC_RMID, 0) < 0 )
    {
        perror("shmctl");
        exit(0);
    }

    exit(0);
}
回顾一下内存映射 IO
可以映射 / dev /zero 设备
也可以匿名映射: mmap (0, SIZE, , MAP_ANON | MAP_SHARED, -1 , 0)

7、 信号灯
信号灯是一种进程同步机制(想想十字路口的红绿灯)
信号灯其实是一个集合,里面可以包含多个灯
1 、创建 / 获取信号灯
#include <sys/ sem.h >
int semget ( key_t key, int nsems , int flag);  # nsems 指定集合内的灯的数目
2 、信号灯控制
int semctl ( int semid , int semnum , int   cmd , ... /* union semun arg */);
union semun {
    int                          val /* for SETVAL */
    struct semid_ds * buf /* for IPC_STAT and IPC_SET */
    unsigned short  *array;  /* for GETALL and SETALL */
};
cmd 参数的取值: GETVAL SETVAL GETALL SETALL IPC_XX
3 、信号灯操作( 原子 的执行 一组 信号灯操作)
int semop ( int semid , struct sembuf semoparray [], size_t nops );
struct sembuf {
    unsigned short  sem_num ;   /* member # in set (0, 1, ..., nsems-1) */
    short                   sem_op /* operation (negative, 0, or positive) */
    short                   sem_flg /* IPC_NOWAIT, SEM_UNDO */
};
4 、一个示例
/* cat sem.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define   KEYTEST   0x12300

typedef union        /* 信号灯取值联合 */
{
        int             val;
        struct semid_ds *buf;
        unsigned short  *array;
} USEMUN;

int main(void)
{
    int    id;
    USEMUN un;

    // create
    if( (id = semget(KEYTEST, 1, IPC_CREAT | IPC_EXCL | 0666)) < 0 )
    {
        perror("semget");
        return(-1);
    }

    // initialize
    un.val = 1;

    if( semctl(id, 0, SETVAL, un) < 0 )
    {
        perror("semctl");
        return(-1);
    }
}
/* cat sem1.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define   KEYTEST   0x12300

int main(void)
{
    int    id;
    struct sembuf sb;

    // get
    if( (id = semget(KEYTEST, 0, 0)) < 0 )
    {
        perror("semget");
        return(-1);
    }

    // operation lock
    sb.sem_num = 0;
    sb.sem_op  = -1;
    sb.sem_flg = SEM_UNDO;    /* 进程退出时,回滚操作过的信号灯值 */

    if( semop(id, &sb, 1) < 0 )
    {
        perror("semop");
        return(-1);
    }

    printf("OK, I get it, sleep 30 now ...\n");
    sleep(30);

    // operation unlock
    sb.sem_op = 1;
    if( semop(id, &sb, 1) < 0 )
    {
        perror("semop");
        return(-1);
    }
}

共享内存通常与信号灯一起使用,寻求进程同步机制
信号灯与记录锁的比较:
信号灯更快;记录锁方便
 
8、 客户机 / 服务器特性
广义而言,一个进程请求另外一个进程为其服务,都可以称为 C/S 模型
他们可以采用各种 IPC 通信机制
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值