페이지 캐시에 100 % 페이징 된 파일이 다른 프로세스에 의해 수정 될 때 발생하는 상황


14

페이지 캐시 페이지가 수정되면 더티로 표시되고 다시 쓰기가 필요하다는 것을 알고 있습니다.

시나리오 : 실행 파일 인 / apps / EXE 파일은 페이지 캐시에 완전히 페이징되고 (모든 페이지는 캐시 / 메모리에 있음) 프로세스 P에 의해 실행됩니다.

연속 릴리스는 / apps / EXE를 새로운 실행 파일로 대체합니다.

가정 1 : 프로세스 P (및 이전 실행 파일을 참조하는 파일 디스크립터가있는 다른 사람)는 문제없이 메모리 / apps / EXE에서 이전을 계속 사용하고 해당 경로를 실행하려고하는 새로운 프로세스는 새로운 실행 파일

가정 2 : 파일의 모든 페이지가 메모리에 매핑되지 않는 경우 교체 된 파일의 페이지를 요구하는 페이지 오류가 발생할 때까지 문제가 없으며 아마도 segfault가 발생한다고 가정합니다.

질문 1 : 파일의 모든 페이지를 vmtouch와 같은 것으로 막 으면 시나리오가 전혀 바뀌지 않습니까?

질문 2 : / apps / EXE가 원격 NFS에 있다면 차이가 있습니까? (그렇지 않다고 가정)

두 가지 가정을 수정하거나 확인하고 두 가지 질문에 대답하십시오.

이것이 일종의 3.10.0-957.el7 커널을 가진 CentOS 7.6 박스라고 가정하자

업데이트 : 더 생각해 보면이 시나리오가 다른 더티 페이지 시나리오와 다르지 않은지 궁금합니다.

새 바이너리를 작성하는 프로세스는 모두 페이지로 묶여 있기 때문에 모든 캐시 페이지를 읽고 가져오고 모든 페이지가 더티로 표시된다고 가정합니다. 그들이 잠겨 있으면 참조 카운트가 0이 된 후 코어 메모리를 차지하는 쓸모없는 페이지 일뿐입니다.

현재 실행중인 프로그램이 끝나면 다른 모든 것이 새로운 바이너리를 사용할 것이라고 생각합니다. 그것이 모두 맞다고 가정하면 파일의 일부만 페이징 될 때만 흥미로울 것 같습니다.


명시 적으로 표현하기 위해 파일을 교체하는 것은 큰 문제가되지 않습니다 (응용 프로그램에서 파일을 다시 열 었는지 여부와 응용 프로그램이 수정 된 내용에 반응하는 방식에 따라 다름). mmaped 디렉토리 항목이있는 zip 파일이 변경 될 때 Java 세계에서). 그러나 플랫폼에 따라 다르므로 mmaped 영역에서 변경 사항을 볼 수 있습니다.
eckes

답변:


12

연속 릴리스는 / apps / EXE를 새로운 실행 파일로 대체합니다.

이것이 중요한 부분입니다.

새 파일을 해제하는 방법은 새 파일 (예를 만드는 것입니다 /apps/EXE.tmp.20190907080000,) 내용을 작성, 권한과 소유권을 설정하고 마지막으로 최종 이름을 보내고 (2)의 이름을 /apps/EXE이전 파일을 교체.

결과적으로 새 파일은 새로운 inode 번호를 갖게됩니다 (즉, 다른 파일임을 의미합니다).

그리고 이전 파일에는 고유 한 inode 번호가있었습니다. 실제로 는 파일 이름이 더 이상 가리 키지 않더라도 실제로 는 주변에 있습니다 (또는 더 이상 해당 inode를 가리키는 파일 이름이 없습니다).

따라서 여기서 핵심은 Linux에서 "파일"에 대해 이야기 할 때 파일이 열리면 inode가 파일에 대한 참조이므로 "inodes"에 대해 가장 자주 이야기하는 것입니다.

가정 1 : 프로세스 P (및 이전 실행 파일을 참조하는 파일 설명자가있는 다른 사람)는 문제없이 메모리 / apps / EXE에서 이전을 계속 사용하고 해당 경로를 실행하려고하는 새로운 프로세스는 새로운 실행 파일

옳은.

가정 2 : 파일의 모든 페이지가 메모리에 매핑되지 않는 경우 교체 된 파일의 페이지를 요구하는 페이지 오류가 발생할 때까지 문제가 없으며 아마도 segfault가 발생한다고 가정합니다.

맞지 않습니다. 이전 inode는 여전히 존재하므로 이전 바이너리를 사용하는 프로세스의 페이지 결함은 여전히 ​​디스크에서 해당 페이지를 찾을 수 있습니다.

이전 바이너리를 실행하는 프로세스 에 대한 /proc/${pid}/exe심볼릭 링크 (또는 동등한 lsof출력)를 보면이 효과 /app/EXE (deleted)가 나타납니다.이 이름은 더 이상 존재하지 않지만 inode가 여전히 있음 을 나타냅니다.

바이너리가 사용하는 디스크 공간은 프로세스가 종료 된 후에 만 ​​해제된다는 것을 알 수 있습니다 (아이 노드가 열려있는 유일한 프로세스라고 가정합니다). df프로세스 종료 전과 후에 출력을 확인 하면 크기가 줄어 듭니다 당신이 더 이상 주변에 없다고 생각했던 오래된 바이너리의.

BTW, 이것은 바이너리뿐만 아니라 열린 파일을 가진 것입니다. 프로세스에서 파일을 열고 파일을 제거하면 해당 프로세스가 파일을 닫을 때까지 파일이 디스크에 보관됩니다 (또는 죽습니다). 하드 링크가 디스크의 inode를 가리키는 이름 수에 대한 카운터를 유지하는 방법과 마찬가지로, Linux 커널에서 파일 시스템 드라이버 는 메모리 에있는 해당 inode 대한 참조 수를 카운터로 유지 하고 실행중인 시스템의 모든 참조가 릴리스 된 후에 만 ​​디스크에서 inode를 해제합니다.

질문 1 : 파일의 모든 페이지를 vmtouch와 같은 것으로 막 으면 시나리오가 변경됩니다.

이 질문은 페이지를 잠그지 않으면 segfault가 발생한다는 잘못된 가정 2에 근거합니다. 그렇지 않습니다.

질문 2 : / apps / EXE가 원격 NFS에 있다면 차이가 있습니까? (그렇지 않다고 가정)

그것은 똑같은 방식으로 대부분의 시간 동안 작동하도록 의도 되었지만 NFS에는 "gotchas"가 있습니다.

때로는 NFS에서 여전히 열려있는 파일을 삭제하는 결과물을 볼 수 있습니다 (해당 디렉토리에 숨겨진 파일로 표시됨).

또한 NFS 서버를 재부팅 할 때 장치 번호가 "재편성"되지 않도록 NFS 내보내기에 장치 번호를 할당 할 수있는 방법이 있습니다.

그러나 주요 아이디어는 동일합니다. NFS 클라이언트 드라이버는 여전히 inode를 사용하며 inode가 계속 참조되는 동안 서버에 파일을 보관하려고합니다.


1
oldname 파일의 참조 횟수가 0이 될 때까지 rename (2)가 차단됩니까?
Gregg Leventhal

2
아니요, rename (2)는 차단되지 않습니다. 오래된 inode는 잠재적으로 매우 오랫동안 유지됩니다.
filbranden

1
실행중인 파일에 쓸 수없는 이유에 대한 @mosvy의 답변을 참조하십시오 (ETXTBSY를 얻음). 링크를 해제하고 새로 만들면 이름이 바뀌는 것과 같은 효과가 있습니다. 새로운 inode가 생깁니다. (파일 이름이 존재하지 않는 순간이 없기 때문에 이름을 바꾸는 것이 더 좋습니다. 새로운 inode를 가리 키도록 이름을 대체하는 원자 작업입니다.)
filbranden

4
@GreggLeventhal : "내가 사용하는 연속 릴리스 프로세스에 대해 어떤 가정을하고 있다면 임시 파일을 사용하게 될 것입니까?" – 유닉스가 존재하는 한, 그것이 유일하고 현명한 방법이기 때문입니다. rename파일 시스템이나 장치 경계를 넘지 않는다고 가정 할 때 원 자성으로 보장되는 유일한 파일 및 파일 시스템 작업이므로 "임시 파일을 만든 다음 rename"이 파일 업데이트 표준 패턴입니다. 예를 들어 유닉스의 모든 텍스트 편집기도 사용합니다.
Jörg W Mittag

1
@ grahamj42 : renamePOSIX의 일부입니다. 물론 ISO C (현재 초안의 섹션 7.21.4.2)와 관련하여 포함되어 있지만 거기에 있습니다.
Jörg W Mittag

7

가정 2 : 파일의 모든 페이지가 메모리에 매핑되지 않는 경우 교체 된 파일의 페이지를 요구하는 페이지 오류가 발생할 때까지 문제가 없으며 아마도 segfault가 발생한다고 가정합니다.

커널이 현재 실행중인 파일 내부의 대체물을 쓰기 위해 열 수 없기 때문에 그렇지 않습니다. 이러한 조치는 ETXTBSY[1] 과 함께 실패합니다 .

cp /bin/sleep sleep; ./sleep 3600 & echo none > ./sleep
[9] 5332
bash: ./sleep: Text file busy

dpkg 등이 바이너리를 업데이트 할 때는 덮어 쓰지는 않지만 rename(2)단순히 디렉토리 항목을 완전히 다른 파일을 가리 키도록 사용 하며 이전 파일에 대한 핸들 또는 열린 핸들이 여전히있는 프로세스는 문제없이 계속 사용합니다. .

[1] 이러한 보호는 "텍스트"(라이브 코드 / 실행 파일) : 공유 라이브러리, 자바 클래스 등으로 간주 될 수있는 다른 파일로 확장되지 않습니다. 다른 프로세스에 의해 매핑하는 동안 이러한 파일을 수정하는 것입니다 그것은 충돌이 발생합니다. 리눅스에서 동적 링커는 MAP_DENYWRITE플래그를 충실하게 전달 mmap(2)하지만 실수하지는 않습니다. 아무것도 영향을 미치지 않습니다.


1
dpkg 시나리오에서 / apps / EXE의 dentry가 새 바이너리의 inode를 참조하도록 이름 바꾸기가 완료되는 시점은 언제입니까? 이전에 대한 언급이 더 이상 없을 때? 어떻게 작동합니까?
Gregg Leventhal

2
rename(2)원자이다; 완료 되 자마자 dir 항목은 새 파일을 참조합니다. 그 시점에서 여전히 이전 파일을 사용하고 있던 프로세스는 기존 매핑 또는 파일에 대한 열린 핸들을 통해서만 파일에 액세스 할 수 있습니다 (이것은 via 이외의 다른 액세스 할 수없는 고아 dentry를 참조 할 수 있음 /proc/PID/fd).
mosvy

1
ETXTBSY에 대한 언급으로 인해 utcc.utoronto.ca/~cks/space/blog/unix/WhyTextFileBusyError가 발생 했기 때문에 귀하의 답변이 가장 마음에 듭니다 .
Gregg Leventhal

4

연속 릴리스 프로세스가을 통해 파일의 적절한 원자 교체를 수행한다고 가정하면 filbranden의 대답은 정확합니다 rename. 그렇지 않은 경우 파일을 제자리에서 수정하지만 상황이 다릅니다. 그러나 당신의 정신 모델은 여전히 ​​잘못입니다.

페이지 캐시는 정식 버전 이고 수정 된 버전 이기 때문에 디스크에서 항목이 수정되고 페이지 캐시와 일치하지 않을 가능성이 없습니다 . 파일에 대한 모든 쓰기는 페이지 캐시를 통해 이루어집니다. 이미 존재하는 경우 기존 페이지가 수정됩니다. 아직 없으면 부분 페이지를 수정하려고하면 전체 페이지가 캐시되고 이미 캐시 된 것처럼 수정됩니다. 전체 페이지 또는 그 이상에 걸친 쓰기는 페이징하는 읽기 단계를 최적화 할 수 있습니다 (그리고 거의 확실하게 할 수 있습니다). .

(*) 나는 약간 거짓말을했다. NFS 및 기타 원격 파일 시스템의 경우 둘 이상이있을 수 있으며 일반적으로 사용되는 마운트 및 서버 측 옵션에 따라 원 자성 및 쓰기 시맨틱 순서를 올바르게 구현하지 않습니다. 그렇기 때문에 우리 중 많은 사람들이 기본적으로 깨진 것으로 생각하고 사용과 동시에 쓰기가 필요한 상황에서는 사용을 거부합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.