programing

왜 malloc()와 printf()가 비재입국으로 언급됩니까?

elecom 2023. 6. 22. 21:26
반응형

왜 malloc()와 printf()가 비재입국으로 언급됩니까?

에서 우리는 UNIX를 알고 .malloc()비재귀 함수(시스템 호출)입니다.왜 그런 것일까요?

유하게사,printf()또한 재입국하지 않는 것으로 알려져 있습니다. 왜죠?

재입국의 정의는 알고 있지만, 왜 이러한 기능에 적용되는지 알고 싶었습니다.무엇이 그들이 재입국을 보장받는 것을 막습니까?

malloc그리고.printf일반적으로 전역 구조를 사용하고 내부적으로 잠금 기반 동기화를 사용합니다.그게 그들이 재입국하지 않는 이유입니다.

malloc함수는 스레드 세이프 또는 스레드 스레딩일 수 있습니다. 다.

  1. 말은글힙로운에며, 두가다있수있다을의 두 가지 합니다.malloc (두 번째 malloc 를 가져오기 전에 .(청크의 주소를 가져오기 전에 두 번째 malloc 호출이 발생해야 하지만 청크가 사용 불가능으로 표시되지 않습니다.)이는 다음의 사후 조건을 위반합니다.malloc따라서 이 구현은 다시 중요하지 않습니다.

  2. 효과를 방식으로 이한효방기위스구세이현프드레해러를 합니다.malloc잠금 기반 동기화를 사용합니다.과 같은 발생할 수 .

    malloc();            //initial call
      lock(memory_lock); //acquire lock inside malloc implementation
    signal_handler();    //interrupt and process signal
    malloc();            //call malloc() inside signal handler
      lock(memory_lock); //try to acquire lock in malloc implementation
      // DEADLOCK!  We wait for release of memory_lock, but 
      // it won't be released because the original malloc call is interrupted
    

    이 상황은 다음과 같은 경우에 발생하지 않습니다.malloc단순히 다른 스레드에서 호출됩니다.실제로 재진입 개념은 스레드 안전을 넘어 호출 중 하나가 종료되지 않더라도 기능이 제대로 작동해야 합니다.그것이 기본적으로 잠금 기능이 있는 어떤 기능도 재진입하지 않는 이유입니다.

printf글로벌 데이터에서도 기능이 작동합니다.모든 출력 스트림은 일반적으로 전송되는 리소스 데이터에 연결된 글로벌 버퍼를 사용합니다(터미널 또는 파일용 버퍼).인쇄 프로세스는 일반적으로 데이터를 버퍼에 복사하고 버퍼를 플러시하는 순서입니다..malloc그습니. 그서래.printf또한 비재입국자입니다.

재진입의 의미를 이해해 보겠습니다.재입장 기능은 이전 호출이 완료되기 전에 호출될 수 있습니다.다음과 같은 경우에 발생할 수 있습니다.

  • 함수를 실행하는 동안 발생한 신호에 대해 신호 핸들러(또는 일반적으로 유닉스 일부 인터럽트 핸들러보다 더 일반적으로)에서 호출됩니다.
  • 함수를 재귀적으로 부릅니다.

malloc은 사용 가능한 메모리 블록을 추적하는 여러 글로벌 데이터 구조를 관리하기 때문에 다시 중요하지 않습니다.

printf는 FILE* stout의 내용과 같은 글로벌 변수를 수정하기 때문에 재입력되지 않습니다.

여기에는 적어도 세 가지 개념이 있는데, 모두 구어체로 융합되어 있기 때문에 혼란스러웠을 수도 있습니다.

  • 안전한
  • 임계 구간
  • 재상용의

가장 쉬운 것을 먼저 선택합니다. 및 둘 다 스레드 세이프입니다.2011년부터 표준 C에서 스레드 안전성이 보장되었으며, 2001년부터 POSIX에서 스레드 안전성이 보장되었습니다.이는 다음 프로그램이 충돌하거나 잘못된 동작을 보이지 않도록 보장된다는 것을 의미합니다.

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

void *printme(void *msg) {
  while (1)
    printf("%s\r", (char*)msg);
}

int main() {
  pthread_t thr;
  pthread_create(&thr, NULL, printme, "hello");        
  pthread_create(&thr, NULL, printme, "goodbye");        
  pthread_join(thr, NULL);
}

스레드 세이프가 아닌 함수의 예는 다음과 같습니다.strtok 당신이 면시하화에 전화를 한다면,strtok의 서로 되지 않은입니다. " " " " " " " 이므로 정의되지 않은 동작입니다. 왜냐하면strtok내부적으로 정적 버퍼를 사용하여 상태를 추적합니다.glibc 추가strtok_r문제를 , 을 (으로, 되지 않았기 에) 이그를기해, 은그고 C11니다습은같을것했가추다니입문때로않위기았이다지리발여되러명왜나냐으서기하름면른그로으고선제리택적치문고)▁the▁to▁(그11▁and▁cbut▁fix▁thing▁(▁as,▁sameally11▁a▁under▁this,▁and▁not▁name다이▁problem▁added▁option니▁different)입은문때▁because▁invented않다기았▁here제기를지문되리그strtok_s.

좋아요, 하지만 그렇지 않아요.printf그것의 산출물을 구축하기 위해 글로벌 자원들도 사용합니까?사실,의 스레드에서 동시에 stdout으로 인쇄하는 것은 무엇을 의미합니까?다음 주제로 넘어가겠습니다.분명히 그것을 사용하는 모든 프로그램에서 중요부분이 될 입니다.한 번에 하나의 실행 스레드만 중요 섹션 내에 있을 수 있습니다.

POSIX 시스템에서는 POSIX를 지원하는 "POSIX"를 사용하여 이를 할 수 .printf에전화시다하작로의▁▁call에 전화하는 것으로 합니다.flockfile(stdout)마지막으로 전화를 드립니다.funlockfile(stdout)이것은 기본적으로 stdout과 관련된 글로벌 뮤텍스를 복용하는 것과 같습니다.

의 구별되는 러나그, 각다른각.FILE프로그램에서 자체 뮤텍스를 가질 수 있습니다.이것은 하나의 스레드가 호출할 수 있음을 의미합니다.fprintf(f1,...)가 두번스호중동시에인에 대한 fprintf(f2,...)여기에는 레이스 조건이 없습니다. (libc가 실제로 두 통화를 병렬로 실행하는지 여부는 QoI 문제입니다.저는 사실 glibc가 무엇을 하는지 모릅니다.)

유하게사,malloc현대 시스템은 시스템의 각 스레드에 대해 하나의 메모리 풀을 유지할 수 있을 만큼 충분히 똑똑하기 때문에 모든 N 스레드가 하나의 풀을 놓고 싸우는 것이 아닙니다.(그sbrk일 수 , 호출은 중요한 섹션입니다.malloc에 거의 시간을 보내지 않습니다.sbrk또는mmap또는 요즘 멋진 아이들이 사용하는 것이 무엇이든.)

좋아요, 그럼 재진입이 실제로 무엇을 의미할까요?기본적으로 이 함수는 안전하게 재귀적으로 호출될 수 있음을 의미합니다. 두 번째 호출이 실행되는 동안 현재 호출은 "보류"되고 첫 번째 호출은 여전히 "끊긴 곳에서 픽업"할 수 있습니다. (기술적으로 이것은 재귀적 호출 때문이 아닐 수 있습니다: 첫 번째 호출은 스레드 A에서 중단될 수 있습니다.)두 번째 호출을 하는 스레드 B에 의한 중간.하지만 그 시나리오는 스레드 안전의 특별한 경우일 뿐이므로 이 단락에서는 잊어버릴 수 있습니다.)

둘 다 아니다.printf도 아니다malloc리프 함수이기 때문에 단일 스레드에 의해 재귀적으로 호출될 수 있습니다(재귀적 호출을 할 수 있는 사용자 제어 코드를 호출하거나 스스로 호출하지 않습니다).또한 위에서 살펴본 바와 같이 2001년 이후 (잠금 장치를 사용하여) *멀티*스레드 재진입 호출에 대해 스레드 세이프 기능을 제공하고 있습니다.

그래서, 누가 당신에게 그 말을 했든printf그리고.malloc재진입이 불가능하다는 것은 잘못된 것입니다. 두 가지 모두 프로그램에서 중요한 섹션, 즉 한 번에 하나의 스레드만 통과할 수 있는 병목 현상일 가능성이 있다는 것을 의미합니다.


현학적 참고: glibc는 다음과 같은 확장을 제공합니다.printf재지정 자체를 포함하여 임의 사용자 코드를 호출할 수 있습니다.이것은 모든 배열에서 완벽하게 안전합니다 - 적어도 스레드 안전에 관한 한. (분명히 이것은 완전히 미친 형식 문자열 취약성에 대한 문을 엽니다.)두 가지 변형이 있습니다.register_printf_function제정신이지만으로는 "및 (문서되비됨공제식는로 "권장으화")register_printf_specifier(문서화되지 않은 파라미터 하나와 사용자 대면 문서가 전혀 없다는 점을 제외하면 거의 동일함).저는 그들 중 어느 것도 추천하지 않고, 단지 흥미로운 측면으로 여기에 언급하고 싶습니다.

#include <stdio.h>
#include <printf.h>  // glibc extension

int widget(FILE *fp, const struct printf_info *info, const void *const *args) {
  static int count = 5;
  int w = *((const int *) args[0]);
  printf("boo!");  // direct recursive call
  return fprintf(fp, --count ? "<%W>" : "<%d>", w);  // indirect recursive call
}
int widget_arginfo(const struct printf_info *info, size_t n, int *argtypes) {
  argtypes[0] = PA_INT;
  return 1;
}
int main() {
  register_printf_function('W', widget, widget_arginfo);
  printf("|%W|\n", 42);
}

대부분의 경우 출력을 쓰기 시작할 수 없기 때문에 printf에 대한 다른 호출이 여전히 자체 인쇄 중입니다.메모리 할당 및 할당 해제도 마찬가지입니다.

둘 다 힙 메모리 구조와 콘솔이라는 글로벌 리소스와 함께 작동하기 때문입니다.

편집: 힙은 링크된 종류의 목록 구조일 뿐입니다.malloc또는free여러 개의 스레드를 동시에 쓰기 액세스 권한으로 설정하면 일관성이 손상됩니다.

EDIT2: 다른 세부사항: 뮤텍스를 사용하여 기본적으로 재입장할 수 있습니다.그러나 이러한 접근 방식은 비용이 많이 들고 MT 환경에서 항상 사용된다는 보장도 없습니다.

따라서 두 가지 해결책이 있습니다. 하나는 재입력 기능이고 하나는 재입력 기능이고 다른 하나는 사용자에게 뮤텍스 부분을 맡기는 것입니다.그들은 두 번째를 선택했습니다.

또한 이러한 기능의 원래 버전이 비귀속적이었기 때문에 호환성을 위해 에 선언되었기 때문일 수 있습니다.

두 개의 별도 스레드에서 malloc를 호출하려고 하면(C 표준에서 보장하지 않는 스레드 안전 버전이 없는 경우), 두 스레드에 대해 하나의 힙만 있기 때문에 나쁜 일이 발생합니다.printf도 마찬가지입니다. 동작이 정의되지 않았습니다.그것이 그들을 실제로 비입국자로 만드는 이유입니다.

언급URL : https://stackoverflow.com/questions/3941271/why-are-malloc-and-printf-said-as-non-reentrant

반응형