php 스 크 립 트 가 실 행 될 때의 시간 초과 메커니즘 에 대한 상세 한 설명

php 개발 을 할 때 max 를 자주 설정 합 니 다.input_time、max_execution_time,스 크 립 트 의 시간 초과 시간 을 제어 합 니 다.하지만 뒤의 원 리 는 생각해 본 적 이 없다.
요 며칠 틈 을 타서 이 문 제 를 연구 해 보 자.
시간 초과 설정
php 의 ini 설정 이 어떻게 작용 하 는 지 는 상투적인 화제 이다.
우선 php.ini 에서 설정 합 니 다.php 가 시 작 될 때(phpmodule_startup 단계)ini 파일 을 읽 고 해석 하려 고 시도 합 니 다.분석 과정 은 쉽게 말 하면 ini 파일 을 분석 하고 합 법 적 인 키 값 을 추출 하여 configuration 에 저장 합 니 다.hash 표.
OK,그리고 phop 은 zend 를 추가 로 호출 합 니 다.startup_extensions 는 각 모듈 을 시작 합 니 다.각 모듈 의 시작 함수 중 REGISTER 이 완 료 됩 니 다.INI_ENTRIES 동작.REGISTER_INI_ENTRIES 는 모듈 에 대응 하 는 일부 설정 을 configurationhash 표 에서 꺼 낸 다음 처리 함 수 를 호출 하여 처리 한 값 을 모듈 의 globals 변 수 를 저장 합 니 다.
max_input_time、max_execution_time 이 두 설정 은 php Core 모듈 에 속 합 니 다.php Core 에 게 REGISTERINI_ENTRIES 는 여전히 php 에서 발생 합 니 다.module_startup 중.php Core 모듈 에 속 하 는 설정 과 exposephp、display_errors、memory_한계 등등...
설명도 아래 와 같다.

---->php_module_startup----------->php_request_startup---->
    |
    |
    |-->REGISTER_INI_ENTRIES
    |
    |
    |-->zend_startup_extensions
    |     |
    |     |-->zm_startup_date
    |     |     |-->REGISTER_INI_ENTRIES
    |     |
    |     |-->zm_startup_json
    |     |     |-->REGISTER_INI_ENTRIES
    |
    |
    |-->do otherthings

서로 다른 설정 에 대해 서 는 REGISTERINI_ENTRIES 는 서로 다른 함 수 를 호출 하여 처리 합 니 다.우리 바로 maxexecution_time 에 대응 하 는 함수:

static PHP_INI_MH(OnUpdateTimeout)
{
  // php       
  if (stage == PHP_INI_STAGE_STARTUP) {
    //         EG(timeout_seconds) 
    EG(timeout_seconds) = atoi(new_value);
    return SUCCESS;
  }
 
  // php      ini set    
  zend_unset_timeout(TSRMLS_C);
  EG(timeout_seconds) = atoi(new_value);
  zend_set_timeout(EG(timeout_seconds), 0);
  return SUCCESS;
}
잠시 상단 만 보 겠 습 니 다.우 리 는 현재 phop 의 시작 단계 에 만 관심 을 가 져 야 하기 때 문 입 니 다.이 함수 의 행 위 는 매우 간단 합 니 다.maxexecution_time EG(timeoutseconds)。
maxinput_time,특별한 처리 함수 가 없습니다.기본적으로 maxinput_time 저장 PG(maxinput_time)。
그래서 REGISTERINI_ENTRIES 가 완성 되 었 습 니 다.발생 한 것 은:
max_execution_time--->EG(timeoutseconds)
max_input_time       ----> PG 저장(maxinput_time)
시간 초과 제어 요청
이제 phop 의 시작 단계 에서 무슨 일이 일 어 났 는 지 알 아 보고 phop 이 실제 요청 을 처리 할 때 시간 초과 관 리 를 어떻게 하 는 지 계속 살 펴 보 겠 습 니 다.
php 에서request_startup 함수 에는 다음 과 같은 코드 가 있 습 니 다.

if (PG(max_input_time) == -1) {
  zend_set_timeout(EG(timeout_seconds), 1);
} else {
  zend_set_timeout(PG(max_input_time), 1);
}
php_request_startup 의 시 기 는 매우 중요 하 다.
cgi 를 예 로 들 면 php 가 CGI 에서 원시 요청 과 일부 CGI 의 환경 변 수 를 받 은 후에 만 phprequest_startup 이 호출 됩 니 다.위의 코드 가 실제 실 행 될 때 요청 을 받 았 기 때문에 SG(requestinfo)준비 완료 상태 이지 만 phop 의$GET,$_POST,$_FILE 등 초 전역 변수 가 생 성 되 지 않 았 습 니 다.
코드 로 이해 하기:
1、사용자 가 maxinput_time 배합-1,또는 설정 이 없 으 면 스 크 립 트 의 수명 주 기 는 EG(timeoutseconds)제약 조건.
2.그렇지 않 으 면 시작 단계 의 시간 초과 통 제 를 요청 하고 PG(maxinput_구속
3、zend_set_timeout 함 수 는 타이머 설정 을 책임 집 니 다.지정 한 시간 이 지나 면 타이머 가 phop 프로 세 스 를 알려 줍 니 다.zend_set_timeout 다음은 구체 적 으로 분석 하 겠 습 니 다.
php_request_startup 이 완성 되면 php 의 실제 실행 단계,즉 phpexecute_script。php 에서execute_script 에서 볼 수 있 습 니 다:

//       
if (PG(max_input_time) != -1) {
#ifdef PHP_WIN32
  zend_unset_timeout(TSRMLS_C); //         
#endif
  zend_set_timeout(INI_INT("max_execution_time"), 0);
}
 
//     
retval = (zend_execute_scripts(ZEND_REQUIRE TSRMLS_CC, NULL, 3, prepend_file_p, primary_file, append_file_p) == SUCCESS);
OK,코드 가 여기까지 실행 된다 면 max 가 발생 하지 않 았 습 니 다.input_time 시간 초과 시 max 다시 지정execution_시간의 초과.
마찬가지 로 zend 호출set_timeout,그리고 maxexecution_time。특히 windows 아래 에 서 는 zend 를 명시 적 으로 호출 해 야 합 니 다.unset_timeout 은 원래 의 타 이 머 를 닫 고 Liux 에 서 는 필요 하지 않 습 니 다.이것 은 두 플랫폼 의 타이머 실현 원리 가 다 르 기 때문에 다음 글 도 상세 하 게 서술 할 것 이다.
마지막 으로 한 장의 그림 으로 시간 초과 제어 절 차 를 표시 하고 왼쪽 의 케이스 는 사용자 가 max 를 설정 했다 는 것 을 나타 낸다.input_time,max 설정execution_time。오른쪽 차 이 는 사용자 가 max 만 설정 한 것 입 니 다.execution_time:

zend_set_timeout
앞에서 언급 한,zendset_timeout 함 수 는 타 이 머 를 설정 하 는 데 사 용 됩 니 다.구체 적 으로 보면 실현:

void zend_set_timeout(long seconds, int reset_signals) /* {{{ */
{
  TSRMLS_FETCH();
 
  //   
  EG(timeout_seconds) = seconds;
 
#ifdef ZEND_WIN32
  if(!seconds) {
    return;
  }
   
  //        
  if (timeout_thread_initialized == 0 && InterlockedIncrement(&timeout_thread_initialized) == 1) {
    /* We start up this process-wide thread here and not in zend_startup(), because if Zend
     * is initialized inside a DllMain(), you're not supposed to start threads from it.
     */
    zend_init_timeout_thread();
  }
   
  //      WM_REGISTER_ZEND_TIMEOUT  
  PostThreadMessage(timeout_thread_id, WM_REGISTER_ZEND_TIMEOUT, (WPARAM) GetCurrentThreadId(),
                                  (LPARAM) seconds);
#else
 
  // linux   
  struct itimerval t_r;    /* timeout requested */
  int signo;
 
  if (seconds) {
    t_r.it_value.tv_sec = seconds;
    t_r.it_value.tv_usec = t_r.it_interval.tv_sec = t_r.it_interval.tv_usec = 0;
 
    //      ,seconds     SIGPROF  
    setitimer(ITIMER_PROF, &t_r, NULL);
  }
  signo = SIGPROF;
 
  if (reset_signals) {
    sigset_t sigset;
 
    //   SIGPROF          zend_timeout
    signal(signo, zend_timeout);
     
    //    
    sigemptyset(&sigset);
    sigaddset(&sigset, signo);
    sigprocmask(SIG_UNBLOCK, &sigset, NULL);
  }
#endif
}

상술 한 실현 은 기본적으로 두 가지 플랫폼 으로 나 눌 수 있다.
linux 먼저 보기:
Liux 의 타이머 가 훨씬 쉬 워 야 합 니 다.setitimer 함 수 를 호출 하면 됩 니 다.그 밖 에 zendset_timeout 은 SIGPROF 신호 의 handler 를 zend 로 설정 했다.timeout。
주의,setitimer 를 호출 할 때 itinterval 은 0 으로 설정 되 어 있 습 니 다.이 타 이 머 는 한 번 만 터치 할 수 있 고 한 번 씩 터치 하지 않 습 니 다.setitimer 는 세 가지 방식 으로 시간 을 계산 할 수 있 으 며,phop 에 서 는 ITIMER 를 사용 합 니 다.PROF 는 사용자 코드 와 커 널 코드 의 실행 시간 을 동시에 계산 합 니 다.시간 이 되면 SIGPROF 신호 가 생 긴 다.
php 프로 세 스 가 SIGPROF 신 호 를 받 으 면 현재 무엇 을 실행 하고 있 든 zend 로 넘 어 갑 니 다.timeout。zend_timeout 이 야 말로 실제 처리 시간 초과 함수 입 니 다.
windows 다시 보기:
우선 키 스 레 드 를 시작 합 니 다.이 스 레 드 는 타이머 설정 과 EG(timedout)변수.
하위 스 레 드 가 생 성 되면 메 인 스 레 드 는 하위 스 레 드 에 메 시 지 를 보 냅 니 다:WMREGISTER_ZEND_TIMEOUT。서브 스 레 드 WM 수신REGISTER_ZEND_TIMEOUT 이후 타이머 가 생 겨 시간 을 잽 니 다.동시에 하위 스 레 드 는 EG(timedout) = 0。중요 해!windows 플랫폼 에서 EG(timedout)시간 초과 여 부 를 1 로 결정 합 니 다.
타이머 가 시간 이 되면 하위 스 레 드 는 WM 을 받 습 니 다.TIMER 메시지,타이머 취소,EG(timedout) = 1。
타 이 머 를 닫 아야 한다 면,하위 스 레 드 는 WM 를 받 습 니 다.UNREGISTER_ZEND_TIMEOUT 소식.타이머 끄 기,EG(timedout)。
관련 코드 는 여전히 명확 하 다.

static LRESULT CALLBACK zend_timeout_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message) {
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
     
    //        ,    
    case WM_REGISTER_ZEND_TIMEOUT:
      /* wParam is the thread id pointer, lParam is the timeout amount in seconds */
      if (lParam == 0) {
        KillTimer(timeout_window, wParam);
      } else {
        SetTimer(timeout_window, wParam, lParam*1000, NULL);
        EG(timed_out) = 0;
      }
      break;
     
    //      
    case WM_UNREGISTER_ZEND_TIMEOUT:
      /* wParam is the thread id pointer */
      KillTimer(timeout_window, wParam);
      break;
     
    //    ,       
    case WM_TIMER: {
        KillTimer(timeout_window, wParam);
        EG(timed_out) = 1;
      }
      break;
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}
위의 설명 에 따 르 면 결국 zend 로 넘 어가 야 합 니 다.타임 아웃 으로 시간 초과 처리.그럼 윈도 우즈 아래 어떻게 zendtimeout 은 요?
window 아래 execute 함수 에서 만(zendvm_execute.h 처음 시작 하 는 곳),zend 호출 을 볼 수 있 습 니 다.timeout:

while (1) {
  int ret;
#ifdef ZEND_WIN32
  if (EG(timed_out)) {  // windows    ,    opcode           zend_timeout
    zend_timeout(0);
  }
#endif
 
  if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {
  ...
  }
}
상기 코드 를 볼 수 있 습 니 다:
windows 에서 opcode 명령 을 실행 할 때마다 시간 초과 판단 을 한다.
주 스 레 드 가 opcode 를 실행 하 는 동시에 하위 스 레 드 가 시간 을 초과 할 수 있 기 때문에 windows 는 주 스 레 드 가 하 던 일 을 멈 추고 zend 로 직접 뛰 어 들 수 있 는 메커니즘 이 없습니다.timeout。그래서 어 쩔 수 없 이 서브 스 레 드 를 이용 하여 먼저 EG(timedout)을 1 로 설정 한 다음 에 메 인 스 레 드 는 현재 opcode 가 실 행 될 때 까지 기다 리 고 다음 opcode 에 들 어가 기 전에 EG(timedout)zend 재 호출timeout。
그래서 정확히 말 하면 windows 의 시간 초과 가 사실은 약간 지연 되 었 다.적어도 하나의 opcode 가 실행 되 는 과정 에서 중단 되 지 않 습 니 다.물론 정상 적 인 상황 에서 단일 opcode 의 실행 시간 은 매우 짧 을 것 이다.그러나 사람 이 시간 이 많이 걸 리 는 함 수 를 만 들 기 쉬 워 서 functioncall 은 오래 기 다 려 야 합 니 다.이때 하위 스 레 드 가 시간 을 초과 했다 고 판단 되면 주 스 레 드 가 이 opcode 를 완성 할 때 까지 긴 기다 림 을 거 쳐 야 zend 를 호출 할 수 있 습 니 다.timeout。
zend_unset_timeout

void zend_unset_timeout(TSRMLS_D) /* {{{ */
{
#ifdef ZEND_WIN32
   
  //     WM_UNREGISTER_ZEND_TIMEOUT        
  if(timeout_thread_initialized) {
    PostThreadMessage(timeout_thread_id, WM_UNREGISTER_ZEND_TIMEOUT, (WPARAM) GetCurrentThreadId(), (LPARAM) 0);
  }
#else
  if (EG(timeout_seconds)) {
    struct itimerval no_timeout;
    no_timeout.it_value.tv_sec = no_timeout.it_value.tv_usec = no_timeout.it_interval.tv_sec = no_timeout.it_interval.tv_usec = 0;
     
    //   0,        
    setitimer(ITIMER_PROF, &no_timeout, NULL);
  }
#endif
}
zend_unset_timeout 역시 두 플랫폼 으로 나 뉘 어 이 루어 집 니 다.
linux 먼저 보기:
Liux 에서 타 이 머 를 끄 는 것 도 간단 합 니 다.struct itimerval 의 4 개 값 을 모두 0 으로 설정 하면 됩 니 다.
windows 다시 보기:
windows 는 독립 된 스 레 드 를 이용 하여 시간 을 잽 니 다.그래서 zendunset_timeout 은 이 스 레 드 에 WM 을 보 냅 니 다.UNREGISTER_ZEND_TIMEOUT 소식.WM_UNREGISTER_ZEND_TIMEOUT 에 대응 하 는 동작 은 KillTimer 를 호출 하여 타 이 머 를 끄 는 것 입 니 다.스 레 드 자체 가 종료 되 지 않 음 을 주의 하 십시오.
앞에서 질문 을 남 겼 습 니 다.phpexecute_script 에서 windows 아래 에 호출 zend 를 표시 합 니 다.unset_timeout 은 타 이 머 를 닫 고 Liux 에 서 는 필요 하지 않 습 니 다.Liux 프로 세 스 에 있어 서 setitimer 타이머 만 존재 하기 때 문 입 니 다.setitimer 를 반복 적 으로 호출 하면 뒤의 타이머 가 앞 을 직접 덮어 쓰 는 것 이다.
zend_timeout

ZEND_API void zend_timeout(int dummy) /* {{{ */
{
  TSRMLS_FETCH();
 
  if (zend_on_timeout) {
    zend_on_timeout(EG(timeout_seconds) TSRMLS_CC);
  }
 
  zend_error(E_ERROR, "Maximum execution time of %d second%s exceeded", EG(timeout_seconds), EG(timeout_seconds) == 1 ? "" : "s");
}
앞에서 말 한 바 와 같이 zendtimeout 은 실제 처리 시간 초과 함수 입 니 다.그것 의 실현 도 매우 간단 하 다.
설정 exiton_timeout,zendon_timeout 은 sapi 호출 을 시도 합 니 다.terminate_process 에서 sapi 프로 세 스 를 닫 습 니 다.exit 가 필요 없다 면on_timeout,바로 zend오류 처리.대부분의 경우,우 리 는 exit 를 설정 하지 않 습 니 다.on_timeout,우리 가 원 하 는 것 은 하나의 요청 이 시간 을 초 과 했 지만 프로 세 스 는 남아 있 고 다음 요청 을 서비스 하 는 것 입 니 다.
zend_error 는 오류 로 그 를 인쇄 하 는 것 외 에 도 longjump 를 이용 하여 boilout 이 지정 한 스 택 프레임 으로 이동 합 니 다.보통 zendend_try 혹은 zendcatch 宏 이 있 는 곳.longjump 에 대해 다른 화 제 를 제기 할 수 있 으 니 본 고 는 구체 적 으로 서술 하지 않 겠 습 니 다.php 에서execute_script 안,zenderror 는 프로그램 을 zend 로 이동 시 킵 니 다.end_try 의 위치 에서 계속 실행 합 니 다.계속 실행 이란 php 를 호출 합 니 다.request_shutdown 등 함수 로 마무리 작업 을 완성 합 니 다.
여기까지 phop 스 크 립 트 의 시간 초과 체 제 는 분명히 말 한 셈 이다.
마지막 으로 phop 커 널 로 의심 되 는 bug 를 보 겠 습 니 다.
windows 아래 maxinput_time bug
기억 해 보 세 요.전에 windows 다음 에 한 군데 만 zend 를 호출 했다 고 했 어 요.timeout 은 execute 함수 에서 모든 opcode 가 실행 되 기 전 입 니 다.
그렇다면 maxinput_time 형식의 시간 초과,하위 스 레 드 가 EG(timedout)1 로 설정 되 어 있 으 며,execute 로 지연 되 어야 시간 초과 처 리 를 할 수 있 습 니 다.모든 것 이 정상 인 것 같다.
문 제 는 우리 가 주 스 레 드 가 execute 까지 실 행 될 때 EG(timedout)임 연 히 1 이다.execute 에 들 어가 기 전에 EG(timedout)이불 스 레 드 를 0 으로 수정 하면 maxinput_time 타 입의 시간 초 과 는 영원히 handle 되 지 않 습 니 다.
왜 EG(timedout)이불 라인 을 0 으로 바 꿀 까요?원인:phpexecute_script 에서 zend 호출 됨set_timeout(INI_INT("max_execution_time"),0)타 이 머 를 설정 합 니 다.
zend_set_timeout 은 하위 스 레 드 에 WM 을 보 냅 니 다.REGISTER_ZEND_TIMEOUT 소식.하위 스 레 드 에서 이 메 시 지 를 받 았 습 니 다.타이머 만 드 는 것 외 에 EG(timedout)=0(위 에서 캡 처 한 zend 참조timeout_WndProc 코드 세 션).스 레 드 가 실 행 된 불확실 성 때문에 주 스 레 드 가 execute 로 실 행 될 때 하위 스 레 드 가 메 시 지 를 받 았 는 지 판단 하고 EG(timedout)은 0 이다.

그림 에서 보 듯 이
execute 의 판단 이 빨 간 선 에 표 시 된 시간 대 에 발생 하면 EG(timedout)1,execute 는 zend 를 호출 합 니 다.timeout 시간 초과 처리.
execute 의 판단 이 파란색 선 에 표 시 된 시간 대 에 발생 하면 EG(timedout)0,max 로 리 셋 됨input_시간 초과 가 철저히 가 려 졌 다.

좋은 웹페이지 즐겨찾기