UNIX-Linux环境编程(七):进程间通信

一、基本概念

1. 何为进程间通信

进程间通信(Interprocess Communication, IPC),是指两个或多个进程之间进行数据交换的过程。

2. 进程间通信分类

•1) 简单进程间通信:
命令行参数、环境变量、信号、文件。
•2) 传统进程间通信:
管道(fifo/pipe)。
•3) XSI进程间通信:
共享内存、消息队列、信号量
•4) 网络进程间通信:
套接字

二、传统进程间通信——管道

  1. 管道是Unix系统最古老的进程间通信方式。
  2. 历史上的管道通常是指半双工管道,只允许数据单向流动。现代系统大都提供全双工管道,数据可以沿着管道双向流动。

3) 有名管道(fifo):基于有名文件(管道文件)的管道通信。

•1) 命令形式

	# mkfifo fifo 
	# echo hello > fifo # cat fifo
•2) 编程模型 

有名管道
范例:wfifo.c、rfifo.c
wfifo.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define FIFO_FILE "/tmp/fifo"

int main () 
{
    printf ("创建管道...\n");

    if (mkfifo (FIFO_FILE, 0666) == -1) 
    {
        perror ("mkfifo");
        return -1;
    }

    printf ("打开管道...\n");

    int fd = open (FIFO_FILE, O_WRONLY);
    if (fd == -1)
    {
        perror ("open");
        return -1;
    }

    printf ("发送数据...\n");

    for (;;) 
    {
        printf ("> ");

        char buf[1024];
        gets (buf);

        if (! strcmp (buf, "!"))
            break;

        if (write (fd, buf, (strlen (buf) + 1) * sizeof (buf[0])) == -1) 
        {
            perror ("write");
            return -1;
        }
    }

    printf ("关闭管道...\n");

    if (close (fd) == -1) 
    {
        perror ("close");
        return -1;
    }

   printf ("删除管道...\n");

    if (unlink (FIFO_FILE) == -1) 
    {
        perror ("unlink");
        return -1;
    }

    printf ("大功告成!\n");

    return 0;
}

rfifo.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#define FIFO_FILE "/tmp/fifo"

int main () 
{
    printf ("打开管道...\n");

    int fd = open (FIFO_FILE, O_RDONLY);
    if (fd == -1) 
    {
        perror ("open");
        return -1;
    }

    printf ("接收数据...\n");

    for (;;) 
    {
        char buf[1024];

        ssize_t rb = read (fd, buf, sizeof (buf));
        if (rb == -1) 
        {
            perror ("read");
            return -1;
        }

        if (! rb)
            break;

       printf ("< %s\n", buf);
    }

    printf ("关闭管道...\n");

    if (close (fd) == -1) 
    {
        perror ("close");
        return -1;
    }

    printf ("大功告成!\n");

    return 0;
}

fifo

4、无名管道(pipe):适用于父子进程之间的通信。

#include <unistd.h>
int pipe (int pipefd[2]);
成功返回0,失败返回-1。
  1. 通过输出参数pipefd返回两个文件描述符,其中pipefd[0]用于读,pipefd[1]用于写。
  2. 一般用法
    A. 调用该函数在内核中创建管道文件,并通过其输出参数,获得分别用于读和写的两个文件描述符;
    B. 调用fork函数,创建子进程;
    C. 写数据的进程关闭读端(pipefd[0]),读数据的进程关闭写端(pipefd[1]);
    D. 传输数据;
    E. 父子进程分别关闭自己的文件描述符。
    pipe
    范例:pipe.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

int main (void) 
{
    printf ("父进程:创建管道...\n");

    int pipefd[2];
    if (pipe (pipefd) == -1) 
    {
        perror ("pipe");
        return -1;
    }

    printf ("父进程:创建进程...\n");

    pid_t pid = fork ();
    if (pid == -1) 
    {
        perror ("fork");
        return -1;
    }

    if (pid == 0) 
    {
        printf ("子进程:关闭写端...\n");

        close (pipefd[1]);

        printf ("子进程:接收数据...\n");

        for (;;) 
        {
            char buf[1024];

            ssize_t rb = read (pipefd[0], buf, sizeof (buf));
            if (rb == -1) 
            {
                perror ("read");
                return -1;
            }

            if (! rb)
                break;

            puts (buf);
        }

        printf ("子进程:关闭读端...\n");

        close (pipefd[0]);

        printf ("子进程:大功告成!\n");

        return 0;
    }

    printf ("父进程:关闭读端...\n");

    close (pipefd[0]);

    printf ("父进程:发送数据...\n");

    for (;;) 
    {
        char buf[1024];
        gets (buf);

        if (! strcmp (buf, "!")) break;

        if (write (pipefd[1], buf, (strlen (buf) + 1) * sizeof (buf[0])) == -1) 
        {
            perror ("write");
            return -1;
        }
    }

   printf ("父进程:关闭写端...\n");
 
    close (pipefd[1]);

    if (wait (0) == -1) 
    {
        perror ("wait");
        return -1;
    }

    printf ("父进程:大功告成!\n");

    return 0;
}

三、XSI进程间通信

1. IPC标识

  1. 内核为每个进程间通信维护一个结构体形式的IPC对象。
  2. 该对象可通过一个非负整数的IPC标识来引用。
  3. 与文件描述符不同,IPC标识在使用时会持续加1,当达到最大值时,向0回转

2. IPC键值

IPC标识是IPC对象的内部名称。
若多个进程需要在同一个IPC对象上会合,则必须通过键值作为其外部名称来引用该对象。

  1. 无论何时,只要创建IPC对象,就必须指定一个键值。
  2. 键值的数据类型在sys/types.h头文件中被定义为key_t,其原始类型就是长整型。

3. 客户机进程与服务器进程在IPC对象上的三种会合方式

  1. 服务器进程以IPC_PRIVATE为键值创建一个新的IPC对象,并将该IPC对象的标识存放在某处(如文件中),以方便客户机进程读取。

  2. 在一个公共头文件中,定义一个客户机进程和服务器进程都认可的键值,服务器进程用此键值创建IPC对象,客户机进程用此键值获取该IPC对象。

  3. 客户机进程和服务器进程,事先约定好一个路径名和一个项目ID(0-255),二者通过ftok函数,将该路径名和项目ID转换为一致的键值。

     #include <sys/types.h>
     #include <sys/ipc.h>
    
     key_t ftok (const char* pathname, int proj_id);
    
     pathname - 一个真实存在的文件或目录的路径名。
    
     proj_id  - 项目ID,仅低8位有效,其值域为[0,255]。
    
     成功返回键值,失败返回-1。
    

注意:起作用的是pathname参数所表示的路径,而非pathname字符串本身。
因此假设当前目录是/home/soft01/uc/day07,则ftok (".", 100);和ftok ("/home/soft01/uc/day07", 100);的返回值完全相同。

4. IPC对象的创建

  1. 若以IPC_PRIVATE为键值创建IPC对象,则永远创建成功。
  2. 若所指定的键值在系统范围内未与任何IPC对象相结合,且创建标志包含IPC_CREAT位,则创建成功。
  3. 若所指定的键值在系统范围内已与某个IPC对象相结合,且创建标志包含IPC_CREAT和IPC_EXCL位,则创建失败。

5. IPC对象的销毁/控制

•IPC_STAT
获取IPC对象属性
•IPC_SET
设置IPC对象属性
•IPC_RMID
删除IPC对象

四、共享内存

1. 基本特点

  1. 两个或者更多进程,共享同一块由系统内核负责维护的内存区域,其地址空间通常被映射到堆和栈之间。
    shm

  2. 无需复制信息,最快的一种IPC机制。

  3. 需要考虑同步访问的问题。

  4. 内核为每个共享内存,维护一个shmid_ds结构体形式的共享内存对象。

2. 常用函数

#include <sys/shm.h>

1) 创建/获取共享内存

	int shmget (key_t key, size_t size, int shmflg);
 A. 该函数以key参数为键值创建共享内存,或获取已有的共享内存。
 B. size参数为共享内存的字节数, 建议取内存页字节数(4096)的整数倍。 

若希望创建共享内存,则必需指定size参数。
若只为获取已有的共享内存,则size参数可取0。
C. shmflg取值:

			0 - 获取,不存在即失败。 
			IPC_CREAT - 创建,不存在即创建, 
			已存在即获取,除非… 
			IPC_EXCL - 排斥,已存在即失败。
◦D. 成功返回共享内存标识,失败返回-1。

2) 加载共享内存

	void* shmat (int shmid, const void* shmaddr,int shmflg); 
A. 将shmid参数所标识的共享内存,映射到调用进程的地址空间。

B. 可通过shmaddr参数人为指定映射地址,也可将该参数置NULL,由系统自动选择。
C. shmflg取值:

	0 - 以读写方式使用共享内存。 
	SHM_RDONLY - 以只读方式使用共享内存。 
	SHM_RND - 只在shmaddr参数非NULL时起作用。 
	表示对该参数向下取内存页的整数倍,作为映射地址。
D. 成功返回映射地址,失败返回-1。

E. 内核将该共享内存的加载计数加1。

3) 卸载共享内存

	int shmdt (const void* shmaddr); 

◦A. 从调用进程的地址空间中,取消由shmaddr参数所指向的,共享内存映射区域。
◦B. 成功返回0,失败返回-1。
◦C. 内核将该共享内存的加载计数减1。

4) 销毁/控制共享内存

int shmctl (int shmid, int cmd, struct shmid_ds* buf); 

◦A. cmd取值:

◾IPC_STAT 
获取共享内存的属性,通过buf参数输出。
◾IPC_SET 
设置共享内存的属性,通过buf参数输入,仅以下三个属性可设置: 
shmid_ds::shm_perm.uid 
shmid_ds::shm_perm.gid 
shmid_ds::shm_perm.mode
◾IPC_RMID 
标记删除共享内存。 
并非真正删除共享内存,只是做一个删除标记,禁止其被继续加载,但有加载依然保留。 
只有当该共享内存的加载计数为0时,才真正被删除。

◦B. 成功返回0,失败返回-1。

struct shmid_ds 
{
struct ipc_perm shm_perm;   // 所有者及其权限
size_t          shm_segsz;  // 大小(以字节为单位)
time_t          shm_atime;  // 最后加载时间
time_t          shm_dtime;  // 最后卸载时间
time_t          shm_ctime;  // 最后改变时间
pid_t           shm_cpid;   // 创建进程PID
pid_t           shm_lpid;   // 最后加载/卸载进程PID
shmatt_t        shm_nattch; // 当前加载计数
...
};
struct ipc_perm 
{
key_t          __key; // 键值
uid_t          uid;   // 有效属主ID
gid_t          gid;   // 有效属组ID
uid_t          cuid;  // 有效创建者ID
gid_t          cgid;  // 有效创建组ID
unsigned short mode;  // 权限字
unsigned short __seq; // 序列号
};

3. 编程模型

shm
范例:wshm.c、rshm.c
wshm.c

#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>

int main () 
{
    printf ("创建共享内存...\n");

    key_t key = ftok (".", 100);
    if (key == -1) 
    {
        perror ("ftok");
        return -1;
    }

    int shmid = shmget (key, 4096, 0644 | IPC_CREAT | IPC_EXCL);
    if (shmid == -1) 
    {
        perror ("shmget");
        return -1;
    }

    printf ("加载共享内存...\n");

    void* shmaddr = shmat (shmid, NULL, 0);
    if (shmaddr == (void*)-1) 
    {
        perror ("shmat");
        return -1;
    }

    printf ("写入共享内存...\n");

    sprintf (shmaddr, "我是%u进程写入的数据。", getpid ());

    printf ("按<回车>卸载共享内存(0x%08x/%d)...", key, shmid);
    getchar ();

    if (shmdt (shmaddr) == -1) 
    {
        perror ("shmdt");
        return -1;
    }

    printf ("按<回车>销毁共享内存(0x%08x/%d)...", key, shmid);
    getchar ();

    if (shmctl (shmid, IPC_RMID, NULL) == -1) 
    {
        perror ("shmctl");
        return -1;
    }

    printf ("大功告成!\n");

   return 0;
}

rshm.c

#include <stdio.h>
#include <time.h>
#include <sys/shm.h>

int shmstat (int shmid) 
{
    struct shmid_ds shm;
    if (shmctl (shmid, IPC_STAT, &shm) == -1) 
    {
        perror ("shmctl");
        return -1;
    }

    printf ("------------------------------------------------\n");
    printf ("                  共享内存信息\n");
    printf ("----+----------------+--------------------------\n");
    printf (" 所 | 键值           | 0x%08x\n", shm.shm_perm.__key);
    printf (" 有 | 有效属主ID     | %u\n", shm.shm_perm.uid);
    printf (" 者 | 有效属组ID     | %u\n", shm.shm_perm.gid);
    printf (" 及 | 有效创建者ID   | %u\n", shm.shm_perm.cuid);
    printf (" 其 | 有效创建组ID   | %u\n", shm.shm_perm.cgid);
    printf (" 权 | 权限字         | %#o\n", shm.shm_perm.mode);
    printf (" 限 | 序列号         | %u\n", shm.shm_perm.__seq);
    printf ("----+----------------+--------------------------\n");
    printf (" 大小(字节)          | %u\n", shm.shm_segsz);
    printf (" 最后加载时间        | %s", ctime (&shm.shm_atime));
    printf (" 最后卸载时间        | %s", ctime (&shm.shm_dtime));
    printf (" 最后改变时间        | %s", ctime (&shm.shm_ctime));
    printf (" 创建进程ID          | %u\n", shm.shm_cpid);
    printf (" 最后加载/卸载进程ID | %u\n", shm.shm_lpid);
    printf (" 当前加载计数        | %ld\n", shm.shm_nattch);
    printf ("---------------------+--------------------------\n");

    return 0;
}

int shmset (int shmid) 
{
    struct shmid_ds shm;
    if (shmctl (shmid, IPC_STAT, &shm) == -1) 
    {
        perror ("shmctl");
        return -1;
    }

    shm.shm_perm.mode = 0600;
    shm.shm_segsz = 8192;

    if (shmctl (shmid, IPC_SET, &shm) == -1) 
    {
        perror ("shmctl");
        return -1;
    }

    return 0;
}

int main () 
{
    printf ("获取共享内存...\n");

    key_t key = ftok (".", 100);
    if (key == -1) 
    {
        perror ("ftok");
        return -1;
    }

    int shmid = shmget (key, 0, 0);
    if (shmid == -1) 
    {
        perror ("shmget");
        return -1;
    }

    printf ("加载共享内存...\n");

    void* shmaddr = shmat (shmid, NULL, 0);
    if (shmaddr == (void*)-1) 
    {
        perror ("shmat");
        return -1;
    }

    shmstat (shmid);

    printf ("读取共享内存...\n");

    printf ("共享内存(0x%08x/%d):%s\n", key, shmid, (char*)shmaddr);

    printf ("卸载共享内存...\n");

    if (shmdt (shmaddr) == -1) 
    {
        perror ("shmdt");
        return -1;
    }

   shmstat (shmid);

    printf ("设置共享内存...\n");

    shmset (shmid);

    shmstat (shmid);

    printf ("大功告成!\n");

    return 0;
}

五、消息队列

1. 基本特点

•1) 消息队列是一个由系统内核负责存储和管理,并通过消息队列标识引用的数据链表。
•2) 可以通过msgget函数创建一个新的消息队列,或获取一个已有的消息队列。
通过msgsnd函数向消息队列的后端追加消息,通过msgrcv函数从消息队列的前端提取消息。
•3) 消息队列中的每个消息单元除包含消息数据外,还包含消息类型和数据长度。
•4) 内核为每个消息队列,维护一个msqid_ds结构体形式的消息队列对象。

2. 常用函数

#include <sys/msg.h>
  1. 创建/获取消息队列

     int msgget (key_t key, int msgflg); 
    

    ◦A. 该函数以key参数为键值创建消息队列,或获取已有的消息队列。
    ◦B. msgflg取值:
    0 - 获取,不存在即失败。
    IPC_CREAT - 创建,不存在即创建,已存在即获取,除非…
    IPC_EXCL - 排斥,已存在即失败。
    ◦C. 成功返回消息队列标识,失败返回-1。

  2. 向消息队列发送消息
    int msgsnd (int msqid, const void* msgp,size_t msgsz, int msgflg); ◦A. msgp参数指向一个包含消息类型和消息数据的内存块。
    该内存块的前4个字节必须是一个大于0的整数,代表消息类型,其后紧跟消息数据。
    消息数据的字节长度用msgsz参数表示。
    msgp
    注意:msgsz参数并不包含消息类型的字节数(4)。
    ◦B. 若内核中的消息队列缓冲区有足够的空闲空间,则此函数会将消息拷入该缓冲区并立即返回0,表示发送成功,否则此函数会阻塞,直到内核中的消息队列缓冲区有足够的空闲空间为止 (比如有消息被接收)。
    ◦C. 若msgflg参数包含IPC_NOWAIT位,则当内核中的消息队列缓冲区没有足够的空闲空间时,此函数不会阻塞,而是返回-1,errno为EAGAIN。
    ◦D. 成功返回0,失败返回-1。

  3. 从消息队列接收消息

     ssize_t msgrcv (int msqid, void* msgp, size_t msgsz,long msgtyp, int msgflg);
    

    ◦A. msgp参数指向一个包含消息类型(4字节),和消息数据的内存块,其中消息数据缓冲区的字节大小用msgsz参数表示。
    ◦B. 若所接收到的消息数据字节数大于msgsz参数,即消息太长,且msgflg参数包含MSG_NOERROR位,则该消息被截取msgsz字节返回,剩余部分被丢弃。
    ◦C. 若msgflg参数不包含MSG_NOERROR位,消息又太长,则不对该消息做任何处理,直接返回-1,errno为E2BIG。
    ◦D. msgtyp参数表示期望接收哪类消息:

     =0 - 返回消息队列中的第一条消息。 
     >0 - 若msgflg参数不包含MSG_EXCEPT位,则返回消息队列中第一个类型为msgtyp的消息,若msgflg参数包含MSG_EXCEPT位,则返回消息队列中第一个类型不为msgtyp的消息。 
     <0 - 返回消息队列中类型小于等于msgtyp的绝对值的消息,若有多个,则取类型最小者。 
    

msg◦E. 若消息队列中有可接收消息,则此函数会将该消息移出消息队列并立即返回0,表示接收成功,否则此函数会阻塞,直到消息队列中有可接收消息为止。

◦F. 若msgflg参数包含IPC_NOWAIT位,则当消息队列中没有可接收消息时,此函数不会阻塞,而是返回-1,errno为ENOMSG。
◦G. 成功返回所接收到的消息数据的字节数,失败返回-1。
4. 销毁/控制消息队列

	int msgctl (int msqid, int cmd, struct msqid_ds* buf); 

◦A. cmd取值:

◾IPC_STAT 
获取消息队列的属性,通过buf参数输出。
◾IPC_SET 
设置消息队列的属性,通过buf参数输入,仅以下四个属性可设置: 
msqid_ds::msg_perm.uid 
msqid_ds::msg_perm.gid、 
msqid_ds::msg_perm.mode 
msqid_ds::msg_qbytes
◾IPC_RMID - 立即删除消息队列。 
 此时所有阻塞在对该消息队列的,msgsnd和msgrcv函数调用,都会立即返回失败,errno为EIDRM。

◦B. 成功返回0,失败返回-1。

struct msqid_ds {
	struct ipc_perm msg_perm;     // 权限信息
	time_t          msg_stime;    // 随后发送时间
	time_t          msg_rtime;    // 最后接收时间
	time_t          msg_ctime;    // 最后改变时间
	unsigned long   __msg_cbytes; // 消息队列中的字节数
	msgqnum_t       msg_qnum;     // 消息队列中的消息数
	msglen_t        msg_qbytes;   // 消息队列能容纳的最大字节数
	pid_t           msg_lspid;    // 最后发送进程PID
	pid_t           msg_lrpid;    // 最后接收进程PID
};

struct ipc_perm {
	key_t          __key; // 键值
	uid_t          uid;   // 有效属主ID
	gid_t          gid;   // 有效属组ID
	uid_t          cuid;  // 有效创建者ID
	gid_t          cgid;  // 有效创建组ID
	unsigned short mode;  // 权限字
	unsigned short __seq; // 序列号
};

3. 编程模型

msg_model
范例:wmsq.crmsq.c
wmsq.c

#include <stdio.h>
#include <string.h>
#include <sys/msg.h>

int main () 
{
    printf ("创建消息队列...\n");

    key_t key = ftok (".", 100);
    if (key == -1) 
    {
        perror ("ftok");
        return -1;
    }

    int msqid = msgget (key, 0644 | IPC_CREAT | IPC_EXCL);
    if (msqid == -1) 
    {
        perror ("msqget");
        return -1;
    }

    printf ("向消息队列(0x%08x/%d)发送数据...\n", key, msqid);

    for (;;) 
    {
        printf ("> ");

        struct 
        {
            long mtype;
            char mtext[1024];
        }   msgbuf = {1234, ""};
        gets (msgbuf.mtext);

        if (! strcmp (msgbuf.mtext, "!")) break;

        if (msgsnd (msqid, &msgbuf, (strlen (msgbuf.mtext) + 1) *
            sizeof (msgbuf.mtext[0]), 0) == -1) 
        {
            perror ("msgsnd");
            return -1;
        }
    }

    printf ("销毁消息队列(0x%08x/%d)...\n", key, msqid);

    if (msgctl (msqid, IPC_RMID, NULL) == -1)
    {
        perror ("msgctl");
        return -1;
    }

    printf ("大功告成!\n");

    return 0;
}

rmsq.c

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/msg.h>

int main () 
{
    printf ("获取消息队列...\n");

    key_t key = ftok (".", 100);
    if (key == -1) 
   {
        perror ("ftok");
        return -1;
    }

    int msqid = msgget (key, 0);
    if (msqid == -1) 
    {
        perror ("msgget");
        return -1;
    }

    printf ("从消息队列(0x%08x/%d)接收消息...\n", key, msqid);

    for (;;) 
    {
        struct 
        {
            long mtype;
            char mtext[1024];
        }   msgbuf = {};

        ssize_t msgsz = msgrcv (msqid, &msgbuf,
            sizeof (msgbuf.mtext) - sizeof (msgbuf.mtext[0]), 1234,
            MSG_NOERROR/* | IPC_NOWAIT*/);
        if (msgsz == -1)
        {
            if (errno == EIDRM) 
            {
                printf ("消息队列(0x%08x/%d)已销毁!\n", key, msqid);
                break;
            }
            else
            if (errno == ENOMSG) 
            {
                printf ("现在没有消息,干点儿别的...\n");
                sleep (1);
            }
            else 
            {
                perror ("msgrcv");
                return -1;
            }
        }
        else
            printf ("%04d< %s\n", msgsz, msgbuf.mtext);
    }

    printf ("大功告成!\n");

     return 0;
}

六、信号量

1. 基本特点

  1. 计数器,用于限制多个进程对有限共享资源的访问。
  2. 多个进程获取有限共享资源的操作模式
    ◦A. 测试控制该资源的信号量;
    ◦B. 若信号量大于0,则进程可以使用该资源,为了表示此进程已获得该资源,需将信号量减1;
    ◦C. 若信号量等于0,则进程休眠等待该资源,直到信号量大于0,进程被唤醒,执行步骤A;
    ◦D. 当某进程不再使用该资源时,信号量增1,正在休眠等待该资源的其它进程将被唤醒。

2. 常用函数

#include <sys/sem.h>
  1. 创建/获取信号量

     int semget (key_t key, int nsems, int semflg); 
    

    ◦A. 该函数以key参数为键值创建一个信号量集合(nsems参数表示集合中的信号量数),或获取已有的信号量集合(nsems取0)。
    ◦B. semflg取值:

     0 - 获取,不存在即失败。 
     IPC_CREAT - 创建,不存在即创建,已存在即获取,除非… 
     IPC_EXCL - 排斥,已存在即失败。
    

    ◦C. 成功返回信号量集合标识,失败返回-1。

  2. 操作信号量

     int semop (int semid, struct sembuf* sops,unsigned nsops); 
    

    ◦A. 该函数对semid参数所标识的信号量集合中,由sops参数所指向的包含nsops个元素的,结构体数组中的每个元素,依次执行如下操作:

     ◾a) 若sem_op大于0,则将其加到第sem_num个信号量的计数值上,以表示对资源的释放;
     ◾b) 若sem_op小于0,则从第sem_num个信号量的计数值中减去其绝对值,以表示对资源的获取;
     ◾c) 若第sem_num个信号量的计数值不够减(信号量不能为负),则此函数会阻塞,直到该信号量够减为止,以表示对资源的等待;
     ◾d) 若sem_flg包含IPC_NOWAIT位,则当第sem_num个信号量的计数值不够减时,此函数不会阻塞,而是返回-1,errno为EAGAIN,以便在等待资源的同时还可做其它处理;
     ◾e) 若sem_op等于0,则直到第sem_num个信号量的计数值为0时才返回,除非sem_flg包含IPC_NOWAIT位。
    

◦B. 成功返回0,失败返回-1。

struct sembuf {
	unsigned short sem_num; // 信号量下标
	short          sem_op;  // 操作数
	short          sem_flg; // 操作标记
};
  1. 销毁/控制信号量

     int semctl (int semid, int semnum, int cmd); 
     int semctl (int semid, int semnum, int cmd,union semun arg);
    

    ◦A. cmd取值:

     ◾IPC_STAT 
     获取信号量集合的属性,通过arg.buf输出。
     ◾IPC_SET 
     设置信号量集合的属性,通过arg.buf输入,仅以下四个属性可设置: 
     semid_ds::sem_perm.uid 
     semid_ds::sem_perm.gid 
     semid_ds::sem_perm.mode
     ◾IPC_RMID 
     立即删除信号量集合。 
     此时所有阻塞在对该信号量集合的,semop函数调用,都会立即返回失败,errno为EIDRM。
     ◾GETALL 
     获取信号量集合中每个信号量的计数值,通过arg.array输出。
     ◾SETALL 
     设置信号量集合中每个信号量的计数值,通过arg.array输入。
     ◾GETVAL 
     获取信号量集合中,第semnum个信号量的计数值,通过返回值输出。
     ◾SETVAL 
     设置信号量集合中,第semnum个信号量的计数值,通过arg.val输入。 
    

    注意:只有针对信号量集合中具体某个信号量的操作,才会使用semnum参数。针对整个信号量集合的操作,会忽略semnum参数。

    ◦B. 成功返回值因cmd而异,失败返回-1。

     union semun {
     	int              val;   // Value for SETVAL
     	struct semid_ds* buf;   // Buffer for IPC_STAT, IPC_SET
     	unsigned short*  array; // Array for GETALL, SETALL
     	struct seminfo*  __buf; // Buffer for IPC_INFO
     };
    
     struct semid_ds {
     	struct ipc_perm sem_perm;  // Ownership and permissions
     	time_t          sem_otime; // Last semop time
     	time_t          sem_ctime; // Last change time
     	unsigned short  sem_nsems; // No. of semaphores in set
     };
    
     struct ipc_perm {
     	key_t          __key; // 键值
     	uid_t          uid;   // 有效属主ID
     	gid_t          gid;   // 有效属组ID
     	uid_t          cuid;  // 有效创建者ID
     	gid_t          cgid;  // 有效创建组ID
     	unsigned short mode;  // 权限字
     	unsigned short __seq; // 序列号
     };
    

3. 编程模型

sem
范例:csem.cgsem.c
csem.c

#include <stdio.h>
#include <errno.h>
#include <sys/sem.h>

int pleft (int semid) 
{
    int val = semctl (semid, 0, GETVAL);
    if (val == -1) 
    {
        perror ("semctl");
        return -1;
    }

    printf ("还剩%d册。\n", val);

    return 0;
}

int main () 
{
    printf ("创建信号量...\n");

    key_t key = ftok (".", 100);
    if (key == -1) 
    {
        perror ("ftok");
        return -1;
    }

    int semid = semget (key, 1, 0644 | IPC_CREAT | IPC_EXCL);
    if (semid == -1) 
    {
        perror ("semget");
        return -1;
    }

    printf ("初始信号量...\n");

    if (semctl (semid, 0, SETVAL, 5) == -1) 
    {
        perror ("semctl");
        return -1;
    }

    int quit = 0;
    while (! quit) 
    {
        printf ("--------\n");
        printf ("三国演义\n");
        printf ("--------\n");
        printf ("[1] 借阅\n");
        printf ("[2] 归还\n");
        printf ("[0] 退出\n");
        printf ("--------\n");
        printf ("请选择:");

        int sel = -1;
        scanf ("%d", &sel);

        switch (sel) 
        {
            case 0:
                quit = 1;
                break;

            case 1: {
            //printf ("请稍候...\n");
                struct sembuf sops = {0, -1, /*0*/IPC_NOWAIT};
                if (semop (semid, &sops, 1) == -1) 
                {
                    if (errno == EAGAIN) 
                    {
                        printf ("暂时无书,下回再试。\n");
                        break;
                    }
                    else 
                    {
                        perror ("semop");
                        return -1;
                    }
                }

                printf ("恭喜恭喜,借阅成功。\n");
                pleft (semid);
                break;
            }
            case 2: 
            {
                struct sembuf sops = {0, 1, 0};
                if (semop (semid, &sops, 1) == -1) 
                {
                    perror ("semop");
                    return -1;
                }

                printf ("好借好还,再借不难。\n");
                pleft (semid);
                break;
            }
            default:
                printf ("无效选择!\n");
                scanf ("%*[^\n]");
                scanf ("%*c");
                break;
        }
    }

    printf ("销毁信号量...\n");

    if (semctl (semid, 0, IPC_RMID) == -1) 
    {
        perror ("semctl");
        return -1;
    }

    printf ("大功告成!\n");

    return 0;
}

gsem.c

#include <stdio.h>
#include <errno.h>
#include <sys/sem.h>

int pleft (int semid) 
{
    int val = semctl (semid, 0, GETVAL);
     if (val == -1) {
        perror ("semctl");
        return -1;
    }

    printf ("还剩%d册。\n", val);

    return 0;
}

int main (void) 
{
    printf ("获取信号量...\n");

    key_t key = ftok (".", 100);
    if (key == -1) 
    {
        perror ("ftok");
        return -1;
    }

    int semid = semget (key, 0, 0);
    if (semid == -1) 
    {
        perror ("semget");
        return -1;
    }

    int quit = 0;
    while (! quit) 
    {
        printf ("--------\n");
        printf ("三国演义\n");
        printf ("--------\n");
        printf ("[1] 借阅\n");
        printf ("[2] 归还\n");
        printf ("[0] 退出\n");
        printf ("--------\n");
        printf ("请选择:");

        int sel = -1;
        scanf ("%d", &sel);

        switch (sel) 
        {
            case 0:
                quit = 1;
                break;

            case 1: 
            {
            //printf ("请稍候...\n");

                struct sembuf sops = {0, -1, /*0*/IPC_NOWAIT};
                if (semop (semid, &sops, 1) == -1) 
                {
                    if (errno == EAGAIN) 
                    {
                        printf ("暂时无书,下回再试。\n");
                        break;
                    }
                    else 
                    {
                        perror ("semop");
                        return -1;
                    }
                }

                printf ("恭喜恭喜,借阅成功。\n");
                pleft (semid);
                break;
            }
            case 2: 
            {
                struct sembuf sops = {0, 1, 0};
                if (semop (semid, &sops, 1) == -1) 
                {
                    perror ("semop");
                    return -1;
                }

                printf ("好借好还,再借不难。\n");
                pleft (semid);
                break;
            }
            default:
                printf ("无效选择!\n");
                scanf ("%*[^\n]");
                scanf ("%*c");
                break;
        }
    }

    printf ("大功告成!\n");

    return 0;
}

七、IPC命令

1. 显示

ipcs -m - 显示共享内存(m: memory)
ipcs -q - 显示消息队列(q: queue)
ipcs -s - 显示信号量(s: semphore)
ipcs -a - 显示所有IPC对象(a: all)

2. 删除

ipcrm -m ID - 删除共享内存
ipcrm -q ID - 删除消息队列
ipcrm -s ID - 删除信号量

猜你喜欢

转载自blog.csdn.net/perror_0/article/details/106943986