LinuxC高度なプログラミング-プロセス


LinuxC高度なプログラミング-プロセス
目的:技術の学習は有限で、分かち合う精神の無限です.
各プロセスは、カーネル内にプロセス関連情報を維持するプロセス制御ブロック(PCB)を有し、Linuxカーネルのプロセス制御ブロックはtask_struct構造体である.PCBには、次の情報が含まれる.
(1)プロセスid.システム内の各プロセスには一意のidがあり,C言語ではpid_tタイプで表されるが,実は非負の整数である.
(2)プロセスの状態は,実行,保留,停止,ゾンビなどの状態である.
(3)プロセス切替時に保存・復元する必要があるCPUレジスタの一部.仮想アドレス空間の情報を記述する.
(4)制御端末の情報を記述する.
(5)現在の作業ディレクトリ(Current Working Directory).(6)umaskマスク.
(7)file構造体へのポインタを多く含むファイル記述子テーブル.
(8)信号に関する情報.ユーザidとグループid.
(9)制御端末,セッション,プロセスグループ.
(10)プロセスで使用可能なリソース上限(ResourceLimit)
一、環境変数
libcで定義されたグローバル変数environは環境変数テーブルを指し、environはヘッダファイルに含まれていないので、使用時にexternで宣言します.
#include<stdio.h>

int main(void)
{
  extern char **environ;
  int i;

  for(i = 0; environ[i] != NULL; i++)
  {
    printf("%s
", environ[i]); } return 0; }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

重要な環境変数:
PATH:実行可能ファイルの検索パス.
SHELL:現在のSHELL、通常は/bin/bashです.
TERM:現在の端末タイプ.グラフィックインタフェースの下では通常xtermです.
LANG:言語とlocaleは、文字コードや時間、通貨などの情報の表示フォーマットを決定します.
HOME:現在のユーザーホームのパスです.多くのプログラムは、メインディレクトリの下にプロファイルを保存する必要があります.これにより、各ユーザーがプログラムを実行するときに独自の構成を持つようになります.
 
二、fork()システム呼び出し
forkの役割は、既存のプロセスに基づいて新しいプロセスをコピーすることです.元のプロセスは親プロセス(Parent Process)と呼ばれ、新しいプロセスは子プロセスと呼ばれます.(Child Process).システムでは同時に多くのプロセスが実行されている.これらのプロセスは、最初に1つのプロセスのみから1つずつコピーされている.Shellの下でコマンドを入力すると、Shellプロセスはユーザーが入力したコマンドを読み込んだ後、forkを呼び出して新しいShellプロセスをコピーし、新しいShellプロセスがexecを呼び出して新しいプロセスを実行するからであるシーケンス.
たとえば、Shellプロンプトでコマンドlsを入力すると、まずforkがサブプロセスを作成し、親プロセスが/bin/bashプログラムを実行し、サブプロセスがexecを呼び出して新しいプログラム/bin/lsを実行します.
サブプロセスと親プロセスのプロセスIDが異なる場合を除き、他のリソースは同じです.
-サブプロセスの作成
(1)関数プロトタイプ:
#include<sys/types.h>
#include <unistd.h>
pid_t fork(void);

(2)パラメータ——なし
(3)戻り値
fork呼び出しに失敗すると-1を返し、プロセスpidを返し、pidが0より大きい:親プロセス;pidは0に等しい:サブプロセス.実行順序が不定で、カーネルのスケジューリングアルゴリズムに基づいています.
特徴:1回呼び出し、2回返します.
setfollow-fork-mode childコマンドgdbがforkの後にサブプロセスを追跡するように設定し(set follow-fork-mode parentは親プロセスを追跡する)、runコマンドを使用すると、親プロセスが常に実行されていることがわかります.
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
  pid_t pid;
  char *message;
  int n;

  pid = fork();
  if (pid < 0)
  {
    perror("fork failed");
    exit(1);
  }
  if (pid == 0)
  {
    message = "This is the child
"; n = 6; } else { message = "This is the parent
"; n = 3; } for(; n > 0; n--) { printf(message); sleep(1); } return 0; }

三、exec関数ファミリー
fork()でサブプロセスを作成した後に実行されるのは親プロセスのようなプログラムで、サブプロセスは往々にしてexec関数を呼び出して別のプログラムを実行します.execは新しいプロセスを作成しないので、プロセスidは変更されません.
1、exec関数ファミリーの詳細
(1)、関数プロトタイプ
#include<unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

(2)、パラメータ——可変パラメータ
(3)、戻り値
これらの関数は、呼び出しが成功した場合、新しいプログラムをロードして起動コードから実行し、返さない.呼び出しエラーが発生した場合、-1を返すので、exec関数はエラーの戻り値のみで、正常な戻り値はありません. 
2、これらの関数のルールを記憶する
(1)アルファベットp(pathを表す)を持たないexec関数の最初のパラメータは、プログラムの相対パスまたは絶対パスでなければならない.
(2)アルファベットp付き関数:パラメータに/が含まれている場合はパス名とします.そうでない場合はパスなしのプログラム名として、PATH環境変数のディレクトリリストでこのプログラムを検索します.
(3)アルファベットl(listを表す)を持つexec関数は,新しいプログラムの各コマンドラインパラメータを1つのパラメータとして渡すことを要求し,コマンドラインパラメータの個数は可変であるため,関数プロトタイプには...,...の最後の可変パラメータはNULLでありsentinelの役割を果たすべきである.
(4)アルファベットv(vectorを表す)を持つ関数については、main関数のargvパラメータや環境変数テーブルのように、各パラメータを指すポインタ配列を構築してから、その配列のヘッダアドレスをパラメータとして渡す必要があります.
(5)e(environmentを表す)で終わるexec関数については、新しい環境変数テーブルを渡すことができ、他のexec関数は現在の環境変数テーブルを使用して新しいプログラムを実行します.
execveのみが真のシステム呼び出しであり、他の5つの関数は最終的にexecveを呼び出す.manで検証できます.man 2 execve、残りはman 3です.
char*const ps_argv[] = {"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};
char*const ps_envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execv("/bin/ps", ps_argv);
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);
execve("/bin/ps", ps_argv, ps_envp);
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execvp("ps", ps_argv);<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

四、waitとwaitpid関数
ゾンビ(Zombie)プロセス:プロセスは終了しましたが、親プロセスはwaitまたはwaitpidを呼び出してクリーンアップしていません.この場合、プロセスステータスはゾンビプロセスと呼ばれます.
(1)関数プロトタイプ
#include<sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

(2)パラメータ
(3)戻り値
呼び出しが成功すると、クリーンアップされたサブプロセスidが返され、呼び出しエラーが発生すると-1が返されます.親プロセスがwaitまたはwaitpidを呼び出すと、ブロックされる可能性があります(すべてのサブプロセスが実行されている場合).サブプロセスの終了情報はすぐに返されます(サブプロセスが終了した場合、親プロセスが終了情報を読み取るのを待っています).エラーはすぐに返されます(サブプロセスがない場合).
違い:親プロセスのすべてのサブプロセスが実行されている場合、waitを呼び出すと親プロセスがブロックされ、waitpidを呼び出すとoptionsパラメータにWNOHANGを指定すると、親プロセスがブロックされずにすぐに0に戻ることができます.waitは最初の終了したサブプロセスを待機し、waitpidはpidパラメータを使用してどのサブプロセスを待機するかを指定できます.
呼び出し:pid=wait(NULL);//成功するとwaitは収集されたサブプロセスのプロセスIDを返し、呼び出しプロセスにサブプロセスがない場合は呼び出しに失敗します.waitは-1を返し、errnoはECHILDに設定されます. 
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
  pid_t pid;
  pid = fork();
  if (pid < 0)
  {
    perror("fork failed");
    exit(1);
  }
  if (pid == 0)
  {
    int i;
    for (i = 3; i > 0; i--)
    {
      printf("This is the child
"); sleep(1); } exit(3); } else { int stat_val; waitpid(pid, &stat_val, 0); if (WIFEXITED(stat_val)) { printf("Child exited with code%d
", WEXITSTATUS(stat_val)); } else if (WIFSIGNALED(stat_val)) { printf("Child terminated abnormally,signal %d
", WTERMSIG(stat_val)); } } return 0; }