Linux进程基础行为(二)

本节主要讲Linux进程间通信
在Linux中,各个进程都共享内核空间,因此LInux进程通信中的管道,消息队列等都将相关数据保存在内核空间中。进程间通信也可以在用户空间进行,例如,同一个进程中的线程可以使用相同的用户空间地址就可以通信。不同进程之间的空间尽管相互隔离,但也可以通过共享内存的方式进行通信,共享内存的申请需要内核来完成。
进程间通信可以分为:

  1. 管道:无名管道(父子进程或两进程有同一个祖先)和有名管道(fifo,用于不同进程间通信)
  2. 信号:仅用于向一个进程或者进程组发送某个事件已发送的标志
  3. 信号量:用于用户态代码的时序控制。
  4. 消息队列:让进程在预定义的消息队列中写消息和读消息实现进程之间的通信
  5. 共享内存:使两个不同进程的内存空间映射到相同的物理区域,这种方式比较高效
  6. 套接字:不同进程之间通过socket通信。

1 无名管道

1. ls|more 的内部实现

  1. shell调用pipe()系统调用,创建一个管道并返回一对文件描述符。 3读,4写
  2. shell调用两次fork()创建两个子进程,使用exec()将两个子进程替换为ls和more进程
  3. shell使用close()关闭文件并释放描述符3,4
  4. ls使用dup2(4,1)将文件描述符复制到标准输出上,使用close()关闭文件并释放描述符3,4
  5. more使用dup2(,0)将文件描述符复制到标准输入上,使用close()关闭文件并释放描述符3,4


    image.png
image.png

2. pipe调用

#include <unistd.h>  
int pipe(int file_descriptor[2]); 
#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  
  
int main()  
{  
    int data_processed = 0;  
    int filedes[2];  
    const char data[] = "Hello pipe!";  
    char buffer[BUFSIZ + 1];  
    pid_t pid;  
    //清空缓冲区  
    memset(buffer, '\0', sizeof(buffer));  
  
    if(pipe(filedes) == 0)  
    {  
        //创建管道成功  
        //通过调用fork创建子进程  
        pid = fork();  
        if(pid == -1)  
        {  
            fprintf(stderr, "Fork failure");  
            exit(EXIT_FAILURE);  
        }  
        if(pid == 0)  
        {  
            //子进程中  
            //读取数据  
            data_processed = read(filedes[0], buffer, BUFSIZ);  
            printf("Read %d bytes: %s\n", data_processed, buffer);  
            exit(EXIT_SUCCESS);  
        }  
        else  
        {  
            //父进程中  
            //写数据  
            data_processed = write(filedes[1], data, strlen(data));  
            printf("Wrote %d bytes: %s\n", data_processed, data);  
            //休眠2秒,主要是为了等子进程先结束,这样做也只是纯粹为了输出好看而已  
            //父进程其实没有必要等等子进程结束  
            sleep(2);  
            exit(EXIT_SUCCESS);  
        }  
    }  
    exit(EXIT_FAILURE);  
}  

3. popen函数和pclose函数

popen函数和pclose函数对pipe()系统调用进行了封装。

#include <stdio.h>  
FILE* popen (const char *command, const char *open_mode);  
int pclose(FILE *stream_to_close);  

poen函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。command是要运行的程序名和相应的参数。open_mode只能是"r(只读)"和"w(只写)"的其中之一。

很多时候,我们根本就不知道输出数据的长度,为了避免定义一个非常大的数组作为缓冲区,我们可以以块的方式来发送数据,一次读取一个块的数据并发送一个块的数据,直到把所有的数据都发送完。

#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  
  
int main()  
{  
    FILE *read_fp = NULL;  
    FILE *write_fp = NULL;  
    char buffer[BUFSIZ + 1];  
    int chars_read = 0;  
      
    //初始化缓冲区  
    memset(buffer, '\0', sizeof(buffer));  
    //打开ls和grep进程  
    read_fp = popen("ls -l", "r");  
    write_fp = popen("grep rwxrwxr-x", "w");  
    //两个进程都打开成功  
    if(read_fp && write_fp)  
    {  
        //读取一个数据块  
        chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);  
        while(chars_read > 0)  
        {  
            buffer[chars_read] = '\0';  
            //把数据写入grep进程  
            fwrite(buffer, sizeof(char), chars_read, write_fp);  
            //还有数据可读,循环读取数据,直到读完所有数据  
            chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);  
        }  
        //关闭文件流  
        pclose(read_fp);  
        pclose(write_fp);  
        exit(EXIT_SUCCESS);  
    }  
    exit(EXIT_FAILURE);  
}  

4.总结

  1. popen函数优缺点
    优点 在Linux中所有的参数扩展都是由shell来完成的。所以在启动程序(command中的命令程序)之前先启动shell来分析命令字符串,也就可以使各种shell扩展(如通配符)在程序启动之前就全部完成,这样我们就可以通过popen启动非常复杂的shell命令缺点::对于每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,即每一个popen调用将启动两个进程,从效率和资源的角度看,popen函数的调用比正常方式要慢一些。

  2. 从函数的原型我们可以看到,它跟popen函数的一个重大区别是,popen函数是基于文件流(FILE)工作的,而pipe是基于文件描述符工作的,所以在使用pipe后,数据必须要用底层的read和write调用来读取和发送。

  3. 现在有这样一个问题,假如父进程向管道file_pipe1写数据,而子进程在管道file_pipe[0]中读取数据,当父进程没有向file_pipe1写数据时,子进程则没有数据可读,则子进程会发生什么呢?再者父进程把file_pipe1关闭了,子进程又会有什么反应呢?

当写数据的管道没有关闭,而又没有数据可读时,read调用通常会阻塞,但是当写数据的管道关闭时,read调用将会返回0而不是阻塞。注意,这与读取一个无效的文件描述符不同,read一个无效的文件描述符返回-1。

2. 信号量

为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。

信号量是一个特殊的变量,程序对其访问都是原子操作且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。这里主要讨论二进制信号量。

1. semget函数

它的作用是创建一个新信号量或取得一个已有信号量,原型为:

int semget(key_t key, int num_sems, int sem_flags); 

第一个参数key是整数值(唯一非零),用来标识一个全局唯一的信号量集,要通过信号量通信的进程需要使用相同的键值来创建/获取该信号量。

第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。

第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

2.semop函数

它的作用是改变信号量的值,原型为:

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops); 

sem_id是由semget返回的信号量标识符,sembuf结构的定义如下:

struct sembuf{  
    short sem_num;//除非使用一组信号量,否则它为0  
    short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,  
                    //一个是+1,即V(发送信号)操作。  
    short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,  
                    //并在进程没有释放该信号量而终止时,操作系统释放信号量  
};  

3.semctl函数

该函数用来直接控制信号量信息,它的原型为:

int semctl(int sem_id, int sem_num, int command, ...); 

如果有第四个参数,它通常是一个union semum结构,定义如下:

union semun{  
    int val;  
    struct semid_ds *buf;  
    unsigned short *arry;  
};  

command通常是下面两个值中的其中一个
SETVAL:用来把信号量初始化为一个已知的值。这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。

4. 使用信号量通信实例

如果程序是第一次被调用(为了区分,第一次调用程序时带一个要输出到屏幕中的字符作为一个参数),则需要调用set_semvalue函数初始化信号并将message字符设置为传递给程序的参数的第一个字符,同时第一个启动的进程还负责信号量的删除工作。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。

#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  
#include <sys/sem.h>  
  
union semun  
{  
    int val;  
    struct semid_ds *buf;  
    unsigned short *arry;  
};  
  
static int sem_id = 0;  
  
static int set_semvalue();  
static void del_semvalue();  
static int semaphore_p();  
static int semaphore_v();  
  
int main(int argc, char *argv[])  
{  
    char message = 'X';  
    int i = 0;  
  
    //创建信号量  
    sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);  
  
    if(argc > 1)  
    {  
        //程序第一次被调用,初始化信号量  
        if(!set_semvalue())  
        {  
            fprintf(stderr, "Failed to initialize semaphore\n");  
            exit(EXIT_FAILURE);  
        }  
        //设置要输出到屏幕中的信息,即其参数的第一个字符  
        message = argv[1][0];  
        sleep(2);  
    }  
    for(i = 0; i < 10; ++i)  
    {  
        //进入临界区  
        if(!semaphore_p())  
            exit(EXIT_FAILURE);  
        //向屏幕中输出数据  
        printf("%c", message);  
        //清理缓冲区,然后休眠随机时间  
        fflush(stdout);  
        sleep(rand() % 3);  
        //离开临界区前再一次向屏幕输出数据  
        printf("%c", message);  
        fflush(stdout);  
        //离开临界区,休眠随机时间后继续循环  
        if(!semaphore_v())  
            exit(EXIT_FAILURE);  
        sleep(rand() % 2);  
    }  
  
    sleep(10);  
    printf("\n%d - finished\n", getpid());  
  
    if(argc > 1)  
    {  
        //如果程序是第一次被调用,则在退出前删除信号量  
        sleep(3);  
        del_semvalue();  
    }  
    exit(EXIT_SUCCESS);  
}  
  
static int set_semvalue()  
{  
    //用于初始化信号量,在使用信号量前必须这样做  
    union semun sem_union;  
  
    sem_union.val = 1;  
    if(semctl(sem_id, 0, SETVAL, sem_union) == -1)  
        return 0;  
    return 1;  
}  
  
static void del_semvalue()  
{  
    //删除信号量  
    union semun sem_union;  
  
    if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)  
        fprintf(stderr, "Failed to delete semaphore\n");  
}  
  
static int semaphore_p()  
{  
    //对信号量做减1操作,即等待P(sv)  
    struct sembuf sem_b;  
    sem_b.sem_num = 0;  
    sem_b.sem_op = -1;//P()  
    sem_b.sem_flg = SEM_UNDO;  
    if(semop(sem_id, &sem_b, 1) == -1)  
    {  
        fprintf(stderr, "semaphore_p failed\n");  
        return 0;  
    }  
    return 1;  
}  
  
static int semaphore_v()  
{  
    //这是一个释放操作,它使信号量变为可用,即发送信号V(sv)  
    struct sembuf sem_b;  
    sem_b.sem_num = 0;  
    sem_b.sem_op = 1;//V()  
    sem_b.sem_flg = SEM_UNDO;  
    if(semop(sem_id, &sem_b, 1) == -1)  
    {  
        fprintf(stderr, "semaphore_v failed\n");  
        return 0;  
    }  
    return 1;  
}  
enter description here
enter description here

5.总结

信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。我们通常通过信号来解决多个进程对同一资源的访问竞争的问题,使在任一时刻只能有一个执行线程访问代码的临界区域,也可以说它是协调进程间的对同一资源的访问权,也就是用于同步进程的。

3. 共享内存

共享内存就是允许两个不相关的进程访问同一段物理内存,是最高效的IPC通信机制。但是,共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如前面说到的信号量。

1.shmget函数

该函数用来创建共享内存,它的原型为:

int shmget(key_t key, size_t size, int shmflg);  

key,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.
size以字节为单位指定需要共享的内存容量
shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。

2.shmat函数

第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:

void *shmat(int shm_id, const void *shm_addr, int shmflg);  

shm_id是由shmget函数返回的共享内存标识。
shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
shm_flg是一组标志位,通常为0。

3. shmdt函数

该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:

int shmdt(const void *shmaddr);  

参数shmaddr是shmat函数返回的地址指针,调用成功时返回0,失败时返回-1.

4.shmctl函数

与信号量的semctl函数一样,用来控制共享内存,它的原型如下:

int shmctl(int shm_id, int command, struct shmid_ds *buf);  

shm_id是shmget函数返回的共享内存标识符
command是要采取的操作,它可以取下面的三个值 :

  • IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
  • IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的
  • IPC_RMID:删除共享内存段

buf是一个结构指针,它指向共享内存模式和访问权限的结构。

struct shmid_ds  
{  
    uid_t shm_perm.uid;  
    uid_t shm_perm.gid;  
    mode_t shm_perm.mode;  
};  

5. 共享内存实例

shmdata.h的源代码如下:

#ifndef _SHMDATA_H_HEADER  
#define _SHMDATA_H_HEADER  
  
#define TEXT_SZ 2048  
  
struct shared_use_st  
{  
    int written;//作为一个标志,非0:表示可读,0表示可写  
    char text[TEXT_SZ];//记录写入和读取的文本  
};  
  
#endif  

源文件shmread.c的源代码如下:

#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <sys/shm.h>  
#include "shmdata.h"  
  
int main()  
{  
    int running = 1;//程序是否继续运行的标志  
    void *shm = NULL;//分配的共享内存的原始首地址  
    struct shared_use_st *shared;//指向shm  
    int shmid;//共享内存标识符  
    //创建共享内存  
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);  
    if(shmid == -1)  
    {  
        fprintf(stderr, "shmget failed\n");  
        exit(EXIT_FAILURE);  
    }  
    //将共享内存连接到当前进程的地址空间  
    shm = shmat(shmid, 0, 0);  
    if(shm == (void*)-1)  
    {  
        fprintf(stderr, "shmat failed\n");  
        exit(EXIT_FAILURE);  
    }  
    printf("\nMemory attached at %X\n", (int)shm);  
    //设置共享内存  
    shared = (struct shared_use_st*)shm;  
    shared->written = 0;  
    while(running)//读取共享内存中的数据  
    {  
        //没有进程向共享内存定数据有数据可读取  
        if(shared->written != 0)  
        {  
            printf("You wrote: %s", shared->text);  
            sleep(rand() % 3);  
            //读取完数据,设置written使共享内存段可写  
            shared->written = 0;  
            //输入了end,退出循环(程序)  
            if(strncmp(shared->text, "end", 3) == 0)  
                running = 0;  
        }  
        else//有其他进程在写数据,不能读取数据  
            sleep(1);  
    }  
    //把共享内存从当前进程中分离  
    if(shmdt(shm) == -1)  
    {  
        fprintf(stderr, "shmdt failed\n");  
        exit(EXIT_FAILURE);  
    }  
    //删除共享内存  
    if(shmctl(shmid, IPC_RMID, 0) == -1)  
    {  
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");  
        exit(EXIT_FAILURE);  
    }  
    exit(EXIT_SUCCESS);  
}  

源文件shmwrite.c的源代码如下:

#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  
#include <sys/shm.h>  
#include "shmdata.h"  
  
int main()  
{  
    int running = 1;  
    void *shm = NULL;  
    struct shared_use_st *shared = NULL;  
    char buffer[BUFSIZ + 1];//用于保存输入的文本  
    int shmid;  
    //创建共享内存  
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);  
    if(shmid == -1)  
    {  
        fprintf(stderr, "shmget failed\n");  
        exit(EXIT_FAILURE);  
    }  
    //将共享内存连接到当前进程的地址空间  
    shm = shmat(shmid, (void*)0, 0);  
    if(shm == (void*)-1)  
    {  
        fprintf(stderr, "shmat failed\n");  
        exit(EXIT_FAILURE);  
    }  
    printf("Memory attached at %X\n", (int)shm);  
    //设置共享内存  
    shared = (struct shared_use_st*)shm;  
    while(running)//向共享内存中写数据  
    {  
        //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本  
        while(shared->written == 1)  
        {  
            sleep(1);  
            printf("Waiting...\n");  
        }  
        //向共享内存中写入数据  
        printf("Enter some text: ");  
        fgets(buffer, BUFSIZ, stdin);  
        strncpy(shared->text, buffer, TEXT_SZ);  
        //写完数据,设置written使共享内存段可读  
        shared->written = 1;  
        //输入了end,退出循环(程序)  
        if(strncmp(buffer, "end", 3) == 0)  
            running = 0;  
    }  
    //把共享内存从当前进程中分离  
    if(shmdt(shm) == -1)  
    {  
        fprintf(stderr, "shmdt failed\n");  
        exit(EXIT_FAILURE);  
    }  
    sleep(2);  
    exit(EXIT_SUCCESS);  
}  

6. 总结

共享内存的优缺点:
1、优点:我们可以看到使用共享内存进行进程间的通信真的是非常方便,而且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。

2、缺点:共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。

4. 消息队列

1. msgget函数

该函数用来创建和访问一个消息队列。它的原型为:

int msgget(key_t, key, int msgflg);  

与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。

它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1.

2.msgsnd函数

该函数用来把消息添加到消息队列中。它的原型为:

int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);  

msgid是由msgget函数返回的消息队列标识符。

msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:

struct my_message{  
    long int message_type;  
    /* The data you wish to transfer*/  
};  

msg_sz是msg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。

msgflg用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。

如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1.

3.msgrcv函数

该函数用来从一个消息队列获取消息,它的原型为

int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);  

msgid, msg_ptr, msg_st的作用也函数msgsnd函数的一样。

msgtype可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。

msgflg用于控制当队列中没有相应类型的消息可以接收时将发生的事情。

调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1.

4.msgctl函数

该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:

int msgctl(int msgid, int command, struct msgid_ds *buf);  

command是将要采取的动作,它可以取3个值,
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列

buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:

struct msgid_ds  
{  
    uid_t shm_perm.uid;  
    uid_t shm_perm.gid;  
    mode_t shm_perm.mode;  
};  

成功时返回0,失败时返回-1.

5. 消息队列通信实例

接收信息的程序源文件为msgreceive.c的源代码为:

#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  
#include <errno.h>  
#include <sys/msg.h>  
  
struct msg_st  
{  
    long int msg_type;  
    char text[BUFSIZ];  
};  
  
int main()  
{  
    int running = 1;  
    int msgid = -1;  
    struct msg_st data;  
    long int msgtype = 0; //注意1  
  
    //建立消息队列  
    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);  
    if(msgid == -1)  
    {  
        fprintf(stderr, "msgget failed with error: %d\n", errno);  
        exit(EXIT_FAILURE);  
    }  
    //从队列中获取消息,直到遇到end消息为止  
    while(running)  
    {  
        if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)  
        {  
            fprintf(stderr, "msgrcv failed with errno: %d\n", errno);  
            exit(EXIT_FAILURE);  
        }  
        printf("You wrote: %s\n",data.text);  
        //遇到end结束  
        if(strncmp(data.text, "end", 3) == 0)  
            running = 0;  
    }  
    //删除消息队列  
    if(msgctl(msgid, IPC_RMID, 0) == -1)  
    {  
        fprintf(stderr, "msgctl(IPC_RMID) failed\n");  
        exit(EXIT_FAILURE);  
    }  
    exit(EXIT_SUCCESS);  
}  

发送信息的程序的源文件msgsend.c的源代码为:

#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  
#include <sys/msg.h>  
#include <errno.h>  
  
#define MAX_TEXT 512  
struct msg_st  
{  
    long int msg_type;  
    char text[MAX_TEXT];  
};  
  
int main()  
{  
    int running = 1;  
    struct msg_st data;  
    char buffer[BUFSIZ];  
    int msgid = -1;  
  
    //建立消息队列  
    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);  
    if(msgid == -1)  
    {  
        fprintf(stderr, "msgget failed with error: %d\n", errno);  
        exit(EXIT_FAILURE);  
    }  
  
    //向消息队列中写消息,直到写入end  
    while(running)  
    {  
        //输入数据  
        printf("Enter some text: ");  
        fgets(buffer, BUFSIZ, stdin);  
        data.msg_type = 1;    //注意2  
        strcpy(data.text, buffer);  
        //向队列发送数据  
        if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)  
        {  
            fprintf(stderr, "msgsnd failed\n");  
            exit(EXIT_FAILURE);  
        }  
        //输入end结束输入  
        if(strncmp(buffer, "end", 3) == 0)  
            running = 0;  
        sleep(1);  
    }  
    exit(EXIT_SUCCESS);  
}  

5.进程间传递文件描述符

Linux 系统系下,子进程会自动继承父进程已打开的描述符。但是,两个无关的进程要想共享文件描述符需要文件描述符的传递了。但是,传递文件描述符并不是传递文件描述符的整数值,而是在接受进程中创建一个新的文件描述符,并且该文件描述符和发送进程中的文件描述符指向内核中相同的文件表项。

简单的说,首先需要在这两个进程之间建立一个 Unix 域套接字接口作为消息传递的通道( Linux 系统上使用 socketpair 函数可以很方面便的建立起传递通道),然后发送进程调用 sendmsg 向通道发送一个特殊的消息,内核将对这个消息做特殊处理,从而将打开的描述符传递到接收进程。然后接收方调用 recvmsg 从通道接收消息,从而得到打开的描述符。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include <sys/socket.h> /* for socketpair */

#define MY_LOGO         "– Tony Bai"

static int send_fd(int fd, int fd_to_send)
{
        struct iovec    iov[1];
        struct msghdr   msg;
        char            buf[1];

        if (fd_to_send >= 0) {
                msg.msg_accrights       = (caddr_t)&fd_to_send;
                msg.msg_accrightslen    = sizeof(int);
        } else {
                msg.msg_accrights       = (caddr_t)NULL;
                msg.msg_accrightslen    = 0;
        }

        msg.msg_name    = NULL;
        msg.msg_namelen = 0;

        iov[0].iov_base = buf;
        iov[0].iov_len  = 1;
        msg.msg_iov     = iov;
        msg.msg_iovlen  = 1;

        if(sendmsg(fd, &msg, 0) < 0) {
                printf("sendmsg error, errno is %d\n", errno);
                return errno;
        }

        return 0;
}

static int recv_fd(int fd, int *fd_to_recv)
{
        struct iovec    iov[1];
        struct msghdr   msg;
        char            buf[1];

        msg.msg_accrights       = (caddr_t)fd_to_recv;
        msg.msg_accrightslen    = sizeof(int);

        msg.msg_name    = NULL;
        msg.msg_namelen = 0;

        iov[0].iov_base = buf;
        iov[0].iov_len  = 1;
        msg.msg_iov     = iov;
        msg.msg_iovlen  = 1;

        if (recvmsg(fd, &msg, 0) < 0) {
                return errno;
        }

        if(msg.msg_accrightslen != sizeof(int)) {
                *fd_to_recv = -1;
        }

        return 0;
}

int x_sock_set_block(int sock, int on)
{
        int             val;
        int             rv;

        val = fcntl(sock, F_GETFL, 0);
        if (on) {
                rv = fcntl(sock, F_SETFL, ~O_NONBLOCK&val);
        } else {
                rv = fcntl(sock, F_SETFL, O_NONBLOCK|val);
        }

        if (rv) {
                return errno;
        }

        return 0;
}

int main() {
        pid_t   pid;
        int     sockpair[2];
        int     rv;
        char    fname[256];
        int     fd;

        rv = socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair);
        if (rv < 0) {
                printf("Call socketpair error, errno is %d\n", errno);
                return errno;
        }

        pid = fork();
        if (pid == 0) {
                /* in child */
                close(sockpair[1]);

                for ( ; ; ) {
                        rv = x_sock_set_block(sockpair[0], 1);
                        if (rv != 0) {
                                printf("[CHILD]: x_sock_set_block error, errno is %d\n", rv);
                                break;
                        }

                        rv = recv_fd(sockpair[0], &fd);
                        if (rv < 0) {
                                printf("[CHILD]: recv_fd error, errno is %d\n", rv);
                                break;
                        }

                        if (fd < 0) {
                                printf("[CHILD]: child process exit normally!\n");
                                break;
                        }

                       /* 处理fd描述符对应的文件 */
                        rv = write(fd, MY_LOGO, strlen(MY_LOGO));
                        if (rv < 0) {
                                printf("[CHILD]: write error, errno is %d\n", rv);
                        } else {
                                printf("[CHILD]: append logo successfully\n");
                        }
                        close(fd);
                }

          
      exit(0);
        }

        /* in parent */
        for ( ; ; ) {
                memset(fname, 0, sizeof(fname));
                printf("[PARENT]: please enter filename:\n");
                scanf("%s", fname);

                if (strcmp(fname, "exit") == 0) {
                        rv = send_fd(sockpair[1], -1);
                        if (rv < 0) {
                                printf("[PARENT]: send_fd error, errno is %d\n", rv);
                        }
                        break;
                }

                fd = open(fname, O_RDWR | O_APPEND);
                if (fd < 0) {
                        if (errno == ENOENT) {
                                printf("[PARENT]: can’t find file ‘%s’\n", fname);
                                continue;
                        }
                        printf("[PARENT]: open file error, errno is %d\n", errno);
                }

                rv = send_fd(sockpair[1], fd);
                if (rv != 0) {
                        printf("[PARENT]: send_fd error, errno is %d\n", rv);
                }

                close(fd);
        }

        wait(NULL);
        return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容