먼저 커널의 {resource} 유형 구조를 설명합니다.
//모든 리소스는 이를 통해 구현됩니다.
typedef struct _zend_rsrc_list_entry
{
void *ptr;
int type;
int refcount;
}zend_rsrc_list_entry
실제로는 파일 핸들과 같이 스칼라 값으로 표현되는 데이터는 C에 대한 포인터일 뿐입니다.
#include
int main(void)
{
FILE *fd
fd = fopen("/home/jdoe/.plan", "r")
fclose(fd);
return 0;
}
C 언어의 stdio 파일 디스크립터(파일 디스크립터)는 실제로 열려 있는 각 파일과 일치하는 변수입니다. , 프로그램이 하드웨어와 상호 작용할 때 사용됩니다. fopen 함수를 사용하여 파일을 열고 핸들을 얻을 수 있습니다. 그런 다음 feof(), fread(), fwrite(), fclose()와 같은 함수에 핸들을 전달하기만 하면 후속 작업을 수행할 수 있습니다. 파일에. 이 데이터는 C 언어의 스칼라 데이터로 직접 표현될 수 없기 때문에 사용자가 PHP 언어에서도 사용할 수 있도록 어떻게 캡슐화할 수 있습니까? 이것이 PHP에서 리소스 유형 변수의 역할입니다! 따라서 zval 구조를 통해 캡슐화됩니다. 리소스 유형의 구현은 복잡하지 않습니다. 해당 값은 실제로 정수 값에 불과하며 커널은 최종 필요한 데이터를 찾기 위해 리소스 풀과 유사한 장소로 이동합니다.
리소스 유형 변수 사용
리소스 유형 변수도 구현에 있어서 유형이 구분됩니다! 파일 핸들, mysql 링크 등 다양한 유형의 리소스를 구별하려면 리소스에 서로 다른 분류 이름을 지정해야 합니다. 먼저 이 카테고리를 프로그램에 추가해야 합니다. 이 단계는 MINIT에서 수행할 수 있습니다:
#define PHP_SAMPLE_DESCRIPTOR_RES_NAME "Shanzhai 파일 설명자"
static int le_sample_descriptor;
ZEND_MINIT_FUNCTION(sample)
{
le_sample_descriptor = zend_register_list_destruct ors_ex(NULL, NULL, PHP _SAMPLE_DESCRIPTOR_RES_NAME ,module_number);
return SUCCESS;
//추가 정보
#defineregister_list_destructors(ld, pld) zend_register_list_destructors((void (*)(void *)) ld, ( void (*)(void *))pld, module_number);
ZEND_API int zend_register_list_destructors(void (*ld)(void *), void (*pld)(void *), int module_number)
ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number)
다음으로 확장된 module_entry에 정의된 MINIT 단계 함수를 추가합니다. 원래 "NULL, /* MINIT */"만 변경하면 됩니다. 한 줄 바꾸기:
ZEND_MINIT(sample), /* MINIT */
ZEND_MINIT_FUNCTION() 매크로는 1장에서 이미 설명한 MINIT 단계의 기능을 정의하는 데 사용됩니다. 자세한 내용은 12장과 3장에서 설명합니다. 이 시점에서 알아야 할 중요한 것은 확장이 처음 로드될 때와 요청이 수신되기 전에 MINIT 메서드가 한 번 실행된다는 것입니다. 여기서는 소멸자 함수(NULL 값)를 등록하는 데 사용했으며, 이 값은 곧 변경됩니다. 앞으로는 고유한 정수 ID로 알려질 리소스 유형입니다. zend_register_list_destructors_ex() 함수를 보면 zend_register_list_destructors() 함수도 있는지 확실히 궁금할 것입니다. 예, 매개변수에 이전보다 적은 리소스 범주 이름이 포함되는 함수가 실제로 있습니다. 그렇다면 둘 사이의 차이점은 무엇입니까?
eaco $re_1;//resource(4) 유형(copycat 파일 핸들)
echo $re_2
//resource(4) 유형(알 수 없음)
리소스 생성
위 커널에 새로운 리소스 유형을 등록했습니다. 다음 단계는 이 유형의 리소스 변수를 생성하는 것입니다. 다음으로, 이제 Sample_open이라고 불리는 fopen 함수를 다시 구현해 보겠습니다.
PHP_FUNCTION(sample_fopen)
{
FILE *fp;
char *filename, *mode; mode_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",&filename, &filename_len,&mode, &mode_len) == FAILURE)
{
RETURN_NULL()
}
if ( !filename_len || !mode_len)
{ php_error_docref(NULL TSRMLS_CC, E_WARNING,"잘못된 파일 이름 또는 모드 길이")
RETURN_FALSE;fp = fopen(파일 이름, 모드);
if (!fp)
{
php_error_docref(NULL TSRMLS_CC, E_WARNING,"%s 모드를 사용하여 %s을(를) 열 수 없습니다.",filename, mode)
~
ZEND_REGISTER_RESOURCE(return_value,fp,le_sample_descriptor);
}
이전 장의 지식을 읽었다면 코드의 마지막 줄이 무엇을 하는지 짐작할 수 있을 것입니다. le_sample_descriptor 유형의 새 리소스를 생성합니다. 또한 이 리소스를 리소스를 저장하는 HashTable에 추가하고 이 리소스의 해당 디지털 키를 return_value에 할당합니다.
리소스는 파일 핸들에만 국한되지 않습니다. 메모리 조각을 적용하고 이에 대한 포인터를 리소스로 사용할 수 있습니다. 따라서 리소스는 모든 유형의 데이터에 해당할 수 있습니다.
자원 파괴
세상의 모든 것에는 부침이 있고 삶과 죽음이 있습니다. 이제 자원을 파괴하는 방법에 대해 논의할 시간입니다. 가장 간단한 방법은 fclose를 모방하여 특정 {리소스: 구체적으로 PHP의 리소스 유형 변수로 표시되는 값을 참조}의 릴리스를 수행하는 Sample_close() 함수를 작성하는 것입니다.
그런데 사용자 측 스크립트가 unset() 함수를 통해 특정 리소스 유형의 변수를 해제하면 어떻게 될까요? 그들은 그 값이 궁극적으로 FILE 포인터에 해당한다는 것을 모르기 때문에 fclose() 함수를 사용하여 이를 해제할 수 없습니다. 이 FILE 핸들은 PHP 프로그램이 중단되고 OS에 의해 재활용될 때까지 메모리에 남아 있을 가능성이 높습니다. . 그러나 일반적인 웹 환경에서는 우리 서버가 오랫동안 실행됩니다. 해결책은 없나요? 물론 그렇지 않습니다. 대답은 NULL 매개변수에 있습니다. 이는 위에서 새로운 리소스 유형을 생성하기 위해 호출한 zend_register_list_destructors_ex() 함수의 첫 번째 매개변수이자 두 번째 매개변수입니다. 두 매개변수 모두 각각 콜백 매개변수를 나타냅니다. 첫 번째 콜백 함수는 범위가 끝나거나 unset()되는 등 스크립트에서 해당 유형의 리소스 변수가 해제될 때 트리거됩니다.
두 번째 콜백 함수는 긴 링크 유형과 유사한 리소스에 사용됩니다. 즉, 이 리소스는 생성된 후 항상 메모리에 존재하며 요청이 끝난 후에도 해제되지 않습니다. 웹 서버 프로세스가 종료될 때 호출되며 이는 MSHUTDOWN 단계 동안 커널에 의해 호출되는 것과 동일합니다. 영구 리소스에 대해서는 다음 섹션에서 자세히 논의하겠습니다.
먼저 첫 번째 콜백 함수를 정의해 보겠습니다.
static void php_sample_descriptor_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
FILE *fp = (FILE*)rsrc->ptr;
fclose(fp)
그런 다음 사용하세요. zend_register_list_destructors_ex() 함수의 첫 번째 매개변수를 NULL로 바꾸세요:
le_sample_descriptor = zend_register_list_destructors_ex(
php_sample_descriptor_dtor,
NULL,
PHP_SAMPLE_ DESCRIPTOR_RES_NAME,
module_number)
, 스크립트의 경우 gets 위 유형의 리소스 변수가 설정되지 않거나 커널에 의해 범위가 해제되면 커널이 이를 전처리하기 위해 기본 php_sample_descriptor_dtor를 호출합니다. 이런 식으로 보면 Sample_close() 함수가 전혀 필요하지 않은 것 같습니다!
$fp = Sample_fopen("/home/jdoe/notes.txt", "r")
unset($fp)
?> unset($ fp)가 실행된 후 커널은 자동으로 php_sample_descriptor_dtor 함수를 호출하여 이 변수에 해당하는 일부 데이터를 정리합니다. 물론 상황이 그렇게 간단하지는 않습니다. 이 질문을 염두에 두고 계속 읽어보겠습니다.
리소스 디코딩
리소스 변수를 북마크와 비교하지만 북마크만 있으면 전혀 효과가 없습니다! 북마크를 통해 해당 페이지를 찾아야 합니다. 자원 변수의 경우 이를 통해 해당 최종 데이터를 찾을 수 있어야 합니다!
ZEND_FUNCTION(sample_fwrite)
{ FILE *fp;
zval *file_resource;
char *data;
int data_len;
if (zend_parse_parameters(ZEND_NUM_AR GS() TSRMLS_CC, " ",&file_resource, &data, &data_len) == FAILURE )
{
RETURN_NULL();
}
/* zval*을 사용하여 리소스 유형을 확인하고
* 포인터를 검색합니다. 조회 테이블 */
ZEND_FETCH_RESOURCE(fp,FILE*,&file_resource,-1,PHP_SAMPLE_DESCRIPTOR_RES_NAME,le_sample_descriptor) */
RETURN_LONG(fwrite(data, 1, data_len, fp))
}
zend_parse_parameters( ) 함수는 리소스 유형을 수신하는 변수를 나타내며 해당 캐리어는 zval *입니다. 그럼 ZEND_FETCH_RESOURCE() 매크로 함수를 살펴보겠습니다.
#define ZEND_FETCH_RESOURCE(rsrc, rsrc_type,passed_id,default_id,resource_type_name,resource_type)
rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC,default_id,resource_type_name, NULL,1,resource_type)
ZEND_VER IFY_RESOURCE(rsrc ) ;
//이 예에서는 다음과 같습니다:
fp = (FILE*) zend_fetch_resource(&file_descriptor TSRMLS_CC, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, NULL,1, le_sample_descriptor)
if ( !fp)
{
RETURN_FALSE;
}
zend_fetch_resource()는 zend_hash_find()를 캡슐화한 계층으로, HashTable에서 다양한 {resources}를 검색합니다. 최종 필수 데이터인 경우 ZEND_VERIFY_RESOURCE() 매크로 함수를 사용하여 데이터를 확인합니다. 위 코드를 보면 NULL과 0은 자원으로 사용할 수 없음을 알 수 있다. 위의 예에서 zend_fetch_resource() 함수는 먼저 le_sample_descriptor가 나타내는 리소스 유형을 가져옵니다. 리소스가 존재하지 않거나 수신된 zval이 리소스 유형 변수가 아닌 경우 NULL을 반환하고 해당 오류 메시지를 발생시킵니다. 마지막 ZEND_VERIFY_RESOURCE() 매크로 함수는 오류가 감지되면 자동으로 반환되므로 오류 감지에서 벗어나 프로그램의 기본 논리에 더 집중할 수 있습니다. 이제 해당 FILE*을 얻었으므로 fwrite()를 사용하여 여기에 데이터를 쓰겠습니다! .
zend_fetch_resource()가 실패할 때 오류가 발생하지 않도록 하려면 간단히 resources_type_name 매개변수에 NULL을 전달하면 됩니다. 표시할 의미 있는 오류 메시지가 없으면 zend_fetch_resource()는 자동으로 실패하게 됩니다.
대신 다른 방법을 사용하여 가져올 수도 있습니다. 우리가 궁극적으로 원하는 데이터.
ZEND_FUNCTION(sample_fwrite)
{
FILE *fp;
zval *file_resource;
char *data; int data_len, rsrc_type
if (zend_parse _parameters) ( ZEND_NUM_ARGS () TSRMLS_CC, "RS", & File_Resource, & Data, & Data_Len) == 실패) {
Return_null ()
}
fp = (file*) Zend_list_find ( Z_resval_p (file_resource) , & rsrc_type);
if (!fp || rsrc_type != le_sample_descriptor) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,"잘못된 리소스가 제공되었습니다.")
RETURN_FALSE; (fwrite(data, 1, data_len, fp));
}
자신의 습관에 따라 어떤 형식을 사용할지 선택할 수 있지만 ZEND_FETCH_RESOURCE() 매크로 함수를 사용하는 것이 좋습니다.
강제 파기
위의 아직 해결되지 않은 질문이 있습니다. 즉, 위에서 구현한 것과 유사한 unset($fp)가 정말 전능할까요? 물론 그렇지 않습니다. 다음 코드를 살펴보세요.
$fp = Sample_fopen("/home/jdoe/world_domination.log", "a")
$evil_log = $fp; 🎜 > unset($fp);
?>
이번에는 $fp와 $evil_log가 zval을 공유하지만 $fp가 아직 사용 중이므로 zval은 해제되지 않습니다. 즉, $evil_log가 나타내는 파일 핸들은 여전히 쓰기 가능합니다! 따라서 이런 종류의 오류를 방지하려면 수동으로 닫아야 합니다! Sample_close() 함수가 존재해야 합니다!
PHP_FUNCTION(sample_fclose)
{ FILE *fp;
zval *file_resource; 🎜 > RETURN_NULL(); }
/* 실제로
* FILE을 가져올 필요는 없습니다. * 리소스를 가져오면
* 종료하는지 확인할 수 있는 기회
* 올바른 리소스 유형 */
ZEND_FETCH_RESOURCE(fp, FILE*, &file_resource, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor); AL_P(file_resource)) ;
RETURN_TRUE;
}
이 삭제 작업은 또한 리소스 데이터가 HashTable에 저장되어 있음을 다시 보여줍니다. . zend_hash_index_find() 또는 zend_hash_next_index_insert()와 같은 함수를 통해 리소스를 저장하는 이 HashTable을 작동할 수 있지만 이는 결코 좋은 생각이 아닙니다. 후속 버전에서는 PHP가 이 부분의 구현을 수정할 수 있으므로 위의 방법은 작동하지 않을 수 있습니다. 더 나은 호환성을 위해 표준 매크로 기능이나 API 기능을 사용하세요. EG(regular_list)의 HashTable에서 데이터를 삭제하면 dtor 함수가 콜백되는데, 이는 zend_register_list_destructors_ex() 함수 호출 시 첫 번째 매개변수인 리소스 변수의 카테고리에 따라 해당 dtor 함수 구현을 호출합니다.
자원 데이터의 자체 참조 횟수를 고려하기 때문에 삭제에 특별히 사용되는 zend_list_delete() 매크로 함수를 여러 곳에서 볼 수 있습니다.