Linuxのプロセス間通信(IPC、プロセス間通信)

 一つは、Linuxのプロセス間通信 - 共有メモリ

共有メモリは、2つの無関係のプロセスが同じ論理メモリにアクセスできるようにすることです。

用法:
1、たshmget関数
この関数はプロトタイプである共有メモリを作成するために使用されている:セマフォ操作がアトミックであります

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

(1)

  • 最初のパラメータは、セマフォsemgetから関数、プロシージャとして、パラメータは、効果的に共有メモリセグメントの名前のキー(非ゼロ整数)を提供する必要があります
  • 戻るキーたshmget成功に関連付けられた機能共有メモリ識別子以降で使用される(非負整数)、共有メモリ機能呼び出しが戻るために失敗した-1。
  • 無関係なプロセスをすることができ、同じ共有メモリにアクセスするための関数の値によって返され、プログラムがたshmget関数を呼び出すことにより、第1、すべての共有メモリへのプログラム・アクセスが間接され、リソースプログラムを使用することができる表し、キーを提供します次いで、システムは、対応する共有メモリ識別子(関数のたshmget戻り値)を生成するが、直接たshmgetファンクションキーセマフォが使用された、セマフォsemgetから関数を使用することによって返されたすべての他の識別子のセマフォ機能。

(2)第二のパラメータは、サイズがバイト単位で共有する必要が指定するメモリ容量のを
(3)第3のパラメータは、shmflgは許可フラグ、及びそれがキーである場合には、オープンモードに関数の引数として作用します共有メモリ識別子を使用すると、IPC_CREATを行うか、操作することができ、それを作成し、存在しません。ファイル許可フラグにメモリの読み取りおよび書き込み権限を共有、他の人が作成している間のような、例えば、プロセスを表し0644は、共有メモリの読み取りと書き込みのデータへのプロセスメモリの作成者が所有する共有メモリを作成することができますプロセスは、共有メモリを読み取ることができます。Linuxのプロセス間通信-共有メモリを使用します


2、にshmat機能
       を初めて使用すると、共有メモリを作成して、それがどのプロセスによってアクセスすることができない、にshmat機能の役割をするために使用される共有メモリへのアクセス、共有メモリと、現在のプロセスのアドレス空間への接続を開始します。
このプロトタイプは次のように次のとおりです。

void *shmat(int shm_id, const void *shm_addr, int shmflg);
  • 最初のパラメータは、shm_idたshmgetは、関数によって返される共有メモリを識別する
  • 第2のパラメータは、現在のプロセスのアドレス位置に接続された共有メモリを指定shm_addr、通常空共有メモリアドレスを選択するようにシステムに指示します。
  • 3番目のパラメータは、shm_flgは、典型的には0、ビットのグループです。
  • 共有メモリポインタの最初のバイト、それが成功した場合、それは-1を返し、コールが失敗した場合へのポインタを返します。

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関数が返す共有メモリ識別子。
  • 2番目のパラメータは、コマンド動作が取られるべきである、それは次の3つの値をとることができる:
    IPC_STATを:するshmid_dsデータ構造は、共有メモリ、使用される共有メモリの関連する電流値のために提供される
    関連値するshmid_dsの現在の値をカバーするように。
    プロセスがセットに十分な権限を持っている場合、現在の値するshmid_dsに関連付けられている共有メモリ構造入れる:IPC_SETは、
    与えられた値
    IPC_RMIDを:共有メモリセグメントを削除します
  • 3番目のパラメータは、buf構造体は、共有メモリ・アクセス・パターンおよび構造へのポインタです。

shmid_ds構造体は、少なくとも以下のメンバーが含まれています。

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

 例1

ここでは、共有メモリを介した方法、プロセス間通信を説明するために2つの無関係なプロセスにあります。1つのファイルは、共有メモリを作成し、その読みshmread.c
共有メモリに別のファイルshmwrite.c書き込みデータの情報を。統一された操作およびデータ構造を容易にするために、両方のファイルの同じ番号定義する
データ構造shared_use_stを構造shared_use_st 可読または書き込み可能なフラグとして書き込まれ、0は非読み取り可能、書き込み可能を表すテキストは、0を表す
メモリファイル。
Shmread.cソースファイルのソースコードは次のとおりです。

Linuxのプロセス間通信 - 共有メモリを使用します

Shmwrite.cソースファイルのソースコードは次のとおりです。 

Linuxのプロセス間通信 - 共有メモリを使用します

ラン1

方法2の実行

分析:

1、程序 shmread 创建共享内存,然后将它连接到自己的地址空间。在共享内存的开始处使用了一个结构 struct_use_st。该结构中有个标志 written,当共享内存中有其他进程向它写入数据时,共享内存中的 written 被设置为 0,程序等待。当它不为 0 时,表示没有进程对共享内存写入数据,程序就从共享内存中读取数据并输出,然后重置设置共享内存中的 written为 0,即让其可被 shmwrite 进程写入数据。
2、程序 shmwrite 取得共享内存并连接到自己的地址空间中。检查共享内存中的 written,是否为 0,若不是,表示共享内存中的数据还没有被读完,则等待其他进程读取完成,并提示用户等待waiting。若共享内存的 written 为 0,表示没有其他进程对共享内存进行读取,则提示用户输入文本,并再次设置共享内存中的 written 为 1,表示写完成,其他进程可对共享内存进行读操作。

深度分析:

这个程序是不安全的,当有多个程序同时向共享内存中读写数据时,问题就会出现。可能你会认为,可以改变一下 written 的使用方式,例如,只有当 written 为 0 时进程才可以向共享内存写入数据,而当一个进程只有在 written 不为 0 时才能对其进行读取,同时把 written进行加 1 操作,读取完后进行减 1 操作。这就有点像文件锁中的读写锁的功能。咋看之下,它似乎能行得通。但是这都不是原子操作,所以这种做法是行不能的。试想当 written 为 0时,如果有两个进程同时访问共享内存,它们就会发现 written 为 0,于是两个进程都对其进行写操作,显然不行。当 written 为 1 时,有两个进程同时对共享内存进行读操作时也是不行,当这两个进程都读取完是,written 就变成了-1.要想让程序安全地执行,就要有一种进程同步的进制,保证在进入临界区的操作是原子操作。例如,可以使用信号量来进行进程的同步。因为信号量的操作都是原子性的

使用共享内存的优缺点

1、优点:我们可以看到使用共享内存进行进程间的通信真的是非常方便,而且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。
2、缺点:共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。

 二、Linux 进程间通信——使用信号量

一、什么是信号量

1、为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
2、信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即 P(信号变量))和发送(即 V(信号变量))信息操作。最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。这里主要讨论二进制信号量。

二、信号量的工作原理

由于信号量只能进行两种操作等待和发送信号,即 P(sv)和 V(sv),他们的行为是这样的:

  • P(sv):如果 sv 的值大于零,就给它减 1;如果它的值为零,就挂起该进程的执行
  • V(sv):如果有其他进程因等待 sv 而被挂起,就让它恢复运行,如果没有进程因等待 sv 而挂起,就给它加 1.

举个例子,就是两个进程共享信号量 sv,一旦其中一个进程执行了 P(sv)操作,它将得到信号量,并可以进入临界区,使 sv 减 1。而第二个进程将被阻止进入临界区,因为当它试图执行 P(sv)时,sv 为 0,它会被挂起以等待第一个进程离开临界区域并执行 V(sv)释放信号量,这时第二个进程就可以恢复执行。

三、Linux 的信号量机制

Linux 提供了一组精心设计的信号量接口来对信号进行操作,它们不只是针对二进制信号量,下面将会对这些函数进行介绍,但请注意,这些函数都是用来对成组的信号量值进行操作的。它们声明在头文件 sys/sem.h 中。
1、semget 函数
它的作用是创建一个新信号量或打开一个已有信号量,原型为:

int semget(key_t key, int num_sems, int sem_flags);
  • 第一个参数 key 是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用 semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget 函数的返回值),只有semget 函数才直接使用信号量键,所有其他的信号量函数使用由 semget 函数返回的信号量标识符。如果多个程序使用相同的 key 值,key 将负责协调工作。
  • 第二个参数 num_sems 指定需要的信号量数目,它的值几乎总是 1。
  • 第三个参数 sem_flags 是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值 IPC_CREAT 做按位或操作。设置了 IPC_CREAT 标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而 IPC_CREAT | IPC_EXCL 则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

semget 函数成功返回一个相应信号标识符(非零),失败返回-1.
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:用来把信号量初始化为一个已知的值。p 这个值通过 union semun 中的 val 成员设置,其作用是在信号量第一次使用前对它进行设置。
  • IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。

四、进程使用信号量通信

下面使用一个例子来说明进程间如何使用信号量来进行通信,这个例子是两个相同的程序同时向屏幕输出数据,使用信号量来使两个进程协调工作,使同一时间只有一个进程可以向屏幕输出数据。如果程序是第一次被调用(为了区分,第一次调用程序时带一个要输出到屏幕中的字符作为一个参数),则需要调用 set_semvalue 函数初始化信号并将message  字符设置为传递给程序的参数的第一个字符,同时第一个启动的进程还负责信号量的删除工作。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。在 main函数中调用 semget 来创建一个信号量,该函数将返回一个信号量标识符,保存于全局变量 sem_id 中,然后以后的函数就使用这个标识符来访问信号量

seml.c

#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;
}

注:这个程序的临界区为 main 函数 for 循环不的 semaphore_p 和 semaphore_v 函数中间的代码。

结果分析

同时运行一个程序的两个实例,注意第一次运行时,要加上一个字符作为参数,例如本例中的字符‘0’,它用于区分是否为第一次调用,同时这个字符输出到屏幕中。因为每个程序都在其进入临界区后和离开临界区前打印一个字符,所以每个字符都应该成对出现,正如你看到的上图的输出那样。在 main 函数中循环中我们可以看到,每次进程要访问stdout(标准输出),即要输出字符时,每次都要检查信号量是否可用(即 stdout 有没有正在被其他进程使用)。所以,当一个进程 A 在调用函数 semaphore_p 进入了临界区,输出字符后,调用 sleep 时,另一个进程 B 可能想访问 stdout,但是信号量的 P 请求操作失败,只能挂起自己的执行,当进程 A 调用函数 semaphore_v 离开了临界区,进程 B 马上被恢复执行。然后进程 A 和进程 B 就这样一直循环了 10 次

五、对比例子——进程间的资源竞争

看了上面的例子,你可能还不是很明白,不过没关系,下面我就以另一个例子来说明一下,它实现的功能与前面的例子一样,运行方式也一样,都是两个相同的进程,同时向 stdout 中输出字符,只是没有使用信号量,两个进程在互相竞争 stdout。它的代码非常简单,文件名为 ex3_normalprint.c,代码如下:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    char message = 'X';
    int i = 0;
    if(argc > 1)
        message = argv[1][0];
    for(i = 0; i < 10; ++i)
    {
        printf("%c", message);
        fflush(stdout);
        sleep(rand() % 3);
        printf("%c", message);
        fflush(stdout);
        sleep(rand() % 2);
    }
    sleep(10);
    printf("\n%d - finished\n", getpid());
    exit(EXIT_SUCCESS);
}

 运行结果:

例子分析:

从上面的输出结果,我们可以看到字符‘X’和‘O’并不像前面的例子那样,总是成对出现,因为当第一个进程 A 输出了字符后,调用 sleep 休眠时,另一个进程 B 立即输出并休眠,而进程 A 醒来时,再继续执行输出,同样的进程 B 也是如此。所以输出的字符就是不成对的出现。这两个进程在竞争 stdout 这一共同的资源。通过两个例子的对比,我想信号量的意义和使用应该比较清了。

 

发布了43 篇原创文章 · 获赞 23 · 访问量 5318

おすすめ

転載: blog.csdn.net/weixin_43442778/article/details/95348507