다중 코어에서 컴파일할 때 make가 중단되는 원인은 무엇입니까?

다중 코어에서 컴파일할 때 make가 중단되는 원인은 무엇입니까?

어제는 컴파일을 하려고 했는데요뿌리소스의 패키지. 6코어 몬스터 머신에서 컴파일하고 있었기 때문에 계속해서 사용하기로 결정했습니다 make -j 6. 처음에는 컴파일이 원활하고 매우 빨랐으나 어느 시점에서 make하나의 코어에서 100% CPU를 사용하면 중단되었습니다.

구글링 좀 하다가 찾아낸건데이것ROOT 게시판에 글을 올려주세요. 이 컴퓨터를 제가 직접 만들다보니 방열판을 제대로 설치하지 않아서 CPU가 과열되는 등의 걱정이 듭니다. 안타깝게도 제가 일하는 곳에는 넣어둘 수 있는 냉장고가 없습니다. ;-)

패키지를 설치 lm-sensors하고 make -j 6다시 실행했는데, 이번에는 CPU 온도를 모니터링했습니다. 온도는 매우 높지만(거의 60°C) 결코 높거나 임계 온도를 초과하지 않습니다.

나는 running 을 시도했지만 make -j 4컴파일 중 어느 시점에서 make이번에는 다른 위치에서 다시 멈췄습니다.

마지막으로 이를 컴파일하고 실행했는데 make매우 잘 작동했습니다. 내 질문은: 왜 멈추나요? 두 개의 서로 다른 위치에서 멈추기 때문에 일종의 경쟁 조건 때문일 것이라고 추측하지만 make제공되는 옵션을 고려하면 모든 것을 올바른 순서로 가져올 만큼 똑똑해야 한다고 생각했습니다 -j.

답변1

이 정확한 질문에 대한 답은 없지만 무슨 일이 일어날 수 있는지에 대한 힌트를 드릴 수는 있습니다. Makefile에 종속성이 없습니다.

예:

target: a.bytecode b.bytecode
    link a.bytecode b.bytecode -o target

a.bytecode: a.source
    compile a.source -o a.bytecode

b.bytecode: b.source
    compile b.source a.bytecode -o a.bytecode

호출하면 make target모든 것이 올바르게 컴파일됩니다. a.source컴파일이 먼저 수행됩니다(임의이지만 결정론적으로). 그런 다음 b.source컴파일하십시오.

그러나 make -j2 targetcompile명령이 동시에 실행되는 경우. 실제로 Makefile의 종속성이 손상되었음을 알 수 있습니다. 두 번째 컴파일에서는 a.bytecode컴파일되었다고 가정하지만 종속성에는 표시되지 않습니다. 그래서 오류가 발생할 가능성이 높습니다. 올바른 종속성 줄은 다음 b.bytecode과 같아야 합니다.

b.bytecode: b.source a.bytecode

질문으로 돌아가서, 운이 좋지 않으면 종속성 누락으로 인해 명령이 100% CPU 루프에서 중단될 수 있습니다. 이것이 아마도 여기서 일어나고 있는 일일 것입니다. 순차 빌드에서는 누락된 종속성을 밝힐 수 없었지만 병렬 빌드에서는 이미 이를 드러냈습니다.

답변2

이것은 매우 오래된 질문이라는 것을 알고 있지만 여전히 검색 결과 상단에 표시되므로 내 해결책은 다음과 같습니다.

GNU make에는 make와 해당 재귀 하위 프로세스가 지정된 코어 수를 초과하지 않도록 보장하는 작업 서버 메커니즘이 있습니다. http://make.mad-scientist.net/papers/jobserver-implementation/

모든 프로세스가 공유하는 파이프에 의존합니다. 추가 하위 프로세스를 포크하려는 각 프로세스는 먼저 파이프에서 토큰을 사용한 다음 완료되면 폐기해야 합니다. 하위 프로세스가 소비한 토큰을 반환하지 않으면 최상위 make는 영원히 정지되어 반환될 때까지 기다립니다.

https://bugzilla.redhat.com/show_bug.cgi?id=654822

"sed"가 GNU sed가 아닌 Solaris 시스템에서 GNU make를 사용하여 binutils를 빌드할 때 이 오류가 발생했습니다. 이 문제는 sed==gsed가 시스템 sed보다 우선하도록 PATH를 수정하여 해결되었습니다. 그러나 sed가 파이프에서 토큰을 소비하는 이유를 모르겠습니다.

답변3

make교착상태를 만든 것 같습니다. 를 사용하면 ps -ef다음 프로세스가 원인인 것 같습니다.

루트 695 615 1 22:18 ? 00:00:00 prebuild-j32 만들기
루트 2127 695 20 22:18 ? 00:00:04 make -f Makefile.prenobuild

각 하위 프로세스가 수행하는 작업을 확인하면 하위 프로세스가 파일 설명자 4에 쓰고 있는 반면 상위 프로세스는 모든 하위 프로세스가 종료되기를 기다리고 있음을 알 수 있습니다.

root@ltzj2-6hl3t-b98zz:/# strace -p 2127
strace: 연결 프로세스 2127
쓰기(4,"+",1
root@ltzj2-6hl3t-b98zz:/# strace -p 695
strace: 프로세스 695 연결
{{잠깐 4(-1, }}

파일 설명자 4는 파이프입니다.

root@ltzj2-6hl3t-b98zz:/# ls -la /proc/2127/fd/4
l-wx------ 1 루트 루트 64 Sep 3 22:22 /proc/2127/fd/4 -> 'pipe:[1393418985]'

파이프는 상위 프로세스와 하위 프로세스 사이에만 있습니다.

root@ltzj2-6hl3t-b98zz:/# lsof 1393418985 |
695 루트 3r FIFO 0,12 0t0 1393418985 파이프 만들기
695 루트 4w FIFO 0,12 0t0 1393418985 파이프 만들기
2127 루트 3r FIFO 0,12 0t0 1393418985 파이프 만들기
2127 루트 4w FIFO 0,12 0t0 1393418985 파이프 만들기

따라서 2127이 695에 다시 파이프에 출력을 추가하려고 시도하는 것처럼 보이지만 695는 보류 상태 wait4()이므로 파이프를 지우지 않습니다.

cat을 사용하여 셸에서 파이프를 지우면 빌드가 예상대로 재개되고 완료됩니다.

root@ltzj2-6hl3t-b98zz:/# 고양이 /proc/695/fd/3
++++++++++++++++++++++++++++++++

빌드가 차단 해제되고 계속 실행됩니다...


나의 초기 이해는 틀렸지만, 더 조사한 후에 결국 다음과 같은 Linux 커널 결함을 발견했습니다.

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=46c4c9d1beb7f5b4cec4dd90e7728720583ee348

일시정지 방법에 대한 정확한 설명은 다음과 같습니다.https://lore.kernel.org/lkml/1628086770.5rn8p04n6j.none@localhost/.

gnu make 소스 코드에 다음 해결 방법을 적용하여 커널 패치를 기다리는 이 문제를 해결할 수 있습니다.

---a/src/posixos.c 2020-01-02 23:11:27.000000000-0800
+++ b/src/posixos.c 2021-09-18 09:12:02.786563319 -0700
@@ -179,8 +179,52 @@
 jobserver_release(int is_fatal)
 {
   정수 r;
- EINTRLOOP(r, 쓰기(job_fds[1], &token, 1));
- if(r != 1)
+ 정수 n;
+ 문자 b[32];
+
+ /* 다중 make 하위 프로세스로 인한 교착 상태를 피하기 위해 비차단 쓰기를 사용합니다.
+ * 또한 작업을 해제합니다. */
+ set_blocking(job_fds[1], 0);
+ memset(b,token,sizeof(b));
+ n = 1;
+ 동시에 ( n > 0 )
+ {
+ r = 쓰기(job_fds[1], b, n);
+ /* 시스템 호출이 중단되었습니다. 다시 시도하십시오*/
+ if ( r == -1 )
+ {
+ if (오류 번호 == EINTR)
+ 계속;
+
+ /* 이 프로세스와 다른 프로세스가 모두 파이프에 쓰려고 했기 때문에 여기까지 왔습니다.
+ * 정확히 같은 시간이며 파이프라인에는 1페이지만 포함됩니다. 우리는 다른 사람을 잃었어
+ * 프로세스가 승리합니다(파이프에 쓰기). 먼저 이 조건을 재설정할 수 있습니다.
+ * 파이프에서 읽습니다. 물론 이는 추가로 반환해야 함을 의미합니다.
+ * 토큰. */
+ if ( errno == EWOULDBLOCK || errno == EAGAIN )
+ {
+ if ( jobserver_acquire(0) )
+ {
+n++;
+ /* 아마도 불가능에 가까울 수도 있습니다... */
+ if (n > 32)
+ 인터럽트;
+ 계속;
+ }
+ }
+ }
+ if ( r == 0 ) /* 0바이트를 썼으나 오류가 없었으니 다시 시도해 보세요 */
+ 계속;
+ if ( r > 0 )
+ {
+ n -= r;
+ 계속;
+ }
+ break; /* 다른 모든 오류는 중단됩니다. */
+ }
+ set_blocking(job_fds[1], 1);
+
+ if(n != 0)
     {
       만약 (치명적이라면)
         pfatal_with_name (_("작업 서버에 쓰기"));

답변4

make시스템은 괜찮을 수 있지만 이는 빌드를 병렬로 실행할 때 발생하는 경쟁 조건일 수 있습니다.

시스템에 문제가 발생하면 병렬 빌드를 수행할 때뿐만 아니라 다른 상황에서도 시스템이 중단되거나 충돌하게 됩니다.

관련 정보