IO およびプロセス スレッド

I/Oプロセス

scanf\printf: ターミナル

IO: 入出力、ファイル

標準I/O

ファイルI/O

ファイル属性取得:ls -l ファイルタイプ ファイルパーミッション リンク番号 ユーザー名 グループ名 サイズ 時間 ファイル名

ディレクトリ操作: ls

図書館

プロセス

プロセス: プロセスを作成する

スレッド: スレッド、同期、およびミューテックスの作成

プロセス間通信:7→6種類

1. 標準I/O

ファイル: 7 ファイルタイプ

b(ブロックデバイス) c(キャラクタデバイス) d(ディレクトリ) -(通常ファイル) l(リンクファイル) s(ソケット) p(名前付きパイプ)

1.コンセプト

1.1 定義

入出力専用の C ライブラリで定義された関数のセット

1.2 特徴

1) バッファ機構があるため、システムコールの数が減り、効率が向上します。

システムコール: カーネルによって提供される一連のインターフェイス

2) ストリームを中心に操作します。ストリームは FILE * で記述され、FILE はファイルの関連情報を記述する構造です。

typedef struct _IO_FILE ファイル;

3) デフォルトでは、stdin (標準入力)、stdout (標準出力)、stderr (標準エラー) の 3 つのストリームが開かれます。

struct _IO_FILE *標準入力; --> ファイル *標準入力;

補足: ctags の使用 (コードに従うことができます)

vi -t FILE (typedef はデータ型、マクロ定義、構造体などを定義します)

適切な番号を選択してください

カーソルをターゲット位置に置きます。ctrl+]: コードを下に追跡します。

Ctrl+T: 戻る

Q:辞める

1.3 バッファー

1) フルキャッシュ: ファイル関連

キャッシュをリフレッシュする条件:

1 - プログラムは正常に終了します

2 - キャッシュのフルリフレッシュ

3-flush 強制リフレッシュ

2) ラインキャッシュ: 端末関連

キャッシュをリフレッシュする条件:

1 - プログラムは正常に終了します

2-\nキャッシュを更新します

3- キャッシュのフルリフレッシュ

4-fflush 強制リフレッシュ

3) キャッシュなし: キャッシュなし、標準エラー

演習: ラインバッファ内の標準出力のバッファサイズを計算します。

#include <stdio.h>

int main(int argc, char const *argv[])
{
    // for(int i = 0; i < 300; i++)
    //     printf("%4d", i);
    // while(1);

    // 结构体,stdout _IO_buf_end
    printf("hello");
    printf("%d\n", stdout->_IO_buf_end - stdout->_IO_buf_base);
    
    return 0;
}

2. 機能インターフェース

2.1 ファイルを開く

FILE *fopen(const char *path, const char *mode);
参数:
    path:打开文件
    mode:打开方式
        r:只读,流被定位到文件开头
        r+:可读可写,流被定位到文件开头
        w:只写,文件不存在创建,文件存在清空,流被定位到文件开头
        w+:可读可写,文件不存在创建,文件存在清空,流被定位到文件开头
        a:追加,文件不存在创建,存在追加,流被定位到文件末尾
        a+:可读可写,文件不存在创建,存在追加,开始进行读从头读,进行写流被定位到文件末尾
返回值:成功:文件流
       失败:NULL,并且设置errno(错误码)

2.2 ファイルの読み取りと書き込み

2.2.1 一度に 1 文字ずつ読み書きする

int fgetc(FILE *stream);
功能:从文件中读一个字符
参数:stream:文件流
返回值:成功:读到字符的ASCII
       失败或读到文件末尾:EOF
int fputc(int c, FILE * stream)
功能:向文件中写入一个字符
参数:c:要写的字符
   stream:文件流
返回值:成功:写的字符的ASCII
      失败:EOF
#include <stdio.h>

int main(int argc, const char *argv[])
{
    FILE *fp;
    fp=fopen("./i2.c","w");
    if(fp==NULL)
    {
        perror("fopen err");
        return -1;
    }
    fputc('a',fp);    //存放字符到指定文件中
    fputc(32,fp);
    fputc('a',stdout);  //向终端(标准输出)一个函数
    return 0;                                         
}
                                                      
                                                      
                                                      

 演習:猫の機能を実現するプログラム  

アイデア: ファイルを開き、ループ (fgetc) でファイルを読み取り、ファイルの終わりが読み取られると (fgetc 関数の戻り値が EOF)、ループが終了し、読み取られた内容が出力されます。

#include <stdio.h>

int main(int argc, const char *argv[])
{
FILE *p;    //定义结构体指针
int ch;
if (argc!=2)   //传入的参数不等于2个的时候进行提示
{
printf("usage:%s <filename>\n",argv[0]);
return -1;
}
p=fopen(argv[1],"r");   //打开文件
if (p==NULL)
{
perror("open err");
//printf("open error\n");
return -1;
}

while((ch=fgetc(p))!=EOF)   //循环打印出文件的全部内容

{
printf("%c",ch);
}
    return 0;                                      
}
                                                   
                                                   

補充:

int  feof(FILE * stream);
功能:判断文件有没有到结尾
返回:到达文件末尾,返回非零值
int ferror(FILE * stream);
功能:检测文件有没有出错
返回:文件出错,返回非零值
void perror(const char *s);
功能:根据errno打印错误信息
参数:s:要打印的字符串
#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE *fp;
    fp = fopen("./a.c", "w");
    if(fp == NULL)
    {
        // printf("fopen err\n");
        perror("fopen err"); 
        return -1;
    }
    printf("fopen success\n");

    int ch = fgetc(fp);
    printf("%c\n", ch);
    //fgetc返回值为EOF时,是因为读到末尾还是因为调用失败,可以用这两个函数区分
    if(feof(fp))   //判断是否读到文件末尾
        printf("eof\n");
    if(ferror(fp))  //判断函数是否调用失败
        printf("error\n");
    return 0;
}

vscode の使用を補足します。

  1. 作業ディレクトリ内の .vscode フォルダーを自分のディレクトリ (ファイル) にコピーします。
  2. ファイルの上位ディレクトリに切り替えます
  3. コードファイルを作成し、vscode でディレクトリを開き、コードを記述します。
  4. ショートカット:

1) Ctrl+Shift+I: コードの自動配置

2) Ctrl+/: コメントコード

3) コード追跡:

Ctrl + マウスの左ボタン: 追いかける

alt+キーボード左キー:戻る

2.2.2 一度に 1 行ずつ読み書きする

char *fgets(char *s, int size, FILE *stream);
功能:从文件中读取一串字符
参数:s:存放读取的字符串的首地址
     size:读取的大小
     stream:文件流
返回值:成功:读取的字符串的首地址
      失败或读到文件末尾:NULL
特性:1.实际读取size-1个字符,在末尾添加\0
     2.读到\n结束读取
int  fputs(const char *s,  FILE *stream);
功能:向文件中写字符串
参数:s:要写的内容
    stream:文件流
返回值:成功:非负整数
       失败:EOF

 演習: ファイルの行数を計算する機能を実現するプログラム (wc -l ファイル名)。

要件: fgets を使用して実装

アイデア: ファイルを開き、ループでファイルを読み取ります。ファイルの末尾が読み取られると (fgets の戻り値が NULL になると)、ループは終了します。ループ内で、文字列に \n があるかどうかを判断します。\n がある場合は、 \n、変数 n++ を使用できます

#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{

    FILE *fp;
    int n=0;
    char buf[32]="";

    fp=fopen("./i2.c","r");
    if(fp==NULL)
    {
        perror("fopen err");
        return -1;                                      
    }

    while(fgets(buf,30,fp)!=NULL)
    {
#if 0
            for(int i=0;buf[i]!='\0';i++)
            {
                if(buf[i]=='\n')
                n++;
            }
#endif
      if(buf[strlen(buf)-1]=='\n')
          n++;

    }
    printf("%d\n",n);
    return 0;
}
                                                        
                                                        

2.3 ファイルを閉じる

int fclose(FILE* stream);
功能:关闭文件
参数:stream:文件流

演習 1:ファイル test.txt を読み書きし、1 秒ごとに 1 行のデータをファイルに書き込むプログラム

このようなもの:

1、2007-7-30 15:16:42  

2、2007-7-30 15:16:43

Ctrl-C を押してプログラムを中断するまで、プログラムは無限にループする必要があります。

ファイルを書き込むためにプログラムを再度開始する場合、そのファイルを元のファイルに追加することができ、シリアル番号は以前のシリアル番号を継続することができます。次に例を示します。

1、2007-7-30 15:16:42

2、2007-7-30 15:16:43

3、2007-7-30 15:19:02

4、2007-7-30 15:19:03

5、2007-7-30 15:19:04

スリープ(1);

fprintf/sprintf();

time(); //計算時間、秒

localtime(); // 秒を年、月、日、時、分、秒に変換します

アイデア: ファイルを開き、行数を数え、ループでファイルに文字列を書き込み、毎秒 1 行書き込みます。

注: フルキャッシュ

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    FILE *fp;
    char buf[32] = "";
    int n = 0;
    fp = fopen("test.txt", "a+");
    if(fp == NULL)
    {
        perror("fopen err");
        return -1;
    }
    //判断行数
    while(fgets(buf, 32, fp) != NULL)
    {
        if(buf[strlen(buf)-1] == '\n')
            n++;
    }
    time_t tm;
    struct tm *t;
    while(1)
    {
        //计算时间
        // time(&tm);
        tm = time(NULL);
        t = localtime(&tm);
        fprintf(fp, "%d,%d-%d-%d %d-%d-%d\n", ++n,t->tm_year+1900,t->tm_mon+1,\
        t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
        fflush(NULL);
        sleep(1);
    }
    return 0;
}

演習 2 : 頭部の機能を実現する

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

int main(int argc, char const *argv[])
{
    FILE *fp;
    char buf[32] = "";
    int n = 0;
    int num = atoi(argv[1]+1); //argv[1]:"-15"
    fp = fopen(argv[2], "r");
    if(fp == NULL)
    {
        perror("fopen err");
        return -1;
    }
    while(fgets(buf, 32, fp) != NULL)
    {
        if(buf[strlen(buf)-1] == '\n')
            n++;
        printf("%s", buf);
        if(n == num)
            break;
    }
    return 0;
}

2.4 バイナリの読み取りと書き込み

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从文件流读取多个元素
参数:	ptr :用来存放读取元素
        size :元素大小  sizeof(数据类型)
		nmemb :读取对象的个数
		stream :要读取的文件
返回值:成功:读取对象的个数
      读到文件尾或失败:0
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:按对象写
参数:同上	
返回值:成功:写的元素个数
      失败 :-1
Fread和fwrite函数注意:
1)两个函数的返回值为:读或写的对象数
2)对于二进制数据我们更愿意一次读或写整个结构。
#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE *fp;
    int arr[3] = {10, 20, 30}, num[3] = {0};

    fp = fopen("a.c", "w+");
    if(fp == NULL)
    {
        perror("fopen err");
        return -1;
    }
    fwrite(arr, sizeof(int), 3, fp);
    //将文件位置移动到文件开头
    rewind(fp);
    fread(num, sizeof(int), 3, fp);
    for(int i = 0; i < 3; i++)
        printf("%d\n", num[i]);

    return 0;
}

2.5 ファイルの場所の操作

void rewind(FILE *stream);
功能:将文件位置指针定位到起始位置
int fseek(FILE *stream, long offset, int whence);
功能:文件的定位操作
参数:stream:文件流
     offset:偏移量:正数表示向后文件尾部偏移,负数表示向文件开头偏移
     whence:相对位置:
           SEEK_SET:相对于文件开头
           SEEK_CUR:相对于文件当前位置
           SEEK_END:相对于文件末尾
返回值:成功:0
        失败:-1   
注:当打开文件的方式为a或a+时,fseek不起作用              
long ftell(FILE *stream);
功能:获取当前的文件位置
参数:要检测的文件流
返回值:成功:当前的文件位置,出错:-1
#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE *fp;
    char buf[32] = "";
    int n = 0;
    fp = fopen("test.txt", "r+");
    if(fp == NULL)
    {
        perror("fopen err");
        return -1;
    }
    //将文件位置进行定位操作
    fseek(fp, 10, SEEK_SET); //相对文件开头向后偏移
    fputc('a', fp);

    fseek(fp, -5, SEEK_CUR); //相对文件当前向前偏移
    fputc('b', fp);

    long l = ftell(fp); //获取当前文件位置
    printf("%ld\n", l);

    //计算文件长度
    // fseek(fp, 0, SEEK_END);
    // l = ftell(fp);
    
    //rewind和fseek等价
    // rewind(fp); //fseek(fp, 0, SEEK_SET);

    return 0;
}

2.6 ファイルを開くためのリダイレクト

FILE * freopen(const char *pathname,  const char *mode,  FILE* fp)
功能:将指定的文件流重定向到打开的文件中
参数:path:文件路径
mode:打开文件的方式(同fopen)
      fp:文件流指针
返回值:成功:返回文件流指针
      失败:NULL
#include <stdio.h>

int main(int argc, char const *argv[])
{
    printf("hello\n");
    //将标准输出重定向到打开的文件
    freopen("test.txt", "r+", stdout);
    printf("world\n");
    //将标准输出重定向到终端
    freopen("/dev/tty", "r+", stdout);
    printf("nihao\n");

    return 0;
}

演習: 標準 IO を使用して cp 関数を実現する

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
    FILE *fd;
    FILE *fd1;
    char ch;
    fd=fopen(argv[1],"r");
    fd1=fopen(argv[2],"w+");
    if(fd==NULL)
    {
        perror("open eror\n");
        return -1;

    }

    if(fd1==NULL)
    {
        perror("open eror\n");
        return -1;

    }

    while((ch=fgetc(fd))!=EOF)
        fputc(ch,fd1);

    fclose(fd);
    fclose(fd1);
    return 0;
}                                               
                                                

Baidu+man マニュアル

時間(time_t *tm)

関数呼び出し:

パラメータ: 関数プロトタイプの番号、型、意味が対応します; 関数プロトタイプのパラメータが第 1 レベルのポインタの場合、アドレスを渡す変数を定義する必要があります

戻り値: すべての関数が戻り値を受け取る必要があるわけではありません。戻り値を受け取る必要がある場合は、関数プロトタイプの戻り値の型と、受け取るコード内でどのような型の変数またはポインターが定義されているかが必要です。

2. ファイルIO

1.コンセプト

 1.1 定義

posix (ポータブル オペレーティング システム インターフェイス) で定義された一連の入出力関数

システムコール: カーネルによって提供される一連のインターフェイス

 1.2 特徴

1) バッファメカニズムがないため、各 IO 操作によりシステムコールが発生します。

2)ファイル記述子、非負の整数 (int)を操作し、順番に割り当てます。

3) デフォルトでは 3 つのファイル記述子が開かれます: 0 (標準入力)、1 (標準出力)、2 (標準エラー)

4) d を除くあらゆる種類のファイルを操作できます。  

2. 機能インターフェース

2.1 ファイルを開く

int open(const char *pathname, int flags);
功能:打开文件
参数:pathname:文件路径名
      flags:打开文件的方式
            O_RDONLY:只读
            O_WRONLY:只写
            O_RDWR:可读可写
            O_CREAT:创建
            O_TRUNC:清空
            O_APPEND:追加   
返回值:成功:文件描述符
        失败:-1
当第二个参数中有O_CREAT选项时,需要给open函数传递第三个参数,指定创建文件的权限 
int open(const char *pathname, int flags, mode_t mode);
创建出来的文件权限为指定权限值&(~umask)  //umask为文件权限掩码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    int fd;
    char buf[32] = "";

    // fd = open("./a.txt", O_RDWR|O_CREAT|O_TRUNC, 0666);
    fd = open("test.txt", O_RDWR);
    if (fd < 0)
    {
        perror("open err");
        return -1;
    }
    printf("fd:%d\n", fd);
    //read返回值s表示实际读到的字符个数
    ssize_t s = read(fd, buf, 32);
    printf("%s\n", buf);
    printf("%d\n", s);
    
    write(fd, "nihao", 5);

    close(fd);

    return 0;
}

比較:ファイルの開き方における標準IOとファイルIOの対応関係

標準I/O

ファイルI/O

r

O_RDONLY

r+

O_RDWR

w

O_WRONLY|O_CREAT|O_TRUNC,0666

w+

O_RDWR|O_CREAT|O_TRUNC,0666

ある

O_WRONLY|O_CREAT|O_APPEND,0666

α+

O_RDWR|O_CREAT|O_APPEND,0666

2.2 ファイルの読み取りと書き込み

ssize_t read(int fd, void *buf, size_t count);
功能:从一个已打开的可读文件中读取数据
参数:fd  文件描述符
     buf  存放位置
    count  期望的个数
返回值:成功:实际读到的个数
      返回-1:表示出错,并设置errno号
      返回0:表示读到文件结尾
ssize_t write(int fd, const void *buf, size_t count);
功能:向指定文件描述符中,写入 count个字节的数据。
参数:fd   文件描述符
          buf   要写的内容
          count  期望值
返回值:成功:实际写入数据的个数
              失败  : -1

演習: cp 関数を実現します。

cp srcfile newfile -> ./a.out srcfile newfile

アイデア: 2 つのファイルを開き、ループ内でソース ファイルを読み取り、新しいファイルを書き込み、ソース ファイルの終わりが読み取られたときにループを終了します。

diff filename1 filename2: 2 つのファイルが等しいかどうかを比較します。

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

int main(int argc, char const *argv[])
{
    int fd_src, fd_new;
    char buf[32] = "";
    ssize_t s;
    fd_src = open(argv[1], O_RDONLY);
    fd_new = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0666);
    if(fd_src < 0 || fd_new < 0)
    {
        perror("open err");
        return -1;
    }
    while(1)
    {
        s = read(fd_src, buf, 32);
        if(s == 0)
            break;
        write(fd_new, buf, s);
    }
    close(fd_src);
    close(fd_new);
    return 0;
}

2.3 ファイルを閉じる

int close(int fd);
参数:fd:文件描述符

2.4 ファイルの場所

off_t lseek(int fd, off_t offset, int whence);
功能:设定文件的偏移位置
参数:fd:文件描述符
    offset偏移量  
        正数:向文件结尾位置移动
        负数:向文件开始位置
    whence  相对位置
        SEEK_SET   开始位置
        SEEK_CUR   当前位置
        SEEK_END   结尾位置
返回值:成功:文件的当前位置
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    int fd;
    char buf[32] = "";

    fd = open("test.txt", O_RDWR);
    if (fd < 0)
    {
        perror("open err");
        return -1;
    }
    printf("fd:%d\n", fd);
    
    lseek(fd, 10, SEEK_SET);
    write(fd, "a", 1);

    off_t off = lseek(fd, 0, SEEK_CUR);
    printf("%ld\n", off);

    close(fd);

    return 0;
}

演習 2: 次の機能を実現します

   1 -- ファイルを開き、ファイルが存在しない場合は作成し、存在する場合はクリアします。 

    2 -- ファイルの位置 10 に文字を書き込みます。

    3-- 現時点でのファイルの位置の最後の 20 位置に、文字列 hello の行を書き込みます。

    4 -- ファイルの長さを調べます。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
char buf[32]="";
fd=open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open eror\n");
return -1;

}

lseek(fd,9,SEEK_SET);
write(fd,"i",1);

lseek(fd,19,SEEK_CUR);
write(fd,"hello",5);

off_t off=lseek(fd,0,SEEK_END);
printf("%ld\n",off);

close(fd);

    return 0;
}                                                 
                                                  
                                                  

2.5 標準IOとファイルIOの比較

標準I/O

ファイルI/O

意味

C ライブラリの入力と出力を定義する関数

posix で定義された入出力関数

特徴

バッファ機構がある

ストリーム周りの操作、FILE*

デフォルトでは、stdin/stdout/stderr の 3 つのストリームが開かれます。

通常のファイルしか操作できない

バッファ機構がない

ファイル記述子を中心に操作します (int 非負の整数)

デフォルトでは 3 つのファイル記述子が開かれます: 0/1/2

d を除く任意のタイプのファイル

機能インターフェイス

ファイルを開く: fopen/freopen

ファイルの読み取りおよび書き込み: fgetc/fputc、fgets/fputs、fread/fwrite

ファイルを閉じる: fclose

ファイルの配置: 巻き戻し、fseek、ftell

ファイルを開く: 開く

ファイルの読み取りと書き込み: 読み取り、書き込み

ファイルを閉じます: close

ファイルの配置: lseek

2.6 ファイル属性の取得

int stat(const char *path, struct stat *buf);
功能:获取文件属性
参数:path:文件路径名
       buf:保存文件属性信息的结构体
返回值:成功:0
      失败:-1
struct stat {
        ino_t     st_ino;     /* inode号 */
        mode_t    st_mode;    /* 文件类型和权限 */
        nlink_t   st_nlink;   /* 硬链接数 */
        uid_t     st_uid;     /* 用户ID */
        gid_t     st_gid;     /* 组ID */
        off_t     st_size;    /* 大小 */
        time_t    st_atime;   /* 最后访问时间 */
        time_t    st_mtime;   /* 最后修改时间 */
        time_t    st_ctime;  /* 最后状态改变时间 */
    };
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    struct stat st;
    if (stat("./test.c", &st) < 0)
    {
        perror("stat err");
        return -1;
    }
    printf("%lu\n", st.st_ino);
    printf("%d\n", st.st_nlink);
    //判断文件类型
    printf("%#o\n", st.st_mode);
    if((st.st_mode & S_IFMT) == S_IFREG)
        putchar('-');
    else if((st.st_mode & S_IFMT) == S_IFDIR)
        putchar('d');
    //判断文件权限
    if(st.st_mode & S_IRUSR)
        putchar('r');
    else 
        putchar('-');
     if(st.st_mode & S_IWUSR)
        putchar('w');
    else 
        putchar('-');
    //获取用户名和组名
    //getpwuid();//将用户ID转换成用户名
    //getgrgid();//将组ID转换成组名

    //时间
    //st_mtime:最后一次修改时间,s
    //localtime();

    return 0;
}

2.7 ディレクトリ操作

ディレクトリストリーム、DIR * を中心に動作します。

DIR *opendir(const char *name);
功能:获得目录流
参数:要打开的目录
返回值:成功:目录流
       失败:NULL
struct dirent *readdir(DIR *dirp);
功能:读目录
参数:要读的目录流
返回值:成功:读到的信息    
      失败或读到目录结尾:NULL
返回值为结构体,该结构体成员为描述该目录下的文件信息
struct dirent {
        ino_t   d_ino;                   /* 索引节点号*/
        off_t   d_off;               /*在目录文件中的偏移*/
        unsigned short d_reclen;    /* 文件名长度*/
        unsigned char  d_type;      /* 文件类型 */
        char    d_name[256];      /* 文件名 */
};
int closedir(DIR *dirp);
功能:关闭目录
参数:dirp:目录流
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, char const *argv[])
{
    DIR *dir;
    struct dirent *d;

    dir = opendir(".");
    if (dir == NULL)
    {
        perror("opendir err");
        return -1;
    }
    while((d = readdir(dir)) != NULL)
    {
        if(d->d_name[0] != '.') 
            printf("%s\n", d->d_name);
    }
    // d = readdir(dir);
    // printf("%s\n", d->d_name);
    // d = readdir(dir);
    // printf("%s\n", d->d_name);

    closedir(dir);

    return 0;
}

演習:ls関数を実現するプログラム

3、図書館

1. ライブラリの定義

他人の関数を使用する場合は、ヘッダファイルをインクルードするだけでなく、ライブラリが必要です

  ライブラリ: 一般的に使用されるいくつかの関数のオブジェクト ファイルをパッケージ化し、プログラマーにとって使いやすい対応する関数のインターフェイスを提供するものです。本質的に、ライブラリは実行可能コードのバイナリ形式です。

Windows と Linux の性質が異なるため、2 つのライブラリのバイナリには互換性がありません。

2. 図書館の分類

静的ライブラリと動的ライブラリの本質的な違いは、コードが異なる時点でロードされることです。

1) 静的ライブラリは、プログラムのコンパイル時にオブジェクト コードにリンクされます。

利点: プログラムの実行時に静的ライブラリが必要なくなり、実行時にライブラリをロードする必要がなくなり、実行速度が速くなります。

欠点: 静的ライブラリのコードがプログラムにコピーされるため、ボリュームが大きくなります。

   静的ライブラリをアップグレードした後、プログラムを再コンパイルしてリンクする必要があります

2) プログラムの実行中に、ダイナミック ライブラリがコードにロードされます。

            利点: プログラムは実行中に動的ライブラリをロードし、コード サイズが小さくなります。

 プログラムのアップグレードが簡単になります。

異なるアプリケーションが同じライブラリを呼び出す場合、メモリ内に共有ライブラリのインスタンスが存在するだけで済みます。

欠点: 実行時に動的ライブラリの存在も必要となり、移植性が低い

3. ライブラリの作成

3.1 スタティックライブラリの作成

1- ソース ファイルをコンパイルしてターゲット ファイルを生成する

gcc -c add.c -o add.o

2- ar コマンドを使用して静的ライブラリを作成します。これにより、多くの .o が .a に変換されます。

ar crs libmyadd.a add.o 

静的ライブラリ ファイル名の命名規則は、先頭に lib が付き、その後に静的ライブラリ名が続き、拡張子は .a になります。

3- 静的ライブラリを使用してテストします。

gcc main.c -L. -lmyadd // -L はライブラリのパスを指定します -l はライブラリ名を指定します

./a.outを実行

3.2 ダイナミックライブラリの作成

1- gcc を使用して共有ライブラリを作成します

gcc -fPIC -c hello.c -o hello.o

-fPIC アドレス非依存のコンパイラを作成します。

gcc -shared -o libmyhello.so hello.o

2- 動的ライブラリの使用状況をテストする

gcc main.c -L. -lmhello

正常にコンパイルできますが、実行時にエラーが報告されます。/a.out: 共有ライブラリのロード中にエラーが発生しました: libmyadd.so: 共有オブジェクト ファイルを開けません: そのようなファイルまたはディレクトリはありません

理由: ダイナミック ライブラリをロードするとき、システムはデフォルトで /lib または /usr/lib からライブラリ ファイルを検索します。

回避策 (3 つあります):

(1) ライブラリを /usr/lib および /lib ディレクトリにコピーします(このメソッドはコンパイル時にライブラリのパスを指定する必要はありません) 

(2) 環境変数LD_LIBRARY_PATHにライブラリのパスを追加します。 

エクスポート LD_LIBRARY_PATH=$LD_LIBRARY_PATH:。 

(ターミナルを閉じると環境変数は消えます)

(3) /etc/ld.so.conf.d/*.conf ファイルを追加します。ライブラリが配置されているパスをファイルの末尾に追加し、ldconfigfresh を実行します。

sudo vi xx.conf

次のように、ダイナミック ライブラリが存在するパスを追加します。

/home/hq/teach/22092/day3/dynamic

補充:

ヘッド ファイル:

それを現在のディレクトリに置きます: #include "xx.h"、現在のパスからファイルを見つけます。そうでない場合は、システム ディレクトリから検索します。

システム ディレクトリ: #include に置きます。デフォルトではシステム パスから検索します。システム パス: /usr/include

別のディレクトリに置きます: #include "xx.h"、オプション -I (大文字の i) を追加して、gcc でコードをコンパイルするときにヘッダー ファイルのパスを指定します。

gcc main.c -I ヘッダー ファイルのパス

ライブラリ ファイル: 動的ライブラリはシステム ディレクトリに配置されます。

システム パス: /usr/lib および /lib

gcc はコンパイル時にオプションを追加する必要があります

-L path: ライブラリのパスを指定します。

-l ライブラリ名: (小文字の L) ライブラリ名を指定します。

-I path: (大文字の i) ヘッダー ファイルへのパスを指定します。

4. プロセス

1.コンセプト:

1.1 プログラムとプロセスの違い:

プログラム: コンパイルされた実行可能ファイル

ディスク (ファイル) に保存された命令とデータの順序付けされたコレクション

プログラムは静的であり、実行の概念はありません

プロセス: 独立したスケジュール可能なタスク

プログラムを実行するために割り当てられるリソースの総称

プロセスとはプログラムの実行です

プロセスは、作成、スケジュール設定、実行、終了などを含めて動的です。

1.2 特徴

  1. システムは各プロセスに 0 ~ 4g の仮想スペースを割り当てます。そのうち 0 ~ 3g は各プロセスに固有のユーザースペース、3g ~ 4g はすべてのプロセスで共有されるカーネルスペースです。
  2. ラウンドロビンスケジューリング:タイムスライス。システムはプロセスごとにタイムスライス(数ミリ秒から数十ミリ秒)を割り当て、プロセスのタイムスライスが使い果たされると、CPUが別のプロセスをスケジュールし、それによってプロセススケジューリングの切り替えを実現します。

1.3. プロセスセグメント:

Linux のプロセスは 3 つのセグメントで構成されます。

「データセグメント」には、グローバル変数、定数、動的データによって割り当てられたデータ空間(malloc関数で取得した空間など)などが格納されます。

プログラム内のコードを格納する「テキストセグメント」

      「スタックセグメント」には、関数の戻りアドレス、関数のパラメータ、プログラム内のローカル変数が格納されます。

1.4. プロセスの分類:

対話型プロセス: このタイプのプロセスはシェルによって制御され、実行されます。対話型プロセスはフォアグラウンドでもバックグラウンドでも実行できます。このタイプのプロセスは、ユーザーと対話することが多く、ユーザー入力を待つ必要があります。ユーザー入力を受け取ると、このタイプのプロセスはすぐに応答します。典型的な対話型プロセスには、シェル コマンド プロセス、テキスト エディタなどが含まれます。

バッチプロセス: このタイプのプロセスは特定の端末に属さず、順番に実行するためにキューに送信されます。

デーモン プロセス: このタイプのプロセスはバックグラウンドで実行されます。通常、Linux の起動時に実行が開始され、システムのシャットダウン時に終了します。

1.5. プロセスのステータス:

1) 実行状態 (TASK_RUNNING): R

CPU によって実行中または準備完了の状態を指します。このようなプロセスを実行プロセスと呼びます。

2) スリープ状態 (待機状態):

割り込み可能なスリープ状態 (TASK_INTERRUPTIBLE) S: 待機状態のプロセス。プロセスが待機しているリソースが解放されると、プロセスは実行状態になります。

無中断スリープ状態 (TASK_UNINTERRUPTIBLE) D: この状態のプロセスは、wake_up() 関数でのみ起動できます。

3) 一時停止状態 (TASK_STOPPED): T

プロセスがシグナル SIGSTOP、SIGTSTP、SIGTTIN、または SIGTTOU を受信すると、サスペンド状態になります。SIGCONT シグナルを送信して、プロセスを実行可能な状態に移行させることができます。

4) 死亡状態: プロセスが終了します X

5) ゾンビ状態: Z プロセスが終了してもシステムリソースを占有している場合、ゾンビ状態の生成を避ける必要があります。

 < 優先度が高い

 N 低優先度

 セッショングループリーダー

 l マルチスレッド

 + フォアグラウンドプロセス

1.6. プロセス状態切り替え図

プロセスが作成されると、プロセスは準備完了状態になります。CPU がこのプロセスをスケジュールすると、実行状態になります。タイム スライスが使い果たされると、プロセスは準備完了状態になります。プロセスがいくつかの IO 操作を実行している場合 (ブロッキング操作)、IO 操作の完了後(ブロッキング終了)、ブロッキング状態に入り、CPU のスケジューリングを待って再び準備完了状態になり、プロセスの実行が終了すると終了状態に入ることができます。

2.機能:

2.1 プロセスの作成

pid_t fork(void);
功能:创建子进程
返回值:
    成功:在父进程中:返回子进程的进程号 >0
         在子进程中:返回值为0
    失败:-1并设置errno
#include <stdio.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    int num = 10;
    pid_t id;
    id = fork(); //创建子进程
    if(id < 0)
    {
        perror("fork err");
        return -1;
    }
    else if(id == 0)
    {
        //in the child
        printf("in the child\n");
    }
    else
    {
        // int s;
        // wait(&s); //回收子进程资源,阻塞函数
        // printf("%d\n", s);
        wait(NULL);
        //in the parent
        printf("in the parent\n");
        while(1);
    }
    // while(1);
    
    return 2;
}

特徴:

1) 子プロセスは、親プロセスのほぼすべてのコンテンツをコピーします。コード、データ、システム データ セグメント内の PC 値、スタック内のデータ、親プロセスで開かれたファイルなどが含まれますが、それらの PID と PPID は異なります。

2) 親プロセスと子プロセスは独立したアドレス空間を持っており、相互に影響を与えず、対応するプロセス内でグローバル変数や静的変数を変更しても、相互に影響を与えません。

3) 親プロセスが先に終了した場合、子プロセスは孤立プロセスとなり、init プロセスによって採用され、子プロセスはバックグラウンドプロセスになります。

4) 子プロセスが先に終了した場合、親プロセスが時間内にリサイクルされないと、子プロセスはゾンビプロセスになります(ゾンビプロセスの生成を避けるため)。

2.2 プロセスリソースを再利用する

pid_t wait(int *status);
功能:回收子进程资源,阻塞函数,等待子进程退出后结束阻塞
参数:status:子进程退出状态,不接受子进程状态设为NULL
返回值:成功:回收的子进程的进程号
        失败:-1
pid_t waitpid(pid_t pid, int *status, int options);
功能:回收子进程资源
参数:
    pid:>0     指定子进程进程号
         =-1   任意子进程
         =0    等待其组ID等于调用进程的组ID的任一子进程
         <-1   等待其组ID等于pid的绝对值的任一子进程
    status:子进程退出状态
    options:0:阻塞
        WNOHANG:非阻塞
返回值:正常:结束的子进程的进程号
      当使用选项WNOHANG且没有子进程结束时:0
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{
    pid_t id;
    id = fork(); //创建子进程
    if (id < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (id == 0)
    {
        sleep(1);
        //in the child
        printf("in the child\n");
    }
    else
    {
        //IO模型:阻塞IO\非阻塞IO
        // waitpid(-1, NULL, 0); //wait(NULL);
        //轮询(循环)
        while(1)
        {
            if(waitpid(id, NULL, WNOHANG)!=0) //WNOHANG:表示非阻塞
                break;
        }
        //in the parent
        printf("in the parent\n");
        while (1)
            ;
    }

    return 0;
}

2.3 終了処理

void exit(int status);
功能:结束进程,刷新缓存
void _exit(int status);
功能:结束进程,不刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
    通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束

終了と復帰の違い:

exit: サブ関数でもメイン関数でも、プロセスを終了できます。

return: サブ関数内にリターンがある場合、処理を終了せずに関数呼び出し位置に戻ります。

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

int fun()
{
    printf("in fun\n");
    exit(0);
    // return 0;
}
int main(int argc, char const *argv[])
{
    printf("hello\n");
    // fun();
    // printf("world\n");
    // return 0;
    // exit(0); //结束进程,刷新缓存
    // _exit(0); //结束进程,不刷新缓存区

    // execl("/bin/ls", "ls", "-l", NULL);

    // char *arg[] = {"ls", "-l", NULL};
    // execv("/bin/ls", arg);

    while(1);

    return 0;
}

2.4 プロセス番号の取得

pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    pid_t id;
    if ((id = fork()) < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (id == 0)
    {
        printf("in child, pid:%d ppid:%d\n", getpid(), getppid());
    }
    else
    {
        printf("in parent, pid:%d ppid:%d\n", id, getpid());
    }

    return 0;
}

exec 関数 - 理解

システム("ls -l");

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

int fun()
{
    printf("in fun\n");
    exit(0);
    // return 0;
}
int main(int argc, char const *argv[])
{
    printf("hello\n");
    // fun();
    // printf("world\n");
    // return 0;
    // exit(0); //结束进程,刷新缓存
    // _exit(0); //结束进程,不刷新缓存区

    // execl("/bin/ls", "ls", "-l", NULL);

    // char *arg[] = {"ls", "-l", NULL};
    // execv("/bin/ls", arg);

    while(1);

    return 0;
}

2.5 デーモン

1. 特徴:

デーモンプロセスはバックグラウンドプロセスであり、制御端末には依存しません。

ライフサイクルは比較的長く、実行時に始まり、システムのシャットダウン時に終了します。

これは、制御端末から離脱して定期的に実行されるプロセスです。

2. 手順:

1) 子プロセスを作成し、親プロセスが終了します

子プロセスを孤立プロセスにしてバックグラウンドプロセスにします; fork()

2) 子プロセスに新しいセッションを作成します。

子プロセスを端末から完全に分離するために、子プロセスをセッション グループのリーダーにします; setid()

3) プロセスの実行パスをルート ディレクトリに変更します。

理由 プロセスが実行されているパスをアンロードできません; chdir("/")

4) ファイル許可マスクをリセットします。

目的: ファイル作成時のプロセスの権限を増やし、柔軟性を向上させます; umask(0)

5) ファイル記述子を閉じます

不要なファイルを閉じる; close()

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

int main(int argc, char const *argv[])
{
    pid_t id;
    if ((id = fork()) < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (id == 0)
    {
        //在子进程中创建新会话
        setsid();
        //修改进程运行路径为根目录
        chdir("/");
        //修改文件权限掩码
        umask(0);
        //关闭文件描述符
        for (int i = 0; i < 2; i++)
            close(i);
        int fd;
        fd = open("/tmp/info.log", O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (fd < 0)
        {
            perror("open err");
            return -1;
        }
        while (1)
        {
            write(fd, "hello", 5);
            sleep(1);
        }
    }
    else
    {
        exit(0);
    }
    return 0;
}

5、スレッド

1.コンセプト

          システムのパフォーマンスを向上させるためにスレッドを導入する軽量のプロセスです。

           Linux では、スレッドを記述するために task_struct も使用します。

           スレッドとプロセスの両方が統合スケジューリングに参加します。

           同じプロセスで作成されたスレッドは、そのプロセスのアドレス空間を共有します。

2. スレッドとプロセスの違い

共通点: どちらもオペレーティング システムに同時実行機能を提供します。

違い:

スケジューリングとリソース: スレッドはシステム スケジューリングの最小単位であり、プロセスはリソース割り当ての最小単位です。

アドレス空間の観点: 同じプロセスによって作成された複数のスレッドはプロセスのリソースを共有し、プロセスのアドレス空間は互いに独立しています。

通信の面では: スレッド通信は比較的単純で、グローバル変数を介してのみ実現できますが、重要なリソース保護の問題を考慮する必要があります。プロセス通信はより複雑で、プロセス間通信メカニズムの使用が必要です (ヘルプを使用) 3g ~ 4g のカーネル空間)

セキュリティの面: スレッドの安全性は低く、プロセスが終了するとすべてのスレッドが終了します。プロセスは比較的安全です。

3. スレッド機能

3.1 スレッドの作成

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
                    void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:thread:线程标识
     attr:线程属性, NULL:代表设置默认属性
     start_routine:函数名:代表线程函数
     arg:用来给前面函数传参
返回值:成功:0
       失败:错误码
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

char buf[32] = "";
//线程函数
void *handler(void *arg)
{
    sleep(1);
    printf("in the thread\n");
    printf("num:%d\n", *((int *)arg));
    printf("buf:%s\n", buf);
    pthread_exit(NULL); //结束线程
}
int main(int argc, char const *argv[])
{
    pthread_t tid;
    int num = 100;
    if(pthread_create(&tid, NULL, handler, &num) != 0)
    {
        perror("create thread err");
        return -1;
    }
    printf("in the main\n");
    printf("main tid:%lu\n", pthread_self());
    strcpy(buf, "hello");
    //线程回收,阻塞函数,等待子线程结束,回收线程资源
    pthread_join(tid, NULL);

    return 0;
}

3.2 終端ねじ

int  pthread_exit(void *value_ptr) 
功能:用于退出线程的执行
参数:value_ptr:线程退出时返回的值
返回值:成功 : 0
        失败:errno

3.3 スレッドのリサイクル

int  pthread_join(pthread_t thread,  void **value_ptr) 
功能:用于等待一个指定的线程结束,阻塞函数
参数:thread:创建的线程对象
        value_ptr:指针*value_ptr指向线程返回的参数
返回值:成功 : 0
      失败:errno

3.4 スレッド番号の取得

pthread_t pthread_self(void);
功能:获取线程号
返回值:线程ID

3.5 糸の分離

int pthread_detach(pthread_t thread);
功能:让线程分离,线程退出让系统自动回收线程资源

演習: スレッドを介した通信を実装します。

メインスレッドはループして端末からデータを入力し、サブスレッドはループしてデータを出力し、quit が入力されるとプログラムを終了します。

要件: 最初に入力してから出力する

ヒント: フラグビット int flag = 0;

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

char buf[32] = "";
int flag = 0;
void *print(void *arg)
{
    while (1)
    {
        if (flag == 1)
        {
            if (strcmp(buf, "quit") == 0)
                break;
            printf("buf:%s\n", buf);
            flag = 0;
        }
    }
}
int main(int argc, char const *argv[])
{
    pthread_t tid;
    if (pthread_create(&tid, NULL, print, NULL) != 0)
    {
        perror("create thread err");
        return -1;
    }
    while (1)
    {
        scanf("%s", buf);
        flag = 1;
        if (strcmp(buf, "quit") == 0)
            break;
    }
    pthread_join(tid, NULL);

    return 0;
}

4. スレッドの同期

4.1 コンセプト

同期とは、複数のタスク (スレッド) が合意された順序で相互に連携して 1 つのことを完了することを指します。

4.2 同期メカニズム

スレッド間の同期はセマフォを通じて実現されます。

セマフォ:セマフォは、スレッドが実行を継続するかブロックして待機するかを決定します。セマフォは特定の種類のリソースを表し、その値はシステム内のリソースの数を表します。

セマフォは、初期化、P 操作 (リソースの適用)、V 操作 (リソースの解放) の 3 つの操作を通じてのみアクセスできる保護された変数です。

セマフォの値は非負の整数です

4.3 特徴

P 操作:

セマフォの値が 0 より大きい場合、リソースを申請できます。リソースが適用された後、セマフォの値は 1 減ります。

セマフォの値が 0 の場合、リソースは適用できず、機能はブロックされます。

V 操作:

ノンブロッキング、リリース操作が実行されるまで、セマフォの値は 1 ずつ増加します

4.4 機能

int  sem_init(sem_t *sem,  int pshared,  unsigned int value)  
功能:初始化信号量   
参数:sem:初始化的信号量对象
    pshared:信号量共享的范围(0: 线程间使用   非0:1进程间使用)
    value:信号量初值
返回值:成功 0
      失败 -1
int  sem_wait(sem_t *sem)  
功能:申请资源  P操作 
参数:sem:信号量对象
返回值:成功 0
      失败 -1
注:此函数执行过程,当信号量的值大于0时,表示有资源可以用,则继续执行,同时对信号量减1;当信号量的值等于0时,表示没有资源可以使用,函数阻塞
int  sem_post(sem_t *sem)   
功能:释放资源  V操作      
参数:sem:信号量对象
返回值:成功 0
      失败 -1
注:释放一次信号量的值加1,函数不阻塞

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

char buf[32] = "";
int flag = 0;
void *print(void *arg)
{
    while (1)
    {
        if (flag == 1)
        {
            if (strcmp(buf, "quit") == 0)
                break;
            printf("buf:%s\n", buf);
            flag = 0;
        }
    }
}
int main(int argc, char const *argv[])
{
    pthread_t tid;
    if (pthread_create(&tid, NULL, print, NULL) != 0)
    {
        perror("create thread err");
        return -1;
    }
    while (1)
    {
        scanf("%s", buf);
        flag = 1;
        if (strcmp(buf, "quit") == 0)
            break;
    }
    pthread_join(tid, NULL);

    return 0;
}

5. スレッド相互排他

5.1 コンセプト

重要なリソース:一度に 1 つのプロセスのみが使用を許可されるリソース

クリティカルセクション:共有リソースにアクセスするプログラムフラグメントを指します。

相互排他: 複数のスレッドが重要なリソースにアクセスする場合、同時にアクセスできるのは 1 つのスレッドだけです

     相互排他ロック: 相互排他ロックは、主に重要なリソースを保護するために使用される相互排他ロックによって実現できます。各重要なリソースは、ミューテックス ロックによって保護されます。スレッドは、クリティカル リソースにアクセスするには、最初にミューテックス ロックを取得する必要があります。リソースにアクセスしたらロックを解除します。ロックを取得できない場合、スレッドはロックが取得されるまでブロックされます。

5.2 関数インターフェース

int  pthread_mutex_init(pthread_mutex_t  *mutex, pthread_mutexattr_t *attr)  
功能:初始化互斥锁  
参数:mutex:互斥锁
    attr:  互斥锁属性  //  NULL表示缺省属性
返回值:成功 0
      失败 -1
int  pthread_mutex_lock(pthread_mutex_t *mutex)   
功能:申请互斥锁     
参数:mutex:互斥锁
返回值:成功 0
      失败 -1
注:和pthread_mutex_trylock区别:pthread_mutex_lock是阻塞的;pthread_mutex_trylock不阻塞,如果申请不到锁会立刻返回
int  pthread_mutex_unlock(pthread_mutex_t *mutex)   
功能:释放互斥锁     
参数:mutex:互斥锁
返回值:成功 0
      失败 -1
int  pthread_mutex_destroy(pthread_mutex_t  *mutex)  
功能:销毁互斥锁     
参数:mutex:互斥锁

ケース: グローバル配列 int a[10] = {};

t1: 反転配列内の要素をループします。

t2: 配列内の要素を周期的に出力します。

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

int a[10] = {0,1,2,3,4,5,6,7,8,9};
pthread_mutex_t lock;
void *print_handler(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&lock);
        for(int i = 0; i < 10; i++)
            printf("%d", a[i]);
        printf("\n");
        pthread_mutex_unlock(&lock);
        sleep(1);
    }
}
void *swap_handler(void *arg)
{
    int t;
    while(1)
    {
        pthread_mutex_lock(&lock);
        for(int i = 0; i < 5; i++)
        {
            t = a[i];
            a[i] = a[9-i];
            a[9-i] = t;
        }
        pthread_mutex_unlock(&lock);
    }
}
int main(int argc, char const *argv[])
{
    pthread_t t1, t2;
    pthread_create(&t1, NULL, print_handler, NULL);
    pthread_create(&t2, NULL, swap_handler, NULL);

    if(pthread_mutex_init(&lock, NULL) != 0)
    {
        perror("mutex init err");
        return -1;
    }

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    return 0;
}

5.3 デッドロック

複数のプロセス/スレッドが実行処理中にリソースの奪い合いや通信などにより発生するブロッキング現象のことで、外部からの力がなければ先に進めなくなります。

デッドロックが発生するために必要な 4 つの条件:

1. 相互排他的使用、つまり、リソースが 1 つのスレッドによって使用 (占有) されている場合、他のスレッドはそのリソースを使用できません。

2. プリエンプトできない リソース要求者はリソース占有者からリソースを強制的に奪うことはできず、リソースを解放できるのはリソース占有者のみです。

3. リクエスト アンド ホールド。つまり、リソース要求者が元のリソースの所有を維持しながら他のリソースを要求するとき。

4. 循環待機、つまり待機キューが存在します。P1 は P2 のリソースを占有し、P2 は P3 のリソースを占有し、P3 は P1 のリソースを占有します。これにより待機ループが形成されます。

注: 上記の 4 つの条件がすべて当てはまる場合、デッドロックが形成されます。もちろん、デッドロックの場合は、上記の条件のいずれかが破られれば、デッドロックが解消される可能性があります。

6. 条件変数

ミューテックスと併用して同期機構を実現する

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
功能:初始化条件变量
参数:cond:是一个指向结构pthread_cond_t的指针
    restrict attr:是一个指向结构pthread_condattr_t的指针,一般设为NULL
返回值:成功:0 失败:非0
int pthread_cond_wait(pthread_cond_t *restrict cond,    pthread_mutex_t *restrict mutex);
功能:等待条件的产生
参数:restrict cond:要等待的条件
     restrict mutex:对应的锁
返回值:成功:0,失败:不为0
注:当没有条件产生时函数会阻塞,同时会将锁解开;如果等待到条件产生,函数会结束阻塞同时进行上锁。
int pthread_cond_signal(pthread_cond_t *cond);
功能:产生条件变量
参数:cond:条件变量值
返回值:成功:0,失败:非0
注:必须等待pthread_cond_wait函数先执行,再产生条件才可以
int pthread_cond_destroy(pthread_cond_t *cond);
功能:将条件变量销毁
参数:cond:条件变量值
返回值:成功:0, 失败:非0

pthread_mtex_init(&lock, NULL);

事例:お金の入出金の例

メインスレッドは周期的にお金を入金し、サブスレッドは周期的にお金を引き出します。残高が 0 になるまで毎回 100 を引き出し、その後お金を入金します。

#include <stdio.h>
#include <pthread.h>
int money = 0;
pthread_mutex_t lock;
pthread_cond_t cond, cond1;
//取钱
void *getMoney(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&lock);
        if(money < 100)
            pthread_cond_wait(&cond, &lock);
        money -= 100;
        printf("money:%d\n", money);
        if(money < 100)
            pthread_cond_signal(&cond1);
        pthread_mutex_unlock(&lock);
    }
}
int main(int argc, char const *argv[])
{
    pthread_t t;
    if(pthread_create(&t, NULL, getMoney, NULL) != 0)
    {
        perror("create thread err");
        return -1;
    }
    //互斥锁
    pthread_mutex_init(&lock, NULL);
    //条件变量
    if(pthread_cond_init(&cond, NULL) != 0)
    {
        perror("cond init err");
        return -1;
    }
    if(pthread_cond_init(&cond1, NULL) != 0)
    {
        perror("cond init err");
        return -1;
    }
    //存钱
    while(1)
    {
        pthread_mutex_lock(&lock);
        if(money >= 100)
            pthread_cond_wait(&cond1, &lock);
        scanf("%d", &money); //50
        if(money >= 100)
            pthread_cond_signal(&cond); //产生条件
        pthread_mutex_unlock(&lock);  
    }
    pthread_join(t, NULL);
    return 0;
}

6. プロセス間通信

7種類

6種類

従来のプロセス間通信方法:

名前のないパイプ、名前付きパイプ、信号

システム V IPC オブジェクト:

共有メモリ、メッセージキュー、セマフォセット

BSD:

ソケット

1.匿名パイプ

1.1 特徴

a. アフィニティのあるプロセス間の通信にのみ使用できます

b. 半二重通信モード、固定読み取りおよび書き込み終了

c. パイプラインを特殊なファイルとしてみなし、読み書き関数などのファイル IO を利用して読み書きすることができます。

d. パイプラインは、ファイル記述子に基づく通信方法です。パイプが確立されると、2 つのファイル記述子が作成されます

fd[0] と fd[1]。このうち、fd[0]はパイプラインの読み込み用に固定され、fd[1]はパイプラインの書き込み用に固定されます。

1.2 関数インターフェース

int pipe(int fd[2])
功能:创建无名管道
参数:文件描述符 fd[0]:读端  fd[1]:写端
返回值:成功 0
      失败 -1
#include <stdio.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    int fd[2] = {0};
    char buf[65536] = "";
    //创建无名管道
    if(pipe(fd) < 0)
    {
        perror("pipe err");
        return -1;
    }
    printf("%d %d\n", fd[0], fd[1]);

    //对管道进行读写
    // write(fd[1], "hello", 5);
    // read(fd[0], buf, 32);
    // printf("%s\n", buf);
    //1.当管道中没有数据时,读阻塞
    // close(fd[1]); //关闭写端
    // ssize_t s = read(fd[0], buf, 32);
    // printf("%s %d\n", buf, s);
    //2.当写满管道时,写阻塞,当至少读出4k空间时,才可以继续写
    //管道大小:64k
    // write(fd[1], buf, 65536);
    // read(fd[0], buf, 4096);
    // printf("befor\n");
    // write(fd[1], "a", 1);
    // printf("after\n");
    //3.当读端关闭,写管道时,会导致管道破裂
    close(fd[0]);
    write(fd[1], "hello", 5); //SIGPIPE
    printf("after\n");

    return 0;
}

1.3 注意事項

a. パイプラインにデータがない場合、読み取り操作はブロックされます。

    パイプラインにデータがない場合、書き込みエンドを閉じると、読み取り操作がすぐに戻ります。

b. パイプラインがいっぱいになり (パイプライン サイズは 64K)、データの書き込みがブロックされます。4K のスペースが確保されると、書き込みは続行されます。

c. パイプの読み取り側が存在する場合にのみ、パイプにデータを書き込むことが意味を持ちます。そうしないと、パイプが破損し、パイプにデータを書き込むプロセスがカーネルから SIGPIPE シグナルを受信します (通常はパイプ破損エラー)。

演習: 親子プロセス通信。

親プロセスはターミナルから文字列を周期的に入力し、子プロセスは文字列を周期的に出力し、quit が入力されるとプログラムは終了します。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>


int main(int argc, char const *argv[])
{
    char buf[32] = "";
    pid_t id;
    int fd[2] = {0};
    if(pipe(fd) < 0)
    {
        perror("pipe err");
        return -1;
    }
    if ((id = fork()) < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (id == 0)
    {
        while(1)
        {
            char buf[32] = "";
            read(fd[0], buf, 32);
            if(strcmp(buf, "quit") == 0)
                break;
            printf("buf:%s\n", buf);
        }
        exit(0);
    }
    else
    {
        while(1)
        {
            char buf[32] = "";
            // scanf("%s", buf);
            fgets(buf, 32, stdin);
            write(fd[1], buf, strlen(buf)+1);
            if(strcmp(buf, "quit") == 0)
                break;
        }
        wait(NULL);
    }

    return 0;
}

2. 有名なパイプライン

2.1 特徴

a. 名前付きパイプを使用すると、無関係な 2 つのプロセスが相互に通信できるようになります。

b. 名前付きパイプはパス名で識別でき、ファイル システムに表示されますが、その内容はメモリに保存されます。

c. プロセスはファイル IO を通じて既知のパイプを操作します

d. 名前付きパイプは先入れ先出しルールに従います

e. lseek() などの操作はサポートされません。

2.2 関数インターフェース

int mkfifo(const char *filename,mode_t mode);
功能:创健有名管道
参数:filename:有名管道文件名
       mode:权限
返回值:成功:0
       失败:-1,并设置errno号
注意对错误的处理方式:
如果错误是file exist时,注意加判断,如:if(errno == EEXIST)

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

int main(int argc, char const *argv[])
{
    int fd;
    //创建管道
    if (mkfifo("./fifo", 0666) < 0)
    {
        if (errno == EEXIST)
            printf("file exists\n");
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    printf("mkfifo ok\n");
    //打开管道
    fd = open("./fifo", O_RDWR);
    if(fd < 0)
    {
        perror("open err");
        return -1;
    }
    char buf[32] = "hello";
    char data[32] = "";
    write(fd, buf, strlen(buf));

    read(fd, data, 32);
    printf("%s\n", data);

    return 0;
}

2.3 注意事項

a. 書き込み専用モード、別のプロセスが読み取りを開くまで書き込みをブロックします。

b. 読み取り専用モード、別のプロセスが書き込みを開始するまで読み取りブロック

c. 読み取りおよび書き込み可能、​​パイプラインにデータがない場合は読み取りブロッキング

演習: 2 つの無関係なプロセス間の通信を実装します。

read.c : 端末からデータを読み取ります

write.c : データを端末に出力します

quitを入力すると終了します。

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

int main(int argc, char const *argv[])
{
    int fd;
    char buf[32] = "";
    //创建管道
    if (mkfifo("./fifo", 0666) < 0)
    {
        if (errno == EEXIST)
            printf("file exists\n");
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    printf("mkfifo ok\n");
    //打开管道
    fd = open("./fifo", O_WRONLY);
    if(fd < 0)
    {
        perror("open err");
        return -1;
    }
    while(1)
    {
        scanf("%s", buf);
        write(fd, buf, strlen(buf)+1);
        if(!strcmp(buf, "quit"))
            break;
    }

    return 0;
}


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

int main(int argc, char const *argv[])
{
    int fd;
    char buf[32] = "";
    //创建管道
    if (mkfifo("./fifo", 0666) < 0)
    {
        if (errno == EEXIST)
            printf("file exists\n");
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    printf("mkfifo ok\n");
    //打开管道
    fd = open("./fifo", O_RDONLY);
    if(fd < 0)
    {
        perror("open err");
        return -1;
    }
    while(1)
    {
        read(fd, buf, 32);
        if(!strcmp(buf, "quit"))
            break;
        printf("buf:%s\n", buf);
    }

    return 0;
}

2.4 名前付きパイプと名前なしパイプの違い

名前のないパイプ

有名なパイプライン

特徴

アフィニティプロセス間でのみ使用可能

半二重通信

読み取りと書き込みの終了は固定です。 fd[0]: 読み取り、fd[1]: 書き込み終了

ファイルIO経由で操作する

手順: パイプラインの作成、読み取りおよび書き込み操作

無関係な任意のプロセス間使用

パスにパイプライン ファイルがあり、実際のデータはカーネル空間に存在します

ファイルIO経由で操作する

ステップ: パイプの作成、パイプのオープン、読み取りおよび書き込み操作

関数

パイプ

mkfifo

読み取りおよび書き込み特性

パイプ内にデータがない場合、読み取りブロック

パイプがいっぱいのときにブロックを書き込む

3. 信号

3.1 コンセプト

1) 信号は割り込み機構をソフトウェアレベルでシミュレーションしたものであり、非同期通信方式です 

2) シグナルはユーザー空間プロセスとカーネルプロセスの間で直接対話することができ、カーネルプロセスはそれを使用してどのシステムイベントが発生したかをユーザー空間プロセスに通知することもできます。

3) プロセスが現在実行状態にない場合、シグナルはプロセスが実行を再開するまでカーネルによって保存され、その後カーネルに配信されます。シグナルがプロセスによってブロックされるように設定されている場合、シグナルの配信は遅延します。ブロックがキャンセルされるときにプロセスに渡されるまで。

 3.2. 信号への応答

1) シグナルを無視します: シグナルに対していかなる処理も行いませんが、無視できないシグナルが 2 つあります: SIGKILL と SIGSTOP

2) 信号をキャプチャ: 信号処理関数を定義し、信号が発生すると、対応する処理関数を実行します。

3) デフォルトの動作を実行します。Linux は各信号のデフォルトの動作を指定します。 

3. 3. 信号の種類 

SIGKILL: プロセスを終了します。無視できず、捕捉できません。

SIGSTOP: プロセスを終了します。無視できず、捕捉できません。

SIGCHLD: 子プロセスの状態が変化したときに親プロセスに送信されるシグナルは、プロセスを終了しません。

SIGINT: プロセスを終了します。ショートカット ctrl+c に対応します。

SIGTSTP: 一時停止信号、ショートカット ctrl+z に対応

SIGQUIT: 終了シグナル。ショートカット ctrl+\ に対応します。

SIGALRM: アラーム クロック信号。アラーム機能はタイミングを設定します。設定時間に達すると、カーネルはこの信号をプロセスに送信してプロセスを終了します。

SIGTERM: 端末プロセスを終了します。kill が番号なしで使用される場合、デフォルトはこのシグナルです

3.4 関数インターフェース

int kill(pid_t pid, int sig);
功能:信号发送
参数:pid:指定进程
   sig:要发送的信号
返回值:成功 0     
       失败 -1

int raise(int sig);
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0   
       失败 -1

int pause(void);
功能:用于将调用进程挂起,直到收到信号为止。
#include <signal.h>
 typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:signum:要处理的信号
      handler:信号处理方式
            SIG_IGN:忽略信号
            SIG_DFL:执行默认操作
           handler:捕捉信号  void handler(int sig){} //函数名可以自定义
返回值:成功:设置之前的信号处理方式
      失败:-1
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    // kill(getpid(), SIGKILL);

    raise(SIGKILL); //给自己发信号

    // while (1)
    //     ;
    while (1)
        pause(); //将当前进程挂起(阻塞),直到收到信号结束

    return 0;
}

typedef void (*signhandler_t)(int); //typedef unsigned int INT;

typedef void (*)(int) sighandler_t;

sighandler_t signal(intsignum, void (*handler)(int) );

void ハンドラー(int sig)

{

if(sig == SIGINT)

printf("xxx\n");

else if(sig == SIGQUI T)

}

シグナル(SIGINT、ハンドラー);

シグナル(SIGQUIT、ハンドラー)

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

void handler(int sig)
{
    printf("ctrl+c\n");
}
int main(int argc, char const *argv[])
{ 
    // signal(SIGINT, SIG_IGN);//忽略信号
    // signal(SIGINT, SIG_DFL); //执行默认操作
    signal(SIGINT, handler); //捕捉信号

    while(1)
        pause();

    return 0;
}

手術:

  1. 2 つのプロセスが cp 関数を実装します

./r srcファイル

./w 新しいファイル

  1. 信号の知識を使用してドライバーと導体の問題を実装します。

1) 車掌は SIGINT (運転を表す) 信号をキャプチャし、SIGUSR1 信号をドライバーに送信し、ドライバーは印刷します (レッツ ゴーゴーゴー)

  2) 車掌は SIGQUIT (停止を表す) 信号をキャプチャし、SIGUSR2 信号をドライバーに送信し、ドライバーは出力します (バスを停止します)。

3) 運転手は SIGTSTP (ターミナル到着に代わって) 信号を取得し、SIGUSR1 信号を車掌に送信し、車掌は (バスから降りてください) と出力します。

4) 運転士は車掌が降車するのを待ち、再び降車する。

ドライバー: 親プロセス

信号をキャプチャ:

信号を無視します。

コンダクタ: 子プロセス

信号をキャプチャ:

信号を無視します。

./a.out

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

pid_t pid;
void driver(int sig)
{
    if (sig == SIGUSR1)
        printf("let's gogogo\n");
    else if (sig == SIGUSR2)
        printf("stop the bus\n");
    else if (sig == SIGTSTP)
    {
        kill(pid, SIGUSR1);
        wait(NULL);
        exit(0);
    }
}
void saler(int sig)
{
    if (sig == SIGINT)
        kill(getppid(), SIGUSR1);
    else if (sig == SIGQUIT)
        kill(getppid(), SIGUSR2);
    else if (sig == SIGUSR1)
    {
        printf("please get off the bus\n");
        exit(0);
    }
}
int main(int argc, char const *argv[])
{
    if ((pid = fork()) < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0)
    {
        signal(SIGINT, saler);
        signal(SIGQUIT, saler);
        signal(SIGUSR1, saler);
        signal(SIGTSTP, SIG_IGN);
    }
    else
    {
        signal(SIGUSR1, driver);
        signal(SIGUSR2, driver);
        signal(SIGTSTP, driver);
        signal(SIGINT, SIG_IGN);
        signal(SIGQUIT, SIG_IGN);
    }
    while (1)
        pause();

    return 0;
}

4. 共有メモリ

4.1 特徴

   1) 共有メモリは最も効率的なプロセス間通信方法であり、プロセスはデータをコピーせずにメモリを直接読み書きできます。

   2) 複数のプロセス間で情報を交換するために、カーネルはメモリ領域を特別に確保します。メモリ領域にアクセスする必要があるプロセスは、この領域を独自のプライベート アドレス空間にマッピングできます。

   3) プロセスはデータをコピーせずにこのメモリ領域を直接読み書きできるため、効率が大幅に向上します。

   4) 複数のプロセスがメモリを共有するため、ミューテックスやセマフォなどの何らかの同期メカニズムに依存する必要もあります。

4.2ステップ

0) キー値を作成する

1) 共有メモリを作成または開く

2) マッピング

3) マップを解除する

4) 共有メモリの削除

4.3 関数インターフェース

key_t ftok(const char *pathname, int proj_id);
功能:创建key值
参数:pathname:文件名
     proj_id:取整型数的低8位数值
返回值:成功:key值
       失败:-1
int shmget(key_t key, size_t size, int shmflg);
功能:创建或打开共享内存
参数:
    key  键值
    size   共享内存的大小
    shmflg   IPC_CREAT|IPC_EXCL|0777
返回值:成功   shmid
      出错    -1
void  *shmat(int  shmid,const  void  *shmaddr,int  shmflg);
功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
参数:
    shmid   共享内存的id号
    shmaddr   一般为NULL,表示由系统自动完成映射
              如果不为NULL,那么有用户指定
    shmflg:SHM_RDONLY就是对该共享内存只进行读操作
                0     可读可写
返回值:成功:完成映射后的地址,
      出错:-1的地址
用法:if((p = (char *)shmat(shmid,NULL,0)) == (char *)-1)
int shmdt(const void *shmaddr);
功能:取消映射
参数:要取消的地址
返回值:成功0  
      失败的-1
int  shmctl(int  shmid,int  cmd,struct  shmid_ds   *buf);
功能:(删除共享内存),对共享内存进行各种操作
参数:
    shmid   共享内存的id号
    cmd     IPC_STAT 获得shmid属性信息,存放在第三参数
            IPC_SET 设置shmid属性信息,要设置的属性放在第三参数
            IPC_RMID:删除共享内存,此时第三个参数为NULL即可
返回:成功0 
     失败-1
用法:shmctl(shmid,IPC_RMID,NULL);

共有メモリの表示コマンド:

ipcs -m 

共有メモリを削除するコマンド:

ipcrm -m シュミッド

コード例:

int main(int argc, char const *argv[])
{
    key_t key;
    int shmid;
    //创建key值
    key = ftok("./app", 'b');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);
    //创建或打印共享内存
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("shmid:%d\n", shmid);
    //映射
    char *p = NULL;
    p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写
    if(p == (char *)-1)
    {
        perror("shmat err");
        return -1;
    }
    strcpy(p, "hello");
    printf("%s\n", p);
    //取消映射
    shmdt(p);
    //删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

演習: 共有メモリ通信を使用して、あるプロセスが端末からデータを入力し、別のプロセスがデータを出力します。

要件: quit を入力するとプログラムが終了する

同期: フラグビット

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>

struct msg
{
    int flg;
    char buf[32];
};
int main(int argc, char const *argv[])
{
    key_t key;
    int shmid;
    //创建key值
    key = ftok("./app", 'b');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);
    //创建或打印共享内存
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("shmid:%d\n", shmid);
    //映射
    struct msg *p = NULL;
    p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写
    if(p == (struct msg *)-1)
    {
        perror("shmat err");
        return -1;
    }
    p->flg = 0;
    while(1)
    {
        scanf("%s", p->buf);
        p->flg = 1;
        if(strcmp(p->buf, "quit") == 0)
            break;
    }

    return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    key_t key;
    int shmid;
    //创建key值
    key = ftok("./app", 'b');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);
    //创建或打印共享内存
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("shmid:%d\n", shmid);
    //映射
    char *p = NULL;
    p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写
    if(p == (char *)-1)
    {
        perror("shmat err");
        return -1;
    }
    strcpy(p, "hello");
    printf("%s\n", p);
    //取消映射
    shmdt(p);
    //删除共享内存
    shmctl(shmid, IPC_RMID, NULL);



    return 0;
}

5.信号灯集

 5.1. 特徴:

セマフォはセマフォとも呼ばれます。これは、異なるプロセス間、または特定のプロセス内の異なるスレッド間で同期するためのメカニズムであり、System V セマフォは 1 つ以上のセマフォの集合です。これらはそれぞれ個別のカウント セマフォです。また、Posix セマフォは、単一のカウント セマフォを指します。

セマフォセットによる共有メモリの同期

5.2 手順:

0) キー値を作成する

1) セマフォセット semget を作成または開きます 

2) セマフォセット semctl を初期化します。

3) pv操作semop

4) セマフォセット semctl を削除します。

5.3 関数インターフェース

int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号灯
参数:key:ftok产生的key值
    nsems:信号灯集中包含的信号灯数目
    semflg:信号灯集的访问权限,通常为IPC_CREAT |0666
返回值:成功:信号灯集ID
       失败:-1
int semop ( int semid, struct sembuf  *opsptr,  size_t  nops);
功能:对信号灯集合中的信号量进行PV操作
参数:semid:信号灯集ID
     opsptr:操作方式
     nops:  要操作的信号灯的个数 1个
返回值:成功 :0
      失败:-1
struct sembuf {
   short  sem_num; // 要操作的信号灯的编号
   short  sem_op;  //    0 :  等待,直到信号灯的值变成0
                   //   1  :  释放资源,V操作
                   //   -1 :  分配资源,P操作                    
    short  sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};
用法:
申请资源 P操作:
    mysembuf.sem_num = 0;
    mysembuf.sem_op = -1;
    mysembuf.sem_flg = 0;
    semop(semid, &mysembuf, 1);
释放资源 V操作:
    mysembuf.sem_num = 0;
    mysembuf.sem_op = 1;
    mysembuf.sem_flg = 0;
    semop(semid, &mysembuf, 1);
int semctl ( int semid, int semnum,  int cmd…/*union semun arg*/);
功能:信号灯集合的控制(初始化/删除)
参数:semid:信号灯集ID
    semnum: 要操作的集合中的信号灯编号
     cmd:
        GETVAL:获取信号灯的值,返回值是获得值
        SETVAL:设置信号灯的值,需要用到第四个参数:共用体
        IPC_RMID:从系统中删除信号灯集合
返回值:成功 0
      失败 -1
用法:初始化:
union semun{
    int val; //信号灯的初值
}mysemun;
mysemun.val = 10;
semctl(semid, 0, SETVAL, mysemun);
获取信号灯值:函数semctl(semid, 0, GETVAL)的返回值
删除信号灯集:semctl(semid, 0, IPC_RMID);

5.4 コマンド

ipcs -s: ビューシグナルランプセット

ipcrm -s semid: セマフォ セットを削除します

例:

union semun {
    int val; //信号灯的初值
};
int main(int argc, char const *argv[])
{
    key_t key;
    int semid;
    key = ftok("./app", 'b');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);
    //创建或打开信号灯集
    semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
    if (semid < 0)
    {
        if (errno == EEXIST)
            semid = semget(key, 2, 0666);
        else
        {
            perror("semget err");
            return -1;
        }
    }
    else
    {
        //初始化
        union semun sem;
        sem.val = 10;
        semctl(semid, 0, SETVAL, sem); //对编号为0的信号灯初值设置为10
        sem.val = 0;
        semctl(semid, 1, SETVAL, sem); //对编号为1的信号灯初值设置为0
    }
    printf("semid:%d\n", semid);
    printf("%d\n",semctl(semid, 0, GETVAL));//获取编号为0的信号灯的值
    printf("%d\n",semctl(semid, 1, GETVAL));//获取编号为1的信号灯的值

    //pv操作
    //p操作:申请资源
    struct sembuf buf;
    buf.sem_num = 0; //信号灯的编号
    buf.sem_op = -1; //p操作
    buf.sem_flg = 0; //阻塞
    semop(semid, &buf, 1);
    //v操作:释放资源
    buf.sem_num = 1;
    buf.sem_op = 1; //v操作
    buf.sem_flg = 0;
    semop(semid, &buf, 1);
    printf("%d\n",semctl(semid, 0, GETVAL));//获取编号为0的信号灯的值
    printf("%d\n",semctl(semid, 1, GETVAL));//获取编号为1的信号灯的值
    //删除信号灯集
    semctl(semid, 0, IPC_RMID); //指定任意一个编号即可删除信号灯集
    return 0;

//input.c代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/sem.h>
#include <string.h>

union semun {
    int val; //信号灯的初值
};
//初始化
void seminit(int semid, int snum, int val)
{
    union semun sem;
    sem.val = val;
    semctl(semid, snum, SETVAL, sem);
}
//pv操作
void sem_op(int semid, int num, int op)
{
    struct sembuf buf;
    buf.sem_num = num;
    buf.sem_op = op;
    buf.sem_flg = 0;
    semop(semid, &buf, 1);
}
int main(int argc, char const *argv[])
{
    key_t key;
    int shmid, semid;
    //创建key值
    key = ftok("./app", 'b');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);
    //创建或打印共享内存
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("shmid:%d\n", shmid);
    //创建或打开信号灯集
    semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
    if (semid < 0)
    {
        if (errno == EEXIST)
            semid = semget(key, 1, 0666);
        else
        {
            perror("semget err");
            return -1;
        }
    }
    else
    {
        //初始化
        seminit(semid, 0, 0);
    }
    //映射
    char *p = NULL;
    p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写
    if(p == (char *)-1)
    {
        perror("shmat err");
        return -1;
    }
    while(1)
    {
        scanf("%s", p);
        //释放资源
        sem_op(semid, 0, 1);
        if(strcmp(p, "quit") == 0)
            break;
    }

    return 0;
}



//output.c代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/sem.h>
#include <string.h>

union semun {
    int val; //信号灯的初值
};
//初始化
void seminit(int semid, int snum, int val)
{
    union semun sem;
    sem.val = val;
    semctl(semid, snum, SETVAL, sem);
}
//pv操作
void sem_op(int semid, int num, int op)
{
    struct sembuf buf;
    buf.sem_num = num;
    buf.sem_op = op;
    buf.sem_flg = 0;
    semop(semid, &buf, 1);
}
int main(int argc, char const *argv[])
{
    key_t key;
    int shmid, semid;
    //创建key值
    key = ftok("./app", 'b');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);
    //创建或打印共享内存
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("shmid:%d\n", shmid);
    //创建或打开信号灯集
    semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
    if (semid < 0)
    {
        if (errno == EEXIST)
            semid = semget(key, 1, 0666);
        else
        {
            perror("semget err");
            return -1;
        }
    }
    else
    {
        //初始化
        seminit(semid, 0, 0);
    }
    //映射
    char *p = NULL;
    p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写
    if(p == (char *)-1)
    {
        perror("shmat err");
        return -1;
    }
    while(1)
    {
        //申请资源
        sem_op(semid, 0, -1);
        if(strcmp(p, "quit") == 0)
            break;
        printf("data:%s\n", p);
    }

    //取消映射
    shmdt(p);
    //删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    //删除信号灯集
    semctl(semid, 0, IPC_RMID);

    return 0;
}

6. メッセージキュー

 6.1 特徴:

メッセージキューはIPCオブジェクトの一種です

メッセージ キューはメッセージ キュー ID によって一意に識別されます。

メッセージ キューはメッセージのリストです。ユーザーは、メッセージ キューにメッセージを追加したり、メッセージを読み取ったりすることができます。

メッセージキューはタイプ別にメッセージを送受信できます

6.2 手順:

1) キー値 ftok を作成します

2) メッセージキューを作成または開く msgget  

3) メッセージの追加/メッセージの読み取り msgsnd/msgrcv

4) メッセージキュー msgctl を削除します。

6.3 操作コマンド

ipcs -q : メッセージキューを表示します

ipcrm -q msgid : メッセージキューを削除します

6.4. 機能インターフェイス

int msgget(key_t key, int flag);
功能:创建或打开一个消息队列
参数:  key值
       flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666
返回值:成功:msgid
       失败:-1
int msgsnd(int msqid, const void *msgp, size_t size, int flag); 
功能:添加消息
参数:msqid:消息队列的ID
      msgp:指向消息的指针。常用消息结构msgbuf如下:
          struct msgbuf{
            long mtype;        //消息类型
            char mtext[N]};   //消息正文
   size:发送的消息正文的字节数
   flag:IPC_NOWAIT消息没有发送完成函数也会立即返回    
         0:直到发送完成函数才返回
返回值:成功:0
      失败:-1
使用:msgsnd(msgid, &msg,sizeof(msg)-sizeof(long), 0)
注意:消息结构除了第一个成员必须为long类型外,其他成员可以根据应用的需求自行定义。
int msgrcv(int msgid,  void* msgp,  size_t  size,  long msgtype,  int  flag);
功能:读取消息
参数:msgid:消息队列的ID
     msgp:存放读取消息的空间
     size:接受的消息正文的字节数
    msgtype:0:接收消息队列中第一个消息。
            大于0:接收消息队列中第一个类型为msgtyp的消息.
            小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
     flag:0:若无消息函数会一直阻塞
        IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
返回值:成功:接收到的消息的长度
      失败:-1
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
功能:对消息队列的操作,删除消息队列
参数:msqid:消息队列的队列ID
     cmd:
        IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。
        IPC_SET:设置消息队列的属性。这个值取自buf参数。
        IPC_RMID:从系统中删除消息队列。
     buf:消息队列缓冲区
返回值:成功:0
      失败:-1
用法:msgctl(msgid, IPC_RMID, NULL)

例:

#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
#include <string.h>
#include <sys/msg.h>

struct msgbuf{
    long type;
    int num;
    char buf[32];
};
int main(int argc, char const *argv[])
{
    key_t key;
    int msgid;
    //创建key值
    key = ftok("./app", 'b');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);
    //创建消息队列
    msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);
    if(msgid < 0)
    {
        if(errno == EEXIST)
            msgid = msgget(key, 0666);
        else
        {
            perror("msgget err");
            return -1;
        }     
    }
    //添加消息
    int size = sizeof(struct msgbuf)-sizeof(long);
    struct msgbuf msg = {1, 100, "hello"};
    struct msgbuf msg1 = {2, 200, "world"};
    msgsnd(msgid, &msg, size, 0);
    msgsnd(msgid, &msg1, size, 0);
    //读取消息
    struct msgbuf m;
    msgrcv(msgid, &m, size, 2, 0);
    printf("%d %s\n", m.num, m.buf);
    //删除消息队列
    msgctl(msgid, IPC_RMID, NULL);
    return 0;
}

演習: 2 つのプロセスはメッセージ キューを介して通信し、一方のプロセスは端末から送信されたコマンドを入力し、もう一方のプロセスはコマンドを受信して​​対応する操作ステートメントを出力します。

LED ONを入力すると、別のプロセスが「点灯」を出力します

LED OFF を入力すると、別のプロセスが「消灯」を出力します

#include <stdio.h>                     //send.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
#include <string.h>
#include <sys/msg.h>

struct msgbuf
{
    long type;
    char buf[32];
};
int main(int argc, char const *argv[])
{
    key_t key;
    int msgid;
    //创建key值
    key = ftok("./app", 'b');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);
    //创建消息队列
    msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);
    if(msgid < 0)
    {
        if(errno == EEXIST)
            msgid = msgget(key, 0666);
        else
        {
            perror("msgget err");
            return -1;
        }     
    }
    struct msgbuf msg;
    msg.type = 1;
    int s = sizeof(struct msgbuf)-sizeof(long);
    while(1)
    {
        scanf("%s", msg.buf);
        msgsnd(msgid, &msg, s, 0);
    }
    return 0;
}





#include <stdio.h>                               //recieve.c
#include <stdio.h>
#include <sys/types.h>                          
#include <sys/ipc.h>
#include <errno.h>
#include <string.h>
#include <sys/msg.h>

struct msgbuf
{
    long type;
    char buf[32];
};
int main(int argc, char const *argv[])
{
    key_t key;
    int msgid;
    //创建key值
    key = ftok("./app", 'b');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key);
    //创建消息队列
    msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);
    if(msgid < 0)
    {
        if(errno == EEXIST)
            msgid = msgget(key, 0666);
        else
        {
            perror("msgget err");
            return -1;
        }     
    }
    struct msgbuf msg;
    int s = sizeof(struct msgbuf)-sizeof(long);
    while(1)
    {
        msgrcv(msgid, &msg, s, 1, 0);
        if(strcmp(msg.buf, "LEDON") == 0)
            printf("开灯\n");
        else if(strcmp(msg.buf, "LEDOFF") == 0)
            printf("关灯\n");
    }
    
    return 0;
}

おすすめ

転載: blog.csdn.net/qq_52049228/article/details/130565655