Go 스케줄러 소스 코드 9 번째 시나리오 분석 : 운영 체제 스레드 및 스레드 스케줄링

다음 콘텐츠는 https://mp.weixin.qq.com/s/OvGlI5VvvRdMRuJegNrOMg 에서 재현됩니다. 

Awa는 원래 프로그램을 작성하는 것을 좋아합니다. 장  소스 여행  2019-04-25

이 기사는 "Go Scheduler 소스 코드 시나리오 분석"시리즈의 첫 번째 장에 대한 예비 지식의 아홉 번째 섹션입니다.

goroutine의 스케줄러를 심도있게 이해하려면 go의 스케줄링 시스템이 운영 체제 스레드를 기반으로하므로 운영 체제 스레드에 대한 일반적인 이해가 필요합니다. 이에 대해 간단히 소개하겠습니다.

스레드에 대한 정확하고 이해하기 쉬운 정의를 제공하기는 어렵습니다. 특히 멀티 스레드 프로그래밍에 노출 된 적이없는 독자에게는 스레드가 무엇인지 이해하기 어려울 수 있으므로 정의를 제쳐두고 C에서 직접 시작 언어 프로그램은 스레드가 무엇인지 시각적으로보기 시작합니다. C 언어를 사용하는 이유는 일반적으로 C 언어에서 pthread 스레드 라이브러리를 사용하고 스레드 라이브러리를 사용하여 생성 된 사용자 모드 스레드는 실제로 작업자와 동일한 Linux 운영 체제 커널에서 지원하는 스레드이기 때문입니다. go 언어의 스레드.,이 스레드는 Linux 커널에 의해 관리되고 예약되며, go 언어는 운영 체제 스레드 위에 고 루틴을 만들어 2 단계 스레드 모델을 구현합니다.

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

#define N (1000 * 1000 * 1000) 

volatile int g = 0; 

void * start (void * arg) 
{ 
        int i; 

        for (i = 0; i <N; i ++) { 
                g ++; 
        } 

        반환 NULL; 
} 

int main (int argc, char * argv []) 
{ 
        pthread_t tid; 

        // 使用 pthread_create 函数 创建 一个 新 线程 执行 start 函数
        pthread_create (& tid, NULL, start, NULL); 

        for (;;) { 
                usleep (1000 * 100 * 5); 
                printf ( "루프 g : % d \ n", g); 
                if (g == N) {  
                        break;
                }
        } 

        pthread_join (tid, NULL); // 자식 스레드가 실행을 마칠 때까지 기다립니다 

        return 0; 
}

프로그램이 실행 된 후 두 개의 스레드가 있습니다. 하나는 운영 체제가 프로그램을로드 할 때 생성되는 메인 스레드이고 다른 하나는 pthread_create를 호출하는 메인 스레드가 생성하는 시작 서브 스레드입니다. g가 10 억이 될 때까지 500 밀리 초 안에 전역 변수 g의 값을 인쇄하고 시작 스레드가 시작된 후 g를 1 씩 증가시키는 10 억주기를 실행하기 시작합니다.이 두 스레드 시스템에서 동시에 실행되고 운영 체제가 책임을집니다. 일정을 예약하면 스레드가 실행될시기를 정확히 예측할 수 없습니다.

운영 체제의 스레드 스케줄링과 관련하여 명확히해야 할 두 가지 문제가 있습니다.

  • 스케줄링은 언제 발생합니까?

  • 일정을 잡는 동안 무엇을하나요?

먼저 첫 번째 질문을 살펴 보겠습니다. 운영 체제는 언제 스케줄링을 시작합니까? 일반적으로 운영 체제는 스케줄링을 시작하기 전에 CPU에 대한 제어권을 얻어야합니다. 그렇다면 커널이 제어 할 수 있도록 사용자 프로그램이 CPU에서 실행 중일 때 CPU가 운영 체제 코드를 어떻게 실행할 수 있습니까? 일반적으로 두 가지 경우에 운영 체제 코드를 실행하기 위해 사용자 프로그램 코드가 실행됩니다.

  1. 사용자 프로그램은 시스템 호출을 사용하여 운영 체제 커널로 들어갑니다.

  2. 하드웨어 인터럽트. 하드웨어 인터럽트 핸들러는 운영 체제에서 제공하므로 하드웨어가 중단되면 운영 체제 코드가 실행됩니다. 하드웨어 인터럽트에는 운영 체제가 선점 스케줄링을 시작하는 기반이되는 특히 중요한 클럭 인터럽트가 있습니다.

운영 체제는 운영 체제 코드를 실행하는 경로의 특정 지점에서 스케줄링이 필요한지 여부를 확인하므로 이에 따라 운영 체제의 스레드 스케줄링도 위의 두 상황에서 발생합니다.

작성자의 단일 코어 컴퓨터에서 프로그램을 실행 한 결과를 살펴 보겠습니다.

bobo @ ubuntu : ~ / study / c $ gcc thread.c -o thread -lpthread 
bobo @ ubuntu : ~ / study / c $ ./thread 
loop g : 98938361 
루프 g : 198264794 
루프 g : 297862478 
루프 g : 396750048 
루프 g : 489684941 
루프 g : 584723988 
루프 g : 679293257 
루프 g : 777715939 
루프 g : 876083765 
루프 g : 974378774 
루프 g : 1000000000

출력에서 메인 스레드와 시작 스레드가 차례로 실행되고 있음을 알 수 있습니다. 이는 운영 체제가 이들을 스케줄링 한 결과입니다. 운영 체제는 잠시 동안 실행할 시작 스레드를 스케줄링 한 다음 기본 스레드를 스케줄링합니다. 다시 실행합니다.

프로그램의 출력에서 ​​주 스레드가 시작 스레드 동안 실행 중이고 시작 스레드에 의해 실행되는 시작 함수에는 시스템 호출이 전혀없고이 프로그램이 a에서 실행 중이기 때문에 선점 스케줄링의 그림을 볼 수 있습니다. 단일 코어 시스템 : 다른 CPU는 메인 스레드를 실행하므로 중단 중에 발생하는 선제 적 스케줄링이 없으면 운영 체제가 CPU를 제어 할 수 없으며 스레드 스케줄링이 발생할 수 없습니다.

다음으로 스레드를 예약 할 때 운영 체제가 수행하는 작업을 살펴 ​​보겠습니다.

위에서 언급했듯이 운영 체제는 동일한 CPU에서 실행되도록 다른 스레드를 예약하고 각 스레드는 실행할 때 CPU 레지스터를 사용하지만 각 CPU에는 레지스터 집합이 하나만 있으므로 운영 체제는 스레드 B를 예약합니다. CPU에서는 먼저 메모리에서 방금 실행 중이던 스레드 A가 사용한 모든 레지스터 값을 저장 한 다음 메모리에 저장된 스레드 B의 모든 레지스터 값을 다시 CPU 레지스터에 넣어야합니다. 방법은 스레드 B를 이전에 실행 중이던 상태로 복원 한 다음 실행할 수 있습니다.

일반 레지스터 외에도 스레드 스케줄링 중에 운영 체제가 저장 및 복원해야하는 레지스터에는 명령어 포인터 레지스터 rip, 스택 상단 레지스터 rsp 및 스택과 관련된 스택 기본 주소 레지스터 rbp가 포함됩니다. 스레드에 의해 실행되어야하는 다음 명령어., 2 개의 스택 레지스터는 스레드가 실행될 때 사용해야하는 스택 메모리를 결정합니다. 따라서 CPU 레지스터의 값을 복원하는 것은 CPU가 실행할 다음 명령어를 변경하는 것과 동시에 함수 호출 스택을 전환하는 것과 동일하므로 스케줄러의 관점에서 스레드에는 최소한 다음이 포함됩니다. 세 가지 중요한 내용 :

  • 일반 레지스터 세트의 값

  • 실행할 다음 명령어의 주소

  • 스택

따라서 운영 체제에 의한 스레드 스케줄링은 커널 스케줄러에 의해 서로 다른 스레드가 사용하는 레지스터 및 스택의 전환으로 이해 될 수 있습니다.

마지막으로, 운영 체제 스레드에 대한 간단하고 부정확 한 정의가 있습니다. 운영 체제 스레드는 커널에 의해 예약되고 고유 한 레지스터 값 및 스택 실행 흐름이 있습니다.


마지막으로,이 기사가 도움이되었다고 생각되면 기사의 오른쪽 하단에있는 "Looking"을 클릭하거나 친구들에게 전달하도록 도와주세요. 대단히 감사합니다!

영상

추천

출처blog.csdn.net/pyf09/article/details/115238594