사용자 수준 스레드는 어떻게 예약/생성되며 커널 수준 스레드는 어떻게 생성됩니까?
이 질문이 바보같다면 사과드립니다.꽤 오랫동안 온라인으로 답을 찾으려고 했지만 답을 찾지 못해 여기에 문의합니다.스레드를 배우고 있는데 이 링크와 커널 레벨 및 사용자 레벨 스레드에 대한 이 Linux Plumbers Conference 2013 비디오를 살펴보았는데 제가 알기로는 pthread를 사용하면 사용자 공간에 스레드가 생성되고 커널은 이 사실을 알지 못하고 하나의 프로세스로만 보고 내부에 스레드가 얼마나 있는지 알지 못합니다.그런 경우에는
- 커널이 단일 프로세스로 간주하고 스레드를 인식하지 못하기 때문에 프로세스가 발생하는 타임슬라이스 동안 누가 이러한 사용자 스레드의 스케줄링을 결정하며, 스케줄링은 어떻게 수행됩니까?
- pthreads가 사용자 수준 스레드를 생성하는 경우 필요한 경우 커널 수준 또는 OS 스레드를 사용자 공간 프로그램에서 어떻게 생성합니까?
- 위 링크에 따르면 운영체제 커널은 스레드를 생성하고 관리하기 위해 시스템 호출을 제공한다고 나와 있습니다.A도 마찬가지입니다.
clone()
시스템 호출로 커널 레벨 스레드가 생성됩니까 아니면 사용자 레벨 스레드가 생성됩니까?strace
단순한 pthreads 프로그램의 경우 실행 중에 클론 ()를 사용하는 것을 보여주지만, 왜 그것이 사용자 수준 스레드로 간주됩니까?- 커널 수준 스레드를 생성하지 않는 경우 사용자 공간 프로그램에서 커널 스레드를 생성하는 방법은 무엇입니까?
- 링크에 따르면 "스레드에 대한 정보를 유지하려면 각 스레드에 대한 전체 스레드 제어 블록(TCB)이 필요합니다.결과적으로 상당한 오버헤드가 발생하고 커널 복잡도가 증가합니다.". 따라서 커널 수준 스레드에서는 힙만 공유되고 나머지는 모두 스레드에 개별적인 것입니까?
편집:
사용자 수준 스레드 생성에 대해 문의했는데, 여기에 많은 사용자 수준 스레드가 하나의 커널 수준 스레드에 매핑되고 스레드 관리는 스레드 라이브러리에 의해 사용자 공간에서 수행되는 Many to One Model 참조가 있기 때문에 스케줄링 중입니다.pthread를 사용하는 것에 대한 참조만 보고 있었지만 사용자 수준 스레드를 생성하는지 커널 수준 스레드를 생성하는지는 확실하지 않습니다.
이것은 맨 위의 댓글들로 시작됩니다.
지금 읽고 계신 설명서는 일반적이고 리눅스에 특정되지는 않으며 약간 구식입니다.그리고 좀 더 핵심적으로, 그것은 다른 용어를 사용하고 있습니다.그것이 혼란의 원인이라고 생각합니다.자, 읽어보세요...
그것이 "사용자 수준" 스레드라고 부르는 것은 제가 [시대에 뒤떨어진] LWP 스레드라고 부르는 것입니다.커널 수준 스레드라고 하는 것은 리눅스에서 네이티브 스레드라고 하는 것입니다.리눅스 하에서 커널 스레드라고 불리는 것은 완전히 다른 것입니다 [아래 참조].
pthreads를 사용하면 사용자 공간에 스레드가 생성되고, 커널은 이를 인식하지 못하고 단일 프로세스로만 보고 내부에 얼마나 많은 스레드가 있는지 알지 못합니다.
이것은 사용자 공간 스레드가 실행되기 전에 수행된 방법입니다.NPTL
(native posix 스레드 라이브러리).은 SunOS/Solaris이기도 합니다.LWP
자체적으로 다중화하여 스레드를 생성하는 프로세스가 하나 있었습니다.IIRC는 스레드 마스터 프로세스(또는 기타 프로세스)라고 불렸습니다.커널은 이를 인식하지 못했습니다.커널이 아직 스레드를 이해하거나 지원하지 않았습니다.
그러나 이러한 "경량" 스레드는 사용자 공간 기반 스레드 마스터(일명 "경량 프로세스 스케줄러")에서 코드별로 전환되었기 때문에 컨텍스트 전환이 매우 느렸습니다.
또한 "네이티브" 스레드가 등장하기 전에는 10개의 프로세스가 있을 수 있습니다.각 프로세스는 CPU의 10%를 차지합니다. 만약 프로세스 중 하나가 10개의 스레드를 가진 LWP일 경우, 이 스레드들은 10%를 공유해야 하므로 CPU의 1%만 차지합니다.
이 모든 것은 커널의 스케줄러가 인식하는 "네이티브" 스레드로 대체되었습니다.이 전환은 10-15년 전에 이루어졌습니다.
위의 예를 사용하면 20개의 스레드/프로세스가 있으며 각 스레드는 CPU의 5%를 차지합니다. 또한 컨텍스트 전환 속도도 훨씬 빠릅니다.
네이티브 스레드 하에서 LWP 시스템을 갖는 것은 여전히 가능하지만, 지금은 그것이 필수적인 것이라기 보다는 디자인 선택입니다.
또한 LWP는 각 스레드가 "공조"하면 잘 작동합니다.즉, 각 스레드 루프는 주기적으로 "컨텍스트 스위치" 함수에 명시적인 호출을 합니다.다른 LWP가 실행될 수 있도록 자발적으로 프로세스 슬롯을 포기하고 있습니다.
, NPTL에서 사전 glibc
또한 LWP 스레드를 [강제적으로] 선점해야 했습니다 (즉, 타임슬라이싱을 구현).정확한 메커니즘은 기억나지 않지만, 여기 예가 있습니다.스레드 마스터는 알람을 설정하고 잠을 자고 일어나서 활성 스레드에 신호를 보내야 했습니다.신호 처리기는 컨텍스트 전환에 영향을 미칩니다.이것은 지저분하고, 추하고, 다소 신뢰할 수 없었습니다.
요아힘은 다음과 같이 언급했습니다.
pthread_create
다를(를) .
그것을 커널 스레드라고 부르기에는 [기술적으로] 틀렸습니다.pthread_create
네이티브 스레드를 만듭니다.이는 사용자 공간에서 실행되며 프로세스와 동일한 기반의 타임슬라이스에 대해 경쟁합니다.일단 생성되면 스레드와 프로세스 간에 차이가 거의 없습니다.
가장 중요한 차이점은 프로세스에 고유한 주소 공간이 있다는 것입니다.그러나 스레드는 동일한 스레드 그룹에 속한 다른 프로세스/스레드와 주소 공간을 공유하는 프로세스입니다.
커널 수준 스레드를 생성하지 않는 경우 사용자 공간 프로그램에서 커널 스레드를 생성하는 방법은 무엇입니까?
커널 스레드는 사용자 공간 스레드, NPTL, 네이티브 또는 다른 방식이 아닙니다.이들은 커널을 통해 생성됩니다.kernel_thread
기능.커널의 일부로 실행되며 사용자 공간 프로그램/프로세스/스레드와 연관되지 않습니다.그들은 기계에 완전히 접근할 수 있습니다.장치, MMU 등커널 스레드는 가장 높은 권한 수준인 ring 0에서 실행됩니다.또한 사용자 프로세스/스레드의 주소 공간이 아닌 커널의 주소 공간에서 실행됩니다.
사용자 공간 프로그램/프로세스가 커널 스레드를 만들지 않을 수 있습니다.기억하세요, 그것은 다음을 사용하여 네이티브 스레드를 만듭니다.pthread_create
합니다.clone
그렇게 하기 위해 syscall.
실은 커널에서도 일을 할 때 유용합니다.그래서 다양한 스레드에서 코드의 일부를 실행합니다.다음 작업을 수행하면 이러한 스레드를 볼 수 있습니다.ps ax
봐 , 야,kthreadd, ksoftirqd, kworker, rcu_sched, rcu_bh, watchdog, migration
, 등. 커널 스레드이며 프로그램/프로세스가 아닙니다.
업데이트:
커널은 사용자 스레드에 대해 알지 못한다고 말씀하셨습니다.
위에서 언급한 바와 같이, 두 개의 "시대"가 있다는 것을 기억하세요.
(1) 커널이 스레드 지원을 받기 전(2004년경?이것은 스레드 마스터(여기서는 LWP 스케줄러를 호출하겠습니다)를 사용했습니다.그 알맹이는 방금 그 알맹이를 가지고 있었습니다.fork
(2) 그 이후의 모든 커널은 스레드를 이해합니다.스레드 마스터는 없지만, 우리는.pthreads
고.clone
사이콜지금이다,fork
다로 됩니다.clone
.clone
다와 합니다.fork
몇 가지 주장이 필요합니다.히 a.flags
child_stack
논쟁.
이에 대한 자세한 내용은 아래 ...
그렇다면 사용자 수준 스레드가 개별 스택을 갖는 것은 어떻게 가능합니까?
프로세서 스택에는 "마법"이라는 것이 없습니다.[주로] 논의는 x86으로 제한하지만 스택 레지스터가 없는 아키텍처(예: 1970년대 IBM 메인프레임)에도 적용할 수 있습니다.
는 x86입니다%rsp
은 .x86을 있습니다.push
그리고.pop
지침들. 우리는 이를 통해 저장 및 복원 작업을 수행합니다.push %rcx
그리고 [나중에]pop %rcx
.
하지만, x86이 다음을 가지고 있지 않다고 가정해 보겠습니다.%rsp
아니면push/pop
지시?아직도 짐을 좀 둘 수 있을까요?네, 관례상 그렇습니다.우리는 [프로그래머로서] 동의합니다 (예를 들어).%rbx
는 스택 포인터입니다.
''%rcx
[AT&T 어셈블러 사용]:
subq $8,%rbx
movq %rcx,0(%rbx)
, "" .%rcx
다음과 같습니다.
movq 0(%rbx),%rcx
addq $8,%rbx
좀 더 쉽게 하기 위해서 C "사이비 코드"로 바꿀 겁니다.다음은 의사 코드의 위 푸시/팝입니다.
// push %ecx
%rbx -= 8;
0(%rbx) = %ecx;
// pop %ecx
%ecx = 0(%rbx);
%rbx += 8;
LWP 를 .malloc
이 그런 다음 이 포인터를 스레드당 구조로 저장한 다음 하위 LWP를 시작해야 했습니다.좀가 (를 들어다를 있다고 . 예를 들어, 우리가 (예를 들어)LWP_create
다와 .pthread_create
:
typedef void * (*LWP_func)(void *);
// per-thread control
typedef struct tsk tsk_t;
struct tsk {
tsk_t *tsk_next; //
tsk_t *tsk_prev; //
void *tsk_stack; // stack base
u64 tsk_regsave[16];
};
// list of tasks
typedef struct tsklist tsklist_t;
struct tsklist {
tsk_t *tsk_next; //
tsk_t *tsk_prev; //
};
tsklist_t tsklist; // list of tasks
tsk_t *tskcur; // current thread
// LWP_switch -- switch from one task to another
void
LWP_switch(tsk_t *to)
{
// NOTE: we use (i.e.) burn register values as we do our work. in a real
// implementation, we'd have to push/pop these in a special way. so, just
// pretend that we do that ...
// save all registers into tskcur->tsk_regsave
tskcur->tsk_regsave[RAX] = %rax;
// ...
tskcur = to;
// restore most registers from tskcur->tsk_regsave
%rax = tskcur->tsk_regsave[RAX];
// ...
// set stack pointer to new task's stack
%rsp = tskcur->tsk_regsave[RSP];
// set resume address for task
push(%rsp,tskcur->tsk_regsave[RIP]);
// issue "ret" instruction
ret();
}
// LWP_create -- start a new LWP
tsk_t *
LWP_create(LWP_func start_routine,void *arg)
{
tsk_t *tsknew;
// get per-thread struct for new task
tsknew = calloc(1,sizeof(tsk_t));
append_to_tsklist(tsknew);
// get new task's stack
tsknew->tsk_stack = malloc(0x100000)
tsknew->tsk_regsave[RSP] = tsknew->tsk_stack;
// give task its argument
tsknew->tsk_regsave[RDI] = arg;
// switch to new task
LWP_switch(tsknew);
return tsknew;
}
// LWP_destroy -- destroy an LWP
void
LWP_destroy(tsk_t *tsk)
{
// free the task's stack
free(tsk->tsk_stack);
remove_from_tsklist(tsk);
// free per-thread struct for dead task
free(tsk);
}
수 있는 는 다음을 pthread_create
그리고.clone
, 새 스레드 스택을 만들어야 합니다.커널이 새 스레드에 대한 스택을 생성/할당하지 않습니다.clone
은 syscall은합니다.child_stack
논쟁.따라서,pthread_create
.clone
:
// pthread_create -- start a new native thread
tsk_t *
pthread_create(LWP_func start_routine,void *arg)
{
tsk_t *tsknew;
// get per-thread struct for new task
tsknew = calloc(1,sizeof(tsk_t));
append_to_tsklist(tsknew);
// get new task's stack
tsknew->tsk_stack = malloc(0x100000)
// start up thread
clone(start_routine,tsknew->tsk_stack,CLONE_THREAD,arg);
return tsknew;
}
// pthread_join -- destroy an LWP
void
pthread_join(tsk_t *tsk)
{
// wait for thread to die ...
// free the task's stack
free(tsk->tsk_stack);
remove_from_tsklist(tsk);
// free per-thread struct for dead task
free(tsk);
}
프로세스 또는 메인 스레드만이 커널에 의해 초기 스택이 할당되며, 대개는 메모리 주소가 높습니다.따라서 프로세스가 일반적으로 스레드를 사용하지 않는 경우 미리 할당된 스택만 사용합니다.
그러나 LWP 또는 네이티브 스레드 중 하나로 스레드가 생성된 경우 시작 프로세스/스레드는 다음과 같이 제안된 스레드에 대한 영역을 미리 할당해야 합니다.malloc
. 사이드 노트:사용.malloc
방법이지만 라는 큰 수 있습니다.char stack_area[MAXTASK][0x100000];
그렇게 하고 싶다면 말입니다
[어떤 유형의] 스레드를 사용하지 않는 일반 프로그램이 있다면, 주어진 기본 스택을 "오버라이드"하고 싶을 수도 있습니다.
그 프로세스는 사용하기로 결정할 수 있습니다.malloc
그리고 위의 어셈블러 트릭은 매우 큰 재귀적 함수를 수행하는 경우 훨씬 더 큰 스택을 생성합니다.
여기서 답변 보기:메모리 사용 시 사용자 정의 스택과 내장 스택의 차이점은 무엇입니까?
사용자 수준 스레드는 일반적으로 하나 또는 다른 형태의 코루틴입니다.커널 개입 없이 사용자 모드에서 실행 흐름 간에 컨텍스트를 전환합니다.from kernel POV, 는 모두 하나의 스레드입니다.스레드가 실제로 수행하는 작업은 사용자 모드에서 제어되며, 사용자 모드는 실행(즉, 코루틴)의 논리적 흐름을 일시 중단, 전환, 재개할 수 있습니다.이 모든 것은 실제 스레드에 대해 예약된 퀀타 동안 발생합니다.커널은 실제 쓰레드(커널 쓰레드)를 무의식적으로 방해할 수 있고, 또 다른 쓰레드에 프로세서의 제어권을 부여할 수 있습니다.
사용자 모드 코루틴에는 협력 멀티태스킹이 필요합니다.사용자 모드 스레드는 주기적으로 다른 사용자 모드 스레드에 대한 제어를 포기해야 합니다(기본적으로 커널 스레드가 아무것도 인식하지 못한 상태에서 실행이 컨텍스트를 새 사용자 모드 스레드로 변경함).보통 코드가 커널이 제어권을 해제하고자 할 때 훨씬 더 잘 알 수 있습니다.잘못 코딩된 코루틴은 통제권을 빼앗아 다른 코루틴들을 굶길 수 있습니다.
사용된 과거 구현이지만 현재는 사용되지 않습니다.Boost.context는 이를 대체할 수 있지만 완전히 휴대할 수는 없습니다.
Boost.Context는 하나의 스레드에서 일종의 협력 멀티태스킹을 제공하는 기본 라이브러리입니다.스택(로컬 변수 포함) 및 스택 포인터, 모든 레지스터 및 CPU 플래그, 명령 포인터를 포함하여 현재 스레드에서 현재 실행 상태의 추상화를 제공함으로써 execution_context는 응용 프로그램의 실행 경로의 특정 지점을 나타냅니다.
당연히 Boost.coroutine은 Boost.context를 기반으로 합니다.
Windows에서 파이버를 제공했습니다.Net 런타임에 작업 및 비동기/대기가 있습니다.
리눅스 스레드는 소위 "일대일" 모델을 따르는데, 각 스레드는 실제로 커널에서 별개의 프로세스입니다.커널 스케줄러는 정규 프로세스를 예약하는 것과 마찬가지로 스레드의 예약을 처리합니다.스레드는 새 프로세스가 상위의 메모리 공간, 파일 설명자 및 신호 처리기를 공유할 수 있도록 fork()를 일반화한 Linux clone() 시스템 호출로 생성됩니다.
출처 - Xavier Leroy (LinuxThreads를 만든 사람) http://pauillac.inria.fr/ ~xleroy/linuxthreads/faq.html#K
언급URL : https://stackoverflow.com/questions/39185134/how-are-user-level-threads-scheduled-created-and-how-are-kernel-level-threads-c
'programing' 카테고리의 다른 글
sequelize.js - 수동으로 mysql 패키지를 설치해야 합니다. (0) | 2023.10.09 |
---|---|
스프링부트-스타터-부모는 폼 파일에서 정확히 무엇을 합니까? (0) | 2023.10.09 |
jquery에서 특정 속성 값을 가진 모든 요소 찾기 (0) | 2023.10.09 |
jQuery는 모든 텍스트 필드의 값의 합을 계산합니다. (0) | 2023.10.09 |
control.registerOnChange is not function" 오류의 원인 (0) | 2023.10.09 |