linuxでのプロセス通信

8885 ワード

パイプは開いているファイルとみなされますが、インストールされているファイルシステムには対応するイメージがありません.
POSIXは半二重のパイプのみを定義するので、pipe()システム呼び出しが2つの記述子を返したとしても、各プロセスは1つのファイル記述子を使用する前に別のファイル記述子を閉じなければならない.
linuxではpopen()とpclose()がC関数ライブラリに含まれています.
popen()関数が呼び出されると、親プロセスとサブプロセスはパイプを通じて情報を交換することができます.親プロセスは、この関数を使用して、返されたFILEポインタを呼び出してデータを読み書きできます.サブプロセスが実行するプログラムは,標準出力または標準入力にそれぞれデータを読み書きする.
各パイプでは、カーネルにはインデックスノードと2つのファイルオブジェクトが作成され、1つのファイルオブジェクトは読み取り、1つのファイルオブジェクトは書き込みに使用されます.
インデックスノードがパイプを指す場合、そのi_pipeフィールドはpipe_を指しますinde_infoの構造は、1つのインデックスノードオブジェクトと2つのファイルオブジェクトに加えて、各パイプには独自のパイプバッファ(pipe_buffer)があり、実際には個別のページです.
パイプラインはVFSオブジェクトのセットとして実装されているため、対応するディスクイメージはありません.???
init_pipe_fs()関数pipefsファイルシステムを登録してインストールします.
static struct file_system_type pipe_fs_type = {
     .name       = "pipefs",
     .mount      = pipefs_mount,
     .kill_sb    = kill_anon_super,
 };
 
 static int __init init_pipe_fs(void)
 {
     int err = register_filesystem(&pipe_fs_type);
 
     if (!err) {
         pipe_mnt = kern_mount(&pipe_fs_type);
         if (IS_ERR(pipe_mnt)) {
             err = PTR_ERR(pipe_mnt);
             unregister_filesystem(&pipe_fs_type);
         }
     }
     return err;
 }
pipe()システム呼び出しは、以下の関数によって処理される.
SYSCALL_DEFINE1(pipe, int __user *, fildes)
 {
     return sys_pipe2(fildes, 0);
 }
 SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags)
 {
     int fd[2];
     int error;
 
     error = do_pipe_flags(fd, flags);
     if (!error) {
         if (copy_to_user(fildes, fd, sizeof(fd))) {
             sys_close(fd[0]);
             sys_close(fd[1]);
             error = -EFAULT;
         }
     }
     return error;
 }

パイプ読み取り操作は、ブロックされなくてもよい.この場合、読み取り動作は、利用可能なすべてのバイト(0個であっても)がユーザアドレス空間にコピーされると完了する.
read()システム呼び出しは、パイプが空で、現在パイプとのコラボレーションを関連するファイルオブジェクトを使用しているプロセスがない場合にのみ返されます.
2つ以上のプロセスが同時に1つのパイプに書き込まれる場合、4096バイト未満(パイプバッファサイズ)の書き込みは、一意のプロセスと同じパイプの書き込み操作を交差させることなく、単独で完了する必要があります.ただし、4096バイトを超える書き込みは分割可能であり、プロセススリープを強制的に呼び出すこともできます.
パイプにリードプロセスがない場合(つまりパイプのインデックスノードオブジェクトのreadersフィールドの値が0の場合)、パイプに対して実行される書き込み操作は失敗します.この場合、カーネルは書き込みプロセスにSIGPIPE信号を送信し、write()系統括呼び出しを停止してEPIPEエラーコードを返します.このエラーコードは私たちがよく知っている「Broken pipe」のメッセージです.
パイプが共通の祖先プロセスによって作成されない限り、任意の2つのプロセスが同じパイプを共有することはできません.
FIFOは、ファイルシステムにディスクブロックがなく、開いているFIFOは常に1つのカーネルバッファに関連付けられており、このバッファは2つ以上のプロセス間で交換されたデータを一時的に格納します.
2つの主な違いがあります.FIFOインデックスノードはpipefs特殊ファイルシステムではなくシステムディレクトリツリーに表示されます.FIFOは双方向通信パイプであり、すなわち、FIFOを読み書きモードで開くことができる.
FIFOが作成されると、通常のopen()、read()、write()、close()システムを使用してfifoへのアクセスを呼び出すことができますが、VFSはFIFOのインデックスノードやファイル操作が専用であり、FIFOが存在するファイルシステムに依存しないため、FIFOに対する処理方法が特殊です.
IPCデータ構造は、プロセスがIPCリソース(信号量、メッセージキュー、または共有メモリ領域)を要求するときに動的に作成される.各IPCリソースは永続的です.プロセスによって表示されて解放されない限り、システムが閉じるまでメモリに常駐します.IPCリソースは、先祖から作成されたリソースを共有しないプロセスを含む任意のプロセスで使用できます.
各新しいリソースにはIPCキーワードが識別され、IPC識別子はカーネルによってIPCリソースに割り当てられ、システム内部では一意であり、IPCキーワードはプログラマーが自由に選択することができる.
2つ以上のプロセスが1つのIPCリソースプロセスを介して通信する場合、これらのプロセスは、そのリソースのIPC識別子を参照します.
新しいリソースが信号量であるか、メッセージキューであるか、共有メモリ領域であるかに応じて、semget()、msgget()またはshmget()関数をそれぞれ呼び出してIPCリソースを作成します.
IPCリソースの各タイプにはipc_があります.idsデータ構造、kern_ごとipc_permデータ構造はIPCリソースに関連付けられています.
 SYSCALL_DEFINE6(ipc, unsigned int, call, int, first, unsigned long, second,
         unsigned long, third, void __user *, ptr, long, fifth)
 {
     int version, ret;
 
     version = call >> 16; /* hack for backward compatibility */
     call &= 0xffff;
 
     switch (call) {
     case SEMOP:
         return sys_semtimedop(first, (struct sembuf __user *)ptr,
                       second, NULL);
     case SEMTIMEDOP:
         return sys_semtimedop(first, (struct sembuf __user *)ptr,
                       second,
                       (const struct timespec __user *)fifth);
 
     case SEMGET:
         return sys_semget(first, second, third);
     case SEMCTL: {
         union semun fourth;
         if (!ptr)
             return -EINVAL;
         if (get_user(fourth.__pad, (void __user * __user *) ptr))
             return -EFAULT;
         return sys_semctl(first, second, third, fourth);
     }
 
     case MSGSND:
         return sys_msgsnd(first, (struct msgbuf __user *) ptr,
                   second, third);
     case MSGRCV:
         switch (version) {
         case 0: {
             struct ipc_kludge tmp;
             if (!ptr)
                 return -EINVAL;
 
           if (copy_from_user(&tmp,
                        (struct ipc_kludge __user *) ptr,
                        sizeof(tmp)))
                 return -EFAULT;
             return sys_msgrcv(first, tmp.msgp, second,
                        tmp.msgtyp, third);
         }
         default:
             return sys_msgrcv(first,
                        (struct msgbuf __user *) ptr,
                        second, fifth, third);
         }
     case MSGGET:
         return sys_msgget((key_t) first, second);
     case MSGCTL:
         return sys_msgctl(first, second, (struct msqid_ds __user *)ptr);
 
     case SHMAT:
         switch (version) {
         default: {
             unsigned long raddr;
             ret = do_shmat(first, (char __user *)ptr,
                        second, &raddr);
             if (ret)
                 return ret;
             return put_user(raddr, (unsigned long __user *) third);
         }
         case 1:
             /*
              * This was the entry point for kernel-originating calls
              * from iBCS2 in 2.2 days.
              */
             return -EINVAL;
         }
     case SHMDT:
         return sys_shmdt((char __user *)ptr);
     case SHMGET:
        return sys_shmget(first, second, third);
     case SHMCTL:
         return sys_shmctl(first, second,
                    (struct shmid_ds __user *) ptr);
     default:
         return -ENOSYS;
      }
 }

保護されたリソースが利用可能である場合、信号量の値は正数であり、保護されたリソースが現在使用可能でない場合、信号量の値は0である.リソースにアクセスするプロセスは信号量の値を1に減らそうとするが、カーネルはこのプロセスをブロックし、この信号量の操作が正の値を生成することを知り、プロセスが保護されたプロセスリソースを解放すると、信号量の値を1増加する.
1 semget()カプセル化関数を呼び出してIPC信号量識別子を取得し、パラメータとして共有リソースを保護するIPC信号量を指定するIPCキーワード2 semop()カプセル化関数を呼び出して元の信号量に関するすべての値をテストし、減算する.すべてのテストが成功した場合、減算操作を実行し、関数を終了し、このプロセスが保護されたリソースにアクセスできるようにします.3保護されたリソースを放棄すると、semop関数が再び呼び出され、関連するすべての元の信号量4が原子的に増加するように選択され、semctl()カプセル化関数が呼び出され、パラメータでIPC_が指定されるRMIDコマンドはこのIPC信号量をシステムから削除します.
カーネルは、各IPC信号量に、sem_queueデータ構造の双方向チェーンテーブルである配列内の1つ(または複数)の信号量を待機しているプロセスを識別するための保留信号キューを割り当てた.
struct semid_ds {
      struct ipc_perm sem_perm;       /* permissions .. see ipc.h */
      __kernel_time_t sem_otime;      /* last semop time */
      __kernel_time_t sem_ctime;      /* last change time */
      struct sem  *sem_base;      /* ptr to first semaphore in array */
      struct sem_queue *sem_pending;      /* pending operations to be processe    d */
      struct sem_queue **sem_pending_last;    /* last pending operation */
      struct sem_undo *undo;          /* undo requests on this array */
      unsigned short  sem_nsems;      /* no. of semaphores in array */
  };
/* One queue for each sleeping process in the system. */
 struct sem_queue {
     struct list_head    simple_list; /* queue of pending operations */
     struct list_head    list;    /* queue of pending operations */
     struct task_struct  *sleeper; /* this process */
     struct sem_undo     *undo;   /* undo structure */
     int             pid;     /* process id of requesting process */
     int             status;  /* completion status of operation */
     struct sembuf       *sops;   /* array of pending operations */
     int         nsops;   /* number of operations */
     int         alter;   /* does the operation alter the array? */
 };

メッセージは、固定サイズのヘッダと可変長の本文で構成され、整数値(メッセージタイプ)を使用してメッセージを識別できます.これにより、プロセスがメッセージキューからメッセージを選択的に取得できます.プロセスがIPCメッセージキューからメッセージを読み出すと、カーネルはこのメッセージを削除します.したがって、指定されたメッセージを受信するプロセスは1つしかありません.
各メッセージは、1つ以上の動的割り当てのページに別々に保存されます.最初のページの最初の部分はメッセージヘッダを格納し、メッセージヘッダはmsg_である.msgタイプのデータ構造.
 /* one msg_msg structure for each message */
  struct msg_msg {
      struct list_head m_list;
      long  m_type;          
      int m_ts;           /* message text size */
      struct msg_msgseg* next;
      void *security;
      /* the actual message follows immediately */
  };

プロセスが共有メモリ領域に格納されたデータ構造にアクセスする場合は、新しいメモリ領域を自分のアドレス空間に追加する必要があります.この共有メモリ領域に関連するページボックスがマッピングされます.このようなページボックスは、カーネルが要求ページを介して容易に処理できます.
shmat()関数を呼び出して、共有メモリ領域をプロセスに「添付」します.呼び出しプロセスは、このメモリ領域の開始線形アドレスを取得することができるが、このアドレスは通常重要ではなく、この共有メモリ領域にアクセスする各プロセスは、自分のアドレス空間の異なるアドレスを使用することができる.
各IPC共有メモリ領域はshm特殊ファイルシステムに属する一般的なファイルに関連付けられている.
shmファイルシステムは、システムディレクトリツリーにインストールポイントがないため、通常のVFSシステム呼び出しで開いてアクセスすることはできません.ただし、プロセスがメモリセグメントを追加すると、カーネルはdo_を呼び出します.mmap()は、プロセスのアドレス空間にファイルの新しい共有メモリマッピングを作成します.