2010-04-01 7 views
17

setjmplongjmp 기능멀티 태스킹 사용 setjmp는, longjmp를

+1

[토니 핀치의 작은 코 루틴] (http://dotat.at/cgi/git?p=picoro.git;a=blob;f=picoro.c;hb=HEAD). 공동 루틴은 크 누스의 컴퓨팅 기술이며 협업 멀티 태스킹입니다. 뿐만 아니라 Simon Tatham은 훌륭한 설명과 함께 [co-routines web page] (http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html)를 가지고 있습니다. –

+0

또한주의를 기울여야합니다. 'setjmp()'와'longjmp()'는 어셈블러에서 가장 자주/항상 구현되며 OS 컨텍스트 스위치 코드와 유사합니다. 그러나 부동 소수점 *, * SIMD 상태 * 등의 상태를 저장하지 못할 수도 있습니다. 구현 버그인지 표준 문제인지는 모르겠습니다. 그러나이 문제는 실제로 실제로 존재합니다. 절약 할 상태를 알면 컨텍스트 전환 속도를 크게 높일 수 있습니다. –

+0

다른 CPU 상태에 대한 자세한 내용은 ['setjmp()'및 fpmode] (http://www-personal.umich.edu/~williams/archive/computation/setjmp-fpmode.html)를 참조하십시오. –

답변

12

실제로 할 수 있습니다. 그것을 성취하기위한 몇 가지 방법이 있습니다. 어려운 부분은 처음에 다른 스택을 가리키는 jmpbufs를 얻는 것입니다. Longjmp는 setjmp에 의해 생성 된 jmpbuf 인수에 대해서만 정의되었으므로 어셈블리를 사용하거나 정의되지 않은 동작을 사용하지 않고는이 작업을 수행 할 수 없습니다. 사용자 수준 스레드는 본질적으로 이식 가능하지 않으므로 이식성은 실제로 그렇게하지 않는 것에 대한 강력한 논거가 아닙니다.

1 단계 당신은 다른 스레드의 컨텍스트를 저장, 그래서 당신이 원하는 그러나 많은 스레드의 jmpbuf 철골 구조의 큐를 만들 수있는 장소가 필요합니다.

2 단계 이러한 스레드 각각에 대해 스택을 malloc해야합니다.

3 단계 방금 ​​할당 한 메모리 위치에 스택 포인터가있는 jmpbuf 컨텍스트가 필요합니다. 당신은 당신의 머신상의 jmpbuf 구조체를 조사하여 스택 포인터가 어디에 저장되어 있는지 알 수 있습니다. setjmp를 호출 한 다음 스택 포인터가 할당 된 스택 중 하나에 있도록 내용을 수정하십시오. 스택은 일반적으로 커지기 때문에 스택 포인터를 가장 높은 메모리 위치 근처에두고 싶을 것입니다. 기본 C 프로그램을 작성하고 디버거를 사용하여 해체 한 다음 함수에서 돌아 왔을 때 실행되는 명령을 찾으면 오프셋이 무엇인지 알아낼 수 있습니다.예를 들어, x86에서 시스템 V 호출 규칙을 사용하면 % ebp (프레임 포인터)가 튀어 나오고 ret를 호출하여 스택에서 반환 주소를 팝합니다. 그래서 함수에 들어가면 반환 주소와 프레임 포인터를 푸시합니다. 각 푸시는 스택 포인터를 4 바이트만큼 아래로 이동하므로 스택 포인터가 할당 된 영역의 상위 주소 인 -8 바이트에서 시작되도록합니다 (마치 방금 함수를 호출 한 것처럼). 우리는 다음 8 바이트를 채울 것입니다.

당신이 할 수있는 또 다른 일은 스택 포인터를 조작하기 위해 아주 작은 (한 줄짜리) 인라인 어셈블리를 작성한 다음 setjmp를 호출하는 것입니다. 많은 시스템에서 jmpbuf의 포인터가 보안을 위해 맹 글링되므로 쉽게 수정할 수 없기 때문에 실제로 더 이식성이 좋습니다.

아직 시도하지는 않았지만 엄청나게 큰 배열을 선언하고 스택 포인터를 이동하여 의도적으로 스택을 오버플로하여 asm을 피할 수 있습니다.

4 단계 시스템을 안전한 상태로 되돌리려면 종료 스레드가 필요합니다. 이 작업을 수행하지 않고 스레드 중 하나가 반환하면 반환 된 주소보다 할당 된 스택 바로 위에있는 주소로 이동하여 일부 쓰레기 위치 및 가능성이있는 segfault로 이동합니다. 따라서 먼저 안전한 곳으로 돌아 가야합니다. 주 스레드에서 setjmp를 호출하고 jmpbuf를 전역 적으로 액세스 가능한 위치에 저장하여이 작업을 수행하십시오. 인수를 취하지 않고 저장된 전역 jmpbuf로 longjmp를 호출하는 함수를 정의하십시오. 해당 함수의 주소를 가져 와서 반환 주소를위한 공간을 남겨둔 할당 스택에 복사합니다. 프레임 포인터를 비워 둘 수 있습니다. 이제 스레드가 돌아 오면 longjmp를 호출하는 함수로 이동하고 매번 setjmp를 호출 한 주 스레드로 바로 돌아갑니다.

마우스 오른쪽 버튼으로 메인 스레드의 setjmp는 후 5 단계, 당신은 큐 오프 적절한의 jmpbuf를 당겨 거기 가서 longjmp를 호출, 다음으로 이동할 수있는 스레드를 결정 몇 가지 코드를 갖고 싶어. 대기열에 스레드가 남아 있지 않으면 프로그램이 완료됩니다.

6 단계

setjmp는 호출이 다시 큐의 현재 상태를 저장하는 콘텍스트 스위치 함수를 작성하고 다른 큐에서의 jmpbuf는 longjmp.

결론 기본 사항입니다. 스레드가 컨텍스트 스위치를 계속 호출하면 큐는 계속 채워지고 다른 스레드가 실행됩니다. 쓰레드가 돌아 왔을 때 왼쪽 실행이 있다면 하나는 주 쓰레드에 의해 선택되고 남은 것이 없다면 프로세스는 끝난다. 비교적 적은 코드로 매우 기본적인 협업 멀티 태스킹 설정을 할 수 있습니다. 죽은 쓰레드의 스택을 없애기 위해 정리 함수를 구현하는 것과 같은 일을하고 싶을 것이다. 시그널을 사용하여 선점을 구현할 수도 있지만, setjmp가 부동 소수점 레지스터를 저장하지 않기 때문에 훨씬 더 어렵다. 상태 또는 플래그 레지스터는 프로그램이 비동기 적으로 인터럽트 될 때 필요합니다.

+0

setjmp/longjmp의 특정 구현은 원하는대로 동작하도록 jinx 할 수있는 방식으로 작동 할 수 있습니다. 일부 컴파일러는 구현이 특정 형식으로 작동하도록 지정할 수도 있습니다. 이러한 컴파일러를 타겟팅 할 때 문서화되지 않은/정의되지 않은 동작에 의존해야하지만 스택/레지스터 스위치를 수행하는 데 몇 줄의 어셈블리 코드를 사용하는 것이 좋습니다. setjmp/longjmp를 사용하면 어셈블리 코드보다 더 이식성이 떨어지지 만 이식성이 떨어질 수 있습니다. – supercat

+0

그렇게 말한 바에 따르면, 협동 멀티 태스킹에 대해 언급해야 할 것이 많다고 생각합니다. 많은 컴파일러는 외부 어셈블리 언어 모듈에 의해 보존되어야하는 레지스터 (있는 경우)를 명시 적으로 문서화합니다. 선점 형 멀티 태스커는 컴파일러가 사용할 수있는 모든 레지스터를 보존해야합니다. 예를 들어 다음과 같은 경우 문제가 될 수 있습니다. 컴파일러는 멀티 태스킹자가 알지 못하는 하드웨어 곱셈 가속화 단위를 이용하지만 협력 멀티 태스커는 이러한 문제를 피합니다. 그 말은 ... – supercat

+1

... C++ 예외 같은 것들은 구현 방법에 따라 협력적인 멀티 태스킹에서도 훌륭하게 작동하지 않을 수도 있습니다. 실행중인 스레드가 유지 관리하는 스택에 필요한 것이 무엇인지 알기 위해 예외가 구현되는 방법을 연구해야합니다. – supercat

8

그것은 규칙을 약간 굴곡 될 수를 사용하여 멀티 태스킹을 구현하는 방법이 있지만, GNU의 PTH는이 작업을 수행합니다. 가능 합니다만, 학업 성취 증명 연습을 제외하고 직접 시도해서는 안되며, 진지하게 원격으로 이식하기를 원할 경우 pth 구현을 사용하십시오. 읽는 이유를 이해하게 될 것입니다. p 번째 thread 생성 코드

은 (본질적으로는 새로운 스택을 만들기로 OS를 속여 신호 처리기를 사용하여, 다음 longjmp를 거기에서 그리고 주변의 스택을 유지합니다. 그것은 분명, 작동하지만, 지옥 같은 스케치입니다.) 생산에

코드, OS가 makecontext/swapcontext를 지원한다면 대신 그것들을 사용하십시오. CreateFiber/SwitchToFiber를 지원하는 경우 대신이를 사용하십시오. 그리고 coroutine의 가장 강력한 사용 중 하나 인 외부 코드에 의해 호출되는 이벤트 핸들러를 사용하여 제어를 반전하는 제어 코드는 호출 모듈이 재진입 가능해야하므로 안전하지 못하다는 사실을 알고 있어야합니다. 그것을 증명하지 못합니다. 이것이 섬유가 여전히 .NET에서 지원되지 않는 이유입니다 ...

+2

NSPR (Netscape Portable Runtime)은 간단하지만 더 털이 많은 메서드를 사용하여 매크로를 정의하는 것처럼 보입니다. 단지 setjmp를 호출 한 다음 버퍼의 컴퓨터 스택 포인터와 명령 포인터를 변경하기 만합니다. 재미있는 읽기를위한 Google "_MD_INIT_CONTEXT". – user414736

3

이것은 사용자 공간 컨텍스트 스위칭으로 알려진 형식입니다.

특히, setjmp 및 longjmp의 기본 구현을 사용하는 경우 오류가 발생하기 쉽습니다. 이러한 기능의 한 가지 문제는 많은 운영 체제에서 전체 컨텍스트가 아닌 64 비트 레지스터의 하위 집합 만 저장한다는 것입니다. 이것은 종종 충분하지 않습니다. 시스템 라이브러리를 다룰 때 (여기서 나의 경험은 amd64/windows에 대한 커스텀 구현이다.

그렇다면 복잡한 외부 코드베이스 또는 이벤트 처리기로 작업하지 않고 현재 수행중인 작업을 알고있는 경우 (특히 현재 버전을 더 많이 저장하는 어셈블러에서 고유 버전을 작성하는 경우) 문맥 (32 비트 윈도우 나 리눅스를 사용한다면 이것은 필요하지 않을 수도 있습니다. BSD의 몇몇 버전을 사용한다면 거의 확실합니다), 그리고 디스 어셈블리 출력에주의를 기울여 디버깅을하면, 당신이 원하는 것을 얻을 수 있습니다.

1

로서 이미 션 오그 언급되었다 하는 longjmp()는 단지 상향 스택 이동할 수 같은 멀티 태스킹 좋지 수없는 다른 스택 사이 점프. 그걸로 가지 마라. user414736에서 언급 한 바와 같이

, 당신은는 getContext /은 makecontext/swapcontext 기능을 사용할 수 있지만, 이들의 문제들은 사용자 공간에서 완전히없는 것입니다. 실제로 은 컨텍스트 스위칭의 일부로 신호 마스크를 전환하기 때문에 sigprocmask() 시스템 호출을 호출합니다. 이렇게하면 swapcontext()가 longjmp()보다 훨씬 느려지므로 과 천천히 공동 루틴을 원하지 않을 수 있습니다.

내 지식에 에 대한 POSIX 표준 솔루션이 없으므로 다른 에서 내 자신을 컴파일했습니다. 당신은 여기 libtask에서 추출 된 상황에 조작 기능을 찾을 수 있습니다
https://github.com/stsp/dosemu2/tree/devel/src/arch/linux/mcontext
기능은 다음과 같습니다 getmcontext(), setmcontext(), makemcontext()와 swapmcontext을(). 비슷한 이름을 가진 표준 함수와 비슷한 의미를 가지고 있지만, getmcontext()에서 setjmp() 의미를 모방합니다. 당신이 libpcl의 포트를 사용할 수있는 상단에

, 코 루틴 라이브러리 :이
https://github.com/stsp/dosemu2/tree/devel/src/base/misc/libpcl
, 빠른 협력 사용자 공간을 스레딩을 구현하는 것이 가능하다. 리눅스, i386 및 x86_64 아치에서 작동합니다.

관련 문제