nginxプロセスロックの実装の詳細な説明

nginxプロセスロックの実装の詳細な説明

1. nginxプロセスロックの役割

Nginx は、マルチプロセス同時実行モデル アプリケーションです。簡単に言うと、ネットワーク リクエストをリッスンする複数のワーカーがあります。リクエストを受け取ったワーカーが、後続のトランザクションを完了します。ロックがない場合、このシナリオになります。システムがリクエストを受信すると、ポートを監視できるプロセスが同時にトランザクションを処理します。もちろん、システムはそのような悪いことが起こるのを防ぎますが、いわゆるパニックの群れは発生します。 (正しいかどうかは分かりませんが、おそらく私が言いたかったのはそういうことだと思います)

そのため、多くのプロセスが同時にリッスンすることを避けるために、複数のワーカーが順番にソケットをリッスンする必要があります。複数のワーカーを順番にリッスンするために、この記事で説明したプロセス ロックが登場します。ロックを取得したプロセスだけがネットワーク リクエストにアクセスできます。

それは次のプロセスです。

// ワーカーコアトランザクションフレームワーク // ngx_event.c
空所
ngx_process_events_and_timers(ngx_cycle_t *サイクル)
{
    ngx_uint_t フラグ;
    ngx_msec_t タイマー、デルタ;

    ngx_timer_resolution の場合 {
        タイマー = NGX_TIMER_INFINITE;
        フラグ = 0;

    } それ以外 {
        タイマー = ngx_event_find_timer();
        フラグ = NGX_UPDATE_TIME;

#if (NGX_WIN32)

        /* ネットワークが非アクティブな場合にマスターからの信号を処理する */

        if (タイマー == NGX_TIMER_INFINITE || タイマー > 500) {
            タイマー = 500;
        }

#終了
    }

    ngx_use_accept_mutex の場合
        // 公平性を確保するために、ロックの競合が繰り返されないようにします if (ngx_accept_disabled > 0) {
            ngx_accept_disabled--;

        } それ以外 {
            // ロックを取得したプロセスのみがソケットに対して accept() 操作を実行します // 他のワーカーは以前に接続されていたリクエストと読み取り/書き込み操作を処理します if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                戻る;
            }

            ngx_accept_mutex_held の場合 {
                フラグ |= NGX_POST_EVENTS;

            } それ以外 {
                タイマー == NGX_TIMER_INFINITE の場合
                    || タイマー > ngx_accept_mutex_delay)
                {
                    タイマー = ngx_accept_mutex_delay;
                }
            }
        }
    }
    //その他のコアトランザクション処理if (!ngx_queue_empty(&ngx_posted_next_events)) {
        ngx_event_move_posted_next(サイクル);
        タイマー = 0;
    }

    デルタ = ngx_current_msec;

    (void) ngx_process_events(サイクル、タイマー、フラグ);

    デルタ = ngx_current_msec - デルタ;

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT、サイクル->ログ、0、
                   "タイマーデルタ: %M", デルタ);

    ngx_event_process_posted(サイクル、&ngx_posted_accept_events);

    ngx_accept_mutex_held の場合 {
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }

    if (デルタ) {
        ngx_event_expire_timers();
    }

    ngx_event_process_posted(サイクル、&ngx_posted_events);
}
// ロックを取得し、ソケットの accept() プロセスを次のように登録します ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *サイクル)
{
    ngx_shmtx_trylock(&ngx_accept_mutex) の場合 {

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT、サイクル->ログ、0、
                       "ミューテックスロックを受け入れる");

        ngx_accept_mutex_held の場合、 ngx_accept_events は 0 です。
            NGX_OK を返します。
        }

        ngx_enable_accept_events(cycle) == NGX_ERROR の場合 {
            // ロック解除操作 ngx_shmtx_unlock(&ngx_accept_mutex);
            NGX_ERROR を返します。
        }

        ngx_accept_events = 0;
        ngx_accept_mutex_held = 1;

        NGX_OK を返します。
    }

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT、サイクル->ログ、0、
                   "ミューテックスロックの受け入れに失敗しました: %ui", ngx_accept_mutex_held);

    ngx_accept_mutex_held の場合 {
        ngx_disable_accept_events(cycle, 0) == NGX_ERRORの場合{
            NGX_ERROR を返します。
        }

        ngx_accept_mutex_held = 0;
    }

    NGX_OK を返します。
}

残りについてはこれ以上説明する必要はありません。コア、つまりロックを取得したワーカーだけが accept 操作を実行できます。ロックの取得に失敗したワーカーは、以前の accept() 権限を積極的に解放する必要があります。この方法では、受け入れイベントを同時に処理するのは 1 つのワーカーだけです。

2. エントリーレベルのロックの使用

ロックのようなものは、通常、プログラミング言語自体によって定義されたインターフェースを持っているか、または固定された使用法を持っています。

たとえば、java の synchronized xxx、CountDownLatch、CyclicBarrier、ReentrantLock、ReentrantReadWriteLock、Semaphore などのロック関連の同時実行パッケージ ロックなどです。

たとえば、Python の threading.Lock()、threading.RLock() などです。

たとえば、PHP の flock() など...

エントリーレベルと呼ばれる理由は、これらはすべてインターフェース API であるためです。それ以上の知識は必要なく、使用仕様に応じて調整するだけで済みます。しかし、細部をうまく活用するのは実際には簡単ではありません。

3. nginxプロセスロックの実装

nginx は C 言語で書かれているので、間違いなく最下層に近いです。ロックの実装を通じてロックがどのように実装されるかを見ることができれば、ロックのより深い意味をより深く理解できるようになります。

一般的に、ロックには、ロック データ構造の定義、ロック ロジック、ロック解除ロジック、およびいくつかの通知メカニズム、タイムアウト メカニズムなどの主要な側面が含まれます。これらの指示のいくつかを見て、nginx がそれらをどのように実装するかを見てみましょう。

3.1 ロックデータ構造

まず、ロックする変数を定義し、複数のプロセスと共有する値をインスタンス化する必要があります。

// イベント/ngx_event.c
// グローバル accept ロック変数定義 ngx_shmtx_t ngx_accept_mutex;
// このロックには、 volatile 修飾子を使用したアトミック実装があります。 typedef volatile ngx_atomic_uint_t ngx_atomic_t;
typedef構造体{
#if (NGX_HAVE_ATOMIC_OPS)
    // アトミック更新変数はロックを実装するために使用され、その背後には共有メモリ領域があります ngx_atomic_t *lock;
#if (NGX_HAVE_POSIX_SEM)
    ngx_atomic_t *待機;
    ngx_uint_t セマフォ;
    sem_t sem;
#終了
#それ以外
    // FD はロックを実装するために使用されます。FD の背後にはファイル インスタンスがあります ngx_fd_t fd;
    u_char *名前;
#終了
    ngx_uint_t スピン;
} ngx_shmtx_t;
// 共有メモリデータ構造定義 typedef struct {
    u_char *アドレス;
    size_t サイズ;
    ngx_str_t 名前;
    ngx_log_t *ログ;
    ngx_uint_t が存在します; /* 符号なし 存在します:1; */
} ngx_shm_t;

3.2、fdベースのロック/ロック解除の実装

ロック インスタンスを作成したら、それをロックおよびロック解除できます。 Nginx には、主にプラットフォームの違いに基づいて、ファイルベースまたは共有内部実装の 2 つのロック実装があります。 fd、つまりファイルベースの実装に基づいているため、これはまだ少し重い操作です。次のように:

// ngx_shmtx.c
ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
    ngx_err_t エラー;

    エラー = ngx_trylock_fd(mtx->fd);

    (エラー == 0)の場合{
        1 を返します。
    }

    NGX_EAGAIN の場合
        0を返します。
    }

#if __osf__ /* Tru64 UNIX */

    NGX_EACCES の場合
        0を返します。
    }

#終了

    ngx_log_abort(err, ngx_trylock_fd_n " %s が失敗しました", mtx->name);

    0を返します。
}
// コア/ngx_shmtx.c
// 1. ロックプロセス ngx_err_t
ngx_trylock_fd(ngx_fd_t fd)
{
    構造体 flock fl;

    ngx_memzero(&fl, sizeof(構造体flock));
    fl.l_type = F_WRLCK;
    fl.l_whence = SEEK_SET;

    fcntl(fd, F_SETLK, &fl) == -1 の場合 {
        ngx_errno を返します。
    }

    0を返します。
}
// os/unix/ngx_file.c
ngx_err_t
ngx_lock_fd(ngx_fd_t fd)
{
    構造体 flock fl;

    ngx_memzero(&fl, sizeof(struct flock));
    fl.l_type = F_WRLCK;
    fl.l_whence = SEEK_SET;
    // システムが提供するロックメソッドを呼び出す if (fcntl(fd, F_SETLKW, &fl) == -1) {
        ngx_errno を返します。
    }

    0を返します。
}

// 2. 実装のロック解除 // core/ngx_shmtx.c
空所
ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{
    ngx_err_t エラー;

    エラー = ngx_unlock_fd(mtx->fd);

    (エラー == 0)の場合{
        戻る;
    }

    ngx_log_abort(err, ngx_unlock_fd_n " %s が失敗しました", mtx->name);
}
// os/unix/ngx_file.c
ngx_err_t
ngx_unlock_fd(ngx_fd_t fd)
{
    構造体 flock fl;

    ngx_memzero(&fl, sizeof(struct flock));
    fl.l_type = F_UNLCK;
    fl.l_whence = SEEK_SET;

    fcntl(fd, F_SETLK, &fl) == -1 の場合 {
        ngx_errno を返します。
    }

    0を返します。
}

重要な点は、fcntl() システム API の呼び出しであり、他には何もありません。もちろん、傍観者の観点からは、ファイルに対する複数のプロセスの操作が見えるようになるため、プロセス ロックの目的は達成されます。 tryLock と lock には意味上の違いがいくつかあります。試行時には成功したかどうかを示すフラグがいくつか取得されますが、直接ロックする場合はフラグは取得されません。一般的にブロックリクエストが必要

3.3. nginx ロックインスタンスの初期化

おそらく、いくつかの場所では、ロック インスタンスの初期化は単なる変数の割り当てです。しかし、nginx の場合は少し異なります。まず、各ワーカーが同じインスタンスまたは同等のインスタンスを参照できることを確認する必要があります。ワーカーはマスターからフォークされたプロセスであるため、ロックがマスターでインスタンス化されている限り、各ワーカーが同じ値を取得できることが保証されます。それで、それはまさにその通りですか?

// 共有ロックの初期化は ngx マスターで実行され、その後ワーカー プロセスに fork() されます // event/ngx_event.c
静的 ngx_int_t
ngx_event_module_init(ngx_cycle_t *サイクル)
{
    無効***cf;
    u_char *共有;
    size_t サイズ、cl;
    // 共有メモリを定義します ngx_shm_t shm;
    ngx_time_t *tp;
    ngx_core_conf_t *ccf;
    ngx_event_conf_t *ecf;

    cf = ngx_get_conf(cycle->conf_ctx、ngx_events_module);
    ecf = (*cf)[ngx_event_core_module.ctx_index];

    ngx_test_config と ngx_process が NGX_PROCESS_MASTER の場合、
        ngx_log_error(NGX_LOG_NOTICE, サイクル->ログ, 0,
                      "\"%s\" イベント メソッドを使用します", ecf->name);
    }

    ccf は ngx_core_conf_t に格納されます。

    ngx_timer_resolution = ccf->timer_resolution;

#if !(NGX_WIN32)
    {
    ngx_int_t 制限;
    構造体 rlimit rlmt;

    (getrlimit(RLIMIT_NOFILE, &rlmt) == -1)の場合{
        ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
                      "getrlimit(RLIMIT_NOFILE) が失敗しました。無視されました");

    } それ以外 {
        (ecf->connections > (ngx_uint_t) の場合 rlmt.rlim_cur
            && (ccf->rlimit_nofile == NGX_CONF_UNSET
                || ecf->接続 > (ngx_uint_t) ccf->rlimit_nofile))
        {
            制限 = (ccf->rlimit_nofile == NGX_CONF_UNSET) ?
                         (ngx_int_t)rlmt.rlim_cur:ccf->rlimit_nofile;

            ngx_log_error(NGX_LOG_WARN、サイクル->ログ、0、
                          「%ui ワーカー接続数を超えました」
                          "オープンファイルリソース制限: %i",
                          ecf->接続、制限);
        }
    }
    }
#endif /* !(NGX_WIN32) */


    (ccf->master == 0)の場合{
        NGX_OK を返します。
    }

    ngx_accept_mutex_ptr の場合 {
        NGX_OK を返します。
    }


    /* cl はキャッシュラインサイズ以上である必要があります */

    128;

    サイズ = cl /* ngx_accept_mutex */
           + cl /* ngx_connection_counter */
           + cl; /* ngx_temp_number */

#if (NGX_STAT_STUB)

    サイズ += cl /* ngx_stat_accepted */
           + cl /* ngx_stat_handled */
           + cl /* ngx_stat_requests */
           + cl /* ngx_stat_active */
           + cl /* ngx_stat_reading */
           + cl /* ngx_stat_writing */
           + cl; /* ngx_stat_waiting */

#終了

    shm.size = サイズ;
    ngx_str_set(&shm.name, "nginx_shared_zone");
    shm.log = サイクル->ログ;
    // 共有メモリスペースを割り当て、mmap を使用して実装します if (ngx_shm_alloc(&shm) != NGX_OK) {
        NGX_ERROR を返します。
    }

    共有 = shm.addr;

    ngx_accept_mutex_ptr = (ngx_atomic_t *) 共有;
    ngx_accept_mutex.spin = (ngx_uint_t) -1;
    // 共有ファイルまたはメモリ割り当てプロセスロックに基づいて、マルチプロセス制御を実現できます if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared,
                         サイクル->lock_file.data)
        != NGX_OK)
    {
        NGX_ERROR を返します。
    }

    ngx_connection_counter = (ngx_atomic_t *) (共有 + 1 * cl);

    (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);

    ngx_log_debug2(NGX_LOG_DEBUG_EVENT、サイクル->ログ、0、
                   "カウンター: %p, %uA",
                   ngx_connection_counter、*ngx_connection_counter);

    ngx_temp_number = (ngx_atomic_t *) (共有 + 2 * cl);

    tp = ngx_timeofday();

    ngx_random_number = (tp->msec << 16) + ngx_pid;

#if (NGX_STAT_STUB)

    ngx_stat_accepted = (ngx_atomic_t *) (shared + 3 * cl);
    ngx_stat_handled = (ngx_atomic_t *) (共有 + 4 * cl);
    ngx_stat_requests = (ngx_atomic_t *) (共有 + 5 * cl);
    ngx_stat_active = (ngx_atomic_t *) (共有 + 6 * cl);
    ngx_stat_reading = (ngx_atomic_t *) (共有 + 7 * cl);
    ngx_stat_writing = (ngx_atomic_t *) (共有 + 8 * cl);
    ngx_stat_waiting = (ngx_atomic_t *) (共有 + 9 * cl);

#終了

    NGX_OK を返します。
}
// コア/ngx_shmtx.c
// 1. ファイルプロセス共有スペースに基づいて、fdを使用する
ngx_int_t
ngx_shmtx_create(ngx_shmtx_t *mtx、ngx_shmtx_sh_t *addr、u_char *名前)
{
    // マスタープロセスによって作成されるため、プロセスセーフな操作であり、各ワーカーはそれを直接使用できます if (mtx->name) {
        // 作成されている場合、fd は割り当てられており、作成できません。fd を直接共有するだけです。// fd の背後にはファイルインスタンスがあります if (ngx_strcmp(name, mtx->name) == 0) {
            mtx->name = 名前;
            NGX_OK を返します。
        }

        ngx_shmtx_destroy() メソッドは mtx を破棄します。
    }
    // ファイル作成を使用して共有をロックする mtx->fd = ngx_open_file(name, NGX_FILE_RDWR, NGX_FILE_CREATE_OR_OPEN,
                            NGX_FILE_DEFAULT_ACCESS を指定します。

    if (mtx->fd == NGX_INVALID_FILE) {
        ngx_log_error(NGX_LOG_EMERG、ngx_cycle->log、ngx_errno、
                      ngx_open_file_n " \"%s\" が失敗しました", name);
        NGX_ERROR を返します。
    }
    // 一度作成すると削除できます。以降のロック操作は、この fd インスタンスに基づいてのみ実行されます if (ngx_delete_file(name) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT、ngx_cycle->log、ngx_errno、
                      ngx_delete_file_n " \"%s\" が失敗しました", 名前);
    }

    mtx->name = 名前;

    NGX_OK を返します。
}

// 2. 共有メモリに基づく共有ロックの作成 // ngx_shmtx.c
ngx_int_t
ngx_shmtx_create(ngx_shmtx_t *mtx、ngx_shmtx_sh_t *addr、u_char *名前)
{
    mtx->lock = &addr->lock;

    (mtx->spin == (ngx_uint_t) -1)の場合{
        NGX_OK を返します。
    }

    mtx->スピン = 2048;

#if (NGX_HAVE_POSIX_SEM)

    mtx->wait = &addr->wait;

    (sem_init(&mtx->sem、1、0)== -1)の場合{
        ngx_log_error(NGX_LOG_ALERT、ngx_cycle->log、ngx_errno、
                      "sem_init() が失敗しました");
    } それ以外 {
        mtx->セマフォ = 1;
    }

#終了

    NGX_OK を返します。
}
// os/unix/ngx_shmem.c
ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
    shm->addr = (u_char *) mmap(NULL, shm->size,
                                PROT_READ|PROT_WRITE、
                                MAP_ANON|MAP_SHARED、-1、0);

    shm->addr == MAP_FAILEDの場合{
        ngx_log_error(NGX_LOG_ALERT、shm->log、ngx_errno、
                      "mmap(MAP_ANON|MAP_SHARED, %uz) が失敗しました", shm->size);
        NGX_ERROR を返します。
    }

    NGX_OK を返します。
}

fd ベースのロック実装の本質は、その背後にあるファイル システムの実装に基づいています。ファイル システムはプロセスから見えるため、同じ fd の制御は共通ロックの制御になります。

3.4. 共有メモリに基づくロック/ロック解除の実装

いわゆる共有メモリは、実際にはプロセスの範囲外にあるパブリック メモリ領域です (オペレーティング システムによって管理されます)。これは、先ほど見た共有メモリの一部である mmap() の作成です。

// ngx_shmtx.c
ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
    // 共有メモリ領域の値を直接変更します // cas の変更が成功すると、ロックも成功します。
    戻り値 (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}

// shm バージョンのロック解除操作、cas 解析、通知なし
ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{
    (mtx->spin != (ngx_uint_t) -1) の場合 {
        ngx_log_debug0(NGX_LOG_DEBUG_CORE、ngx_cycle->log、0、"shmtx のロック解除");
    }

    ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0) の場合 {
        ngx_shmtx_wakeup() メソッドは mtx をウェイクアップします。
    }
}
//待機中のプロセスに通知する static void
ngx_shmtx_wakeup() メソッドは、mtx をウェイクアップします。
{
#if (NGX_HAVE_POSIX_SEM)
    ngx_atomic_uint_t 待機;

    if (!mtx->セマフォ) {
        戻る;
    }

    のために ( ;; ) {

        待機 = *mtx->待機;

        ngx_atomic_int_t が 0 未満の場合、待機します。
            戻る;
        }

        ngx_atomic_cmp_set(mtx->wait, wait, wait - 1) の場合 {
            壊す;
        }
    }

    ngx_log_debug1(NGX_LOG_DEBUG_CORE、ngx_cycle->log、0、
                   "shmtx %uA を起動"、待機);

    sem_post(&mtx->sem) == -1 の場合 {
        ngx_log_error(NGX_LOG_ALERT、ngx_cycle->log、ngx_errno、
                      "shmtx を起動中に sem_post() が失敗しました");
    }

#終了
}

ロックの共有メモリ バージョンの実装は、基本的に cas によるメモリ変数の設定です。ただ、この指向メモリは共有領域のメモリです。

4. ロックの意味は何ですか?

これまでたくさんのロックを見てきましたが、まだこのロックを通過できません。

ロックとは何でしょうか?実際、ロックは識別ビットです。誰かがこの識別位置を見ると、ロックのように見えて、積極的に操作を停止したり、続行したりするようになります。このフラグは、オブジェクトまたはグローバル値で設定することも、ファイル、redis、zk などのさまざまなメディアを通じて設定することもできます。 違いはありません。重要な問題は、どこに保存するかではなく、このフラグを安全に設定する方法だからです。

ロックを実装するには、CPU レベルでの CAS 操作やアプリケーション レベルでのキューのシリアル アトミック操作など、強力な基礎となる意味の保証が一般的に必要です。 。 。
メモリロック、ファイルロック、高度なロックにはそれぞれ独自の適用シナリオがあります。適切なロックを選択することが評価の鍵となります。この時点で判断できるはずです!

以上がnginxプロセスロックの実装の詳細な説明です。nginxプロセスロックの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Nginx における accept lock の仕組みと実装の詳細な説明
  • proxy_pass を設定した後に Nginx が 404 を返す問題を解決する
  • Nginx SSL証明書設定エラーの解決策
  • Nginx 502 Bad Gateway エラーの原因と解決策
  • nginx の場所に複数の Proxy_pass メソッドがある
  • ファイルをダウンロードするための Nginx 設定サンプルコード
  • リクエストを転送したり、静的リソースファイルにアクセスしたりする複数の場所への nginx の実装
  • nginx 設定ファイルパスとリソースファイルパスを表示する方法

<<:  iframe の src を about:blank に設定した後の詳細

>>:  Navicat for MySQL チュートリアル

推薦する

Linux 環境変数とプロセス アドレス空間の概要

目次Linux 環境変数とプロセスアドレス空間コードを通じて環境変数を取得するプロセスアドレス空間な...

Windows Server 2016 で Flash を有効にする方法

最近、VMware Horizo​​n を導入してテストしましたが、そのコンソールにはデフォルトで ...

Nodejs は readline を使用してコンテンツ入力を促すサンプルコード

目次序文1. batがjsを実行する2. ターミナルにバージョン番号を入力してパッケージ化コマンドを...

クラウドサーバーパゴダパネルの詳細なインストール手順

目次0x01. パゴダパネルをインストールする0x02. サーバーポートを開く0x03. ブラウザを...

デザイン理論:フォントデザインの基礎

<br />言葉は、人間の思考や感情を伝えるために必然的に生み出されるものです。人類の文...

CSS3 はクールな 3D 回転遠近法効果を実現します

CSS3はクールな3D回転パースペクティブを実現します3D アニメーション効果はますます人気が高まっ...

MySQLの左結合を内部結合に素早く変換するプロセス

日々の最適化プロセス中に、奇妙なことに気付きました。同じ SQL にまったく異なる 2 つの実行プラ...

MySql バッチに挿入するときにデータの重複を避ける方法

目次序文1. ignore を挿入2. 重複キーの更新時3. を置き換える要約する序文Mysql は...

Packetdrillの簡潔なユーザーガイド

1. Packetdrillのコンパイルとインストールソースコードリンク https://githu...

MySQL 5.7.17 のインストールと設定方法のグラフィック チュートリアル (Windows)

1. ソフトウェアをダウンロードする1. MySQL の公式サイトにアクセスし、Oracle アカ...

CentOS 8 に htop をインストールする方法のチュートリアル

システムをインタラクティブに監視したい場合は、htop コマンドが最適な選択肢の 1 つです。 ht...

CSS で複数の境界線を実装するためのヒント

1. 複数の国境[1]背景: ボックスシャドウ、アウトライン使用シナリオの多様性を考慮すると、複数の...

Dockerが新しいイメージをロードした後にリポジトリとタグ名が両方ともnoneになる問題を解決する

次のコマンドを使用できます: docker tag [イメージID] [名前]:[バージョン]例えば...

Ubuntuはカーネルモジュールをコンパイルし、その内容はシステムログに反映されます。

目次1.Linuxログインインターフェース2. コードを書く3. Makefileを書く4. コンパ...