1. Apache 설치

$ sudo apt-get install apache2

 

설치가 완료 되고 나면 각 계정 별로 pubilc_html 디렉토리를 사용 할 수 있도록 셋팅 한다.

/etc/apach2/httpd.conf(or /etc/apache2/apache2.conf)

Include /etc/apache2/mods-available/userdir.load

Include /etc/apache2/mods-available/userdir.conf


UserDir public_html


<Directory /home/-/public_html>

        AllowOverride All

        Options ExecCGI

        <Limit GET POST OPTIONS>

                Order allow,deny

                Allow from all

        </Limit>

        <LimitExcept GET POST OPTIONS>

                Order allow,deny

                Allow from all

        </LimitExcept>

</Directory>


ServerName localhost 

 

설정이 끝나고 난 뒤 apache를 재시작 한다. 이제 각 계정별로 public_html 디렉토리 만들게 되면 웹에서 해당 디렉토리 아래에 있는 파일들에 접근 가능 하다. 예를들어 호스트 이름이 some.host.com, 계정이름이 dev 인경우 http://some.host.com/~dev 와 같은 형식으로 각 계정별 접근이 가능하다.

 

2. php 설치

위 1번 과정을 끝내고 .php파일을 웹브라우저를 통해 보려고 하면 php파일의 내용을 보여주는 것이 아니라 파일을 다운로드 하려고 한다. php가 아직 설치가 안되서 그렇다. 아래처럼 php를 설치해주자.

$ sudo apt-get install php5

 

3. mysql 설치

$ sudo apt-get install php5-mysql

ref : http://egloos.zum.com/linguistics/v/1996546

'도구의발견' 카테고리의 다른 글

Ubuntu core dump 셋팅  (0) 2017.03.30
[Unity] Texture2D를 Sprite로 변경  (0) 2017.02.28
Ubuntu Apache(14.04) 설치  (0) 2015.04.29
MySQL 백업 스크립트  (1) 2015.03.06
MySQL master-slave 리플리케이션  (0) 2015.03.06
MySQL Event 등록  (2) 2014.09.24
Posted by kukuta
TAG apache, php, ubuntu

댓글을 달아 주세요

Fatal error: Maximum execution time of 30 seconds exceeded in *.php on line n

/**
php를 이용해 로그 파일을 분석하는 프로그램을 작성 중 이다.  처음에는 php의 간단하면서도 강력한 문자 처리 능력에 반했으나, 곧 거대한 작업(지금 처리하고 있는것은 9백만건 정도 된다)에 있어서는 상당히 불리 한 언어라는 것을 알게 되었다.

하지만 어쩌겠는가 부탁하는 사람이 유지 보수를 위해서 php로 해달라는데.
*/

이 에러는 소스 상에 무한 루프를 돈다던지 실제로 결과가 나오기 까지 max_execution_time을 초과 할 경우 던져진다.

첫번째 해결 방법은, php.ini 파일을 열어 max_execution_time을 수정하고 restart한다.
※ php.ini의 위치는 phpinfo() 함수를 호출 하면 나온다.

두번째 해결 방법은, set_time_limit(0) 을 해당 페이지에 적어 주면 된다.(함수레퍼런스 보기)

'진리는어디에' 카테고리의 다른 글

C++ style type cast  (2) 2008.08.05
DrawPrimitive와 DrawPrimitiveUP의 차이  (1) 2008.05.19
[PHP]Fatal error: Maximum execution time of 30 seconds  (0) 2008.05.19
Reference list  (0) 2008.05.16
Generation reference counting  (0) 2008.05.15
Logical clock  (2) 2008.05.02
Posted by kukuta
TAG error, php

댓글을 달아 주세요

/**
 이번 장을 보기 전에 이전 장에서 공부한 내용에 대해서 간략하게 언급 해 보도록 하지요.
 우리는 PHP extension의 개념에 대해서 간략하게나마 알아보았고, 모듈의 라이프 싸이클에 대해서도 공부했습니다. 간단한 함수들을 직접 만들어 보았고, dynamic 변수와 static 변수를 리턴할 때 무엇에 신경을 써야 하는지 살펴 보았습니다. 그 외에도 다른 몇가지를 더 얇지만 넓게 살펴 보았습니다.

 이번 장에서는 PHP 스크립트로 부터 넘어오는 파라메터를 extension 모듈에서 어떻게 해석하고, PHP와 Zend Engine의 변수 관리 방법에 대해서도 알아 보도록 하겠습니다.
*/

Accepting Values
실질적으로 함수들을 위한 파라메터들은 일반적인 기대와는 달리 함수에 선언되지 않습니다. 대신 넘어오는 파라메터에 대한 참조(reference) 리스트가 넘어 올 뿐이죠. 그러면 함수는 ZE에게 그 리스트를 이용하여 쓸모 있는 무엇인가를 만들어 줄것을 요구합니다. 바로 '변수'죠.

자 새로운 함수를 하나 정의하는 것으로 시작을 해보도록 하겠습니다. 함수의 이름은 'hello_greetme()'입니다. 이 함수는 하나의 문자열 파라메터를 받고 그것을 간단한 인사말과 함께 출력합니다. 앞에서 했던대로 3곳에 새로운 코드를 추가 하도록 하겠습니다.

'php_hello.h'에는 다른 함수 원형 다음에 아래의 프로토타입을 선언합니다 :

PHP_FUNCTION(hello_greetme); 

'hello.c'에는 hello_functions 구조체 맨 밑에 아래와 같이 추가 합니다 :

    PHP_FE(hello_bool, NULL)
    PHP_FE(hello_null, NULL)
    PHP_FE(hello_greetme, NULL)
    {NULL, NULL, NULL}
}; 

그리고 'hello.c'의 맨 끝부분, 함수의 정의들이 모여 있는 부분, 이 부분에는 아래와 같이 함수의 몸체를 추가 해 줍니다 :

PHP_FUNCTION(hello_greetme)
{
    char *name;
    int name_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) {
        RETURN_NULL();
    }
    php_printf("Hello %s ", name);
    RETURN_TRUE;
}

'zend_parse_parameters()'를 살펴 보겠습니다. ZEND_NUM_ARGS()는 ZE에게 파라메터들에 대한 정보를 제공합니다. 'TSRMLS_CC'는 쓰레드 세이프하다는 것을 표시합니다. 그리고 이 함수는 'SUCCESS' 또는 'FAILURE' 둘 중에 하나를 리턴합니다. 일반적인 상황에서는 SUCCESS가 리턴되겠지만, 스크립트에서 지정된것 보다 더 많거나 적은 파라메터를 넘긴다거나, 적절한 데이터 타입으로 변환 될 수 없는 파라메터가 넘어오게 된다면, Zend는 자동적으로 에러메시지를 발생시키며 함수를 종료 시켜버릴것 입니다.

 위의 예제에서 'hello_greetme()' 함수는 'zend_parse_parameters()' 함수에 단 하나의 파라메터만이 넘어 올것이며 그 파라메터는 문자열이라고 지정하고 있습니다. 그리고 그 문자열 파라메터를 저장하기 위해 'char*' 타입의 변수의 참조(referece)를 넘기고 있습니다. 그리고 한 가지 더, 'zend_parse_parameters()'함수에 정수(integer)변수도 함께 넘겨지고 있다는 것에 주의 해 주세요. 이 변수를 통해서 ZE는 문자열의 길리를 리턴합니다.

 다음으로 할 일은 'zend_parse_parameters()'를 통해 넘겨 받은 문자열을 인사말에 섞어 출력 하는 것입니다. 일반적으로 우리에게 친숙한 'printf()'대신 'php_printf()'함수를 사용 했다는 것을 주의 깊게 봐주세요. 이 함수를 사용하는 것에는 몇 가지 이유가 있습니다. 첫째, PHP의 출력 버퍼링 메커니즘을 통해서 출력을 하게 됩니다. 이것은 기존의 데이터 버퍼링 뿐만아니라 gzip 압축 같은 추가적인 프로세스를 수행 한다는 의미 입니다. 둘째, CLI나 CGI에서의 대부분의 출력 stdout인 반면에, SAPIs는 대부분의 출력이 특정 파이프나 소켓입니다. 그렇기 때문에 단순히 'printf()'만을 사용한다는 것은 표준 출력 장치를 이용하지 않는 곳에서는 많은 정보를 잃어 버릴 수 있다는 것을 의미 하기도 합니다.

 마지막으로 리턴에 대해서 살펴 보도록 하지요. 위의 함수는 단순히 TRUE만을 리턴하고 있습니다. 하지만 여러분이 원하지 않는다면 함수에서 아무런 값도 리턴하지 않을 수 있습니다. 만일 리턴 값이 없다면 기본적으로 NULL을 리턴하도록 되어 있습니다만 별로 좋은 습관은 못 됩니다. 정말 아무것도 리턴 할 것이 없다면 그냥 모든 과정이 잘 처리 되었다라는 의미에서 TRUE를 리턴하도록 습관을 길러 봅시다.

'zend_parse_parameters()'함수는 선택 파라메터를 다루는데 사용 될 수 도 있습니다. 다음 예제를 보면, long, double(float) 그리고 boolean 선택 인자가 있습니다. 여기서 '선택 인자'라는 것은 파라메터를 직접 지정해도 되지만, 만일 아무런 지정 없이 그냥 넘어간다면 기본 값으로(아래의 예제에서는 'false')로 셋팅 되는 파라메터를 말합니다. '선택 인자'라는 것이 정확한 표현은 아니지만 당장 생각하는 적절한 표현이 없군요 :

function hello_add($a, $b, $return_long = false) {
    $sum = (int)$a + (float)$b;
    if ($return_long) {
        return intval($sum);
    } else {
        return floatval($sum);
    }
}

C 코드를 살펴 보도록 하겠습니다(아래에는 함수의 정의만 적어 놓았지만, php_hello.h에 함수 원형과, hello_function[]에 엔트리들을 추가 해야 하는 것을 잊지 마세요!) :

PHP_FUNCTION(hello_add)
{
    long a;
    double b;
    zend_bool return_long = 0;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ld|b", &a, &b, &return_long) == FAILURE) {
        RETURN_NULL();
    }
    if (return_long) {
        RETURN_LONG(a + b);
    } else {
        RETURN_DOUBLE(a + b);
    }

 위의 데이터 타입 스트링("ld|b"라고 되어 잇는 부분)을 살펴 보도록 하겠습니다. l은 long 타입의 변수를 말합니다, d는 double을 나타내는 것이고요. 다음 문자열은 파이프 입니다. 이것은 나머지 문자들은 '선택 인자'라는 것을 나타냅니다. 만일 선택 인자가 넘어 오지 않는다면 'zend_parse_parameters()'는 기본적으로 셋팅되어 있는 값을 사용합니다. 마지막 문자는 b입니다. 당연히 boolean을 나타내지요. 데이터 타입 스트링(data type string) 뒤에 따라 오는 a, b와 return_long 들은 앞에서 공부 한 바와 같이 'zend_parse_parameters()'를 통해 넘어오는 데이터들이 저장될 공간을 지정해 주는 것입니다.

Table 1은 여러 가지 타입들과 그에 대응하는 문자 코드를 나타내고 있습니다 :

Table 1: Types and letter codes used in zend_parse_parameters()
Type Code Variable Type
Boolean b zend_bool
Long l long
Double d double
String s char*, int
Resource r zval*
Array a zval*
Object o zval*
zval z zval*

맨 아래의 네 타입이 모두 'zval*'만을 리턴하는 것에 주목할 필요가 있습니다. zval은 실질적으로 PHP의 변수를 나타낼 수 있는 데이터 타입입니다. 이전 장에서 언급했던 세가지 복합 데이터형인 'Resource', 'Array', 'Object'들은 C에서 마땅히 표현 할 수 있는 자료구조가 없으므로, zval* 형태의 타입으로 전환되는 것입니다.

The ZVAL

The zval, and PHP userspace variables in general, will easily be the most difficult concepts you'll need to wrap your head around. They will also be the most vital. To begin with, let's look at the structure of a zval:

struct {
    union {
        long lval;
        double dval;
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;
        zend_object_value obj;
    } value;
    zend_uint refcount;
    zend_uchar type;
    zend_uchar is_ref;
} zval;

As you can see, every zval has three basic elements in common: type, is_ref, and refcount. is_ref and refcount will be covered later on in this tutorial; for now let's focus on type.

By now you should already be familiar with PHP's eight data types. They're the seven listed in Table 1, plus NULL, which despite (or perhaps because of) the fact that it literally is nothing, is a type unto its own. Given a particular zval, the type can be examined using one of three convenience macros: Z_TYPE(zval), Z_TYPE_P(zval*), or Z_TYPE_PP(zval**). The only functional difference between these three is the level of indirection expected in the variable passed into it. The convention of using _P and _PP is repeated in other macros, such as the *VAL macros you're about to look at.

The value of type determines which portion of the zval's value union will be set. The following piece of code demonstrates a scaled down version of var_dump():

PHP_FUNCTION(hello_dump)
{
    zval *uservar;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", uservar) == FAILURE) {
        RETURN_NULL();
    }

    switch (Z_TYPE_P(uservar)) {
        case IS_NULL:
            php_printf("NULL ");
            break;
        case IS_BOOL:
            php_printf("Boolean: %s ", Z_LVAL_P(uservar) ? "TRUE" : "FALSE");
            break;
        case IS_LONG:
            php_printf("Long: %ld ", Z_LVAL_P(uservar));
            break;
        case IS_DOUBLE:
            php_printf("Double: %f ", Z_DVAL_P(uservar));
            break;
        case IS_STRING:
            php_printf("String: ");
            PHPWRITE(Z_STRVAL_P(uservar), Z_STRLEN_P(uservar));
            php_printf(" ");
            break;
        case IS_RESOURCE:
            php_printf("Resource ");
            break;
        case IS_ARRAY:
            php_printf("Array ");
            break;
        case IS_OBJECT:
            php_printf("Object ");
            break;
        default:
            php_printf("Unknown ");
    }

    RETURN_TRUE;
}

As you can see, the Boolean data type shares the same internal element as the long data type. Just as with RETURN_BOOL(), which you used in Part One of this series, FALSE is represented by 0, while TRUE is represented by 1.

When you use zend_parse_parameters() to request a specific data type, such as string, the Zend Engine checks the type of the incoming variable. If it matches, Zend simply passes through the corresponding parts of the zval to the right data types. If it's of a different type, Zend converts it as is appropriate and/or possible, using its usual type-juggling rules.

Modify the hello_greetme() function you implemented earlier by separating it out into smaller pieces:

PHP_FUNCTION(hello_greetme)
{
    zval *zname;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zname) == FAILURE) {
        RETURN_NULL();
    }

    convert_to_string(zname);

    php_printf("Hello ");
    PHPWRITE(Z_STRVAL_P(zname), Z_STRLEN_P(zname));
    php_printf(" ");

    RETURN_TRUE;
}

This time, zend_parse_parameters() was told to simply retrieve a PHP variable (zval) regardless of type, then the function explicitly cast the variable as a string (similar to $zname = (string)$zname; ), then php_printf() was called using the STRing VALue of the zname structure. As you've probably guessed, other convert_to_*() functions exist for bool, long, and double.

Creating ZVALs

So far, the zvals you've worked with have been allocated by the Zend Engine and will be freed the same way. Sometimes, however, it's necessary to create your own zval. Consider the following block of code:

{
    zval *temp;

    ALLOC_INIT_ZVAL(temp);

    Z_TYPE_P(temp) = IS_LONG;
    Z_LVAL_P(temp) = 1234;

    zval_ptr_dtor(&temp);
}

ALLOC_INIT_ZVAL(), as its name implies, allocates memory for a zval* and initializes it as a new variable. Once that's done, the Z_*_P() macros can be used to set the type and value of this variable. zval_ptr_dtor() handles the dirty work of cleaning up the memory allocated for the variable.

The two Z_*_P() calls could have actually been reduced to a single statement:

ZVAL_LONG(temp, 1234);

Similar macros exist for the other types, and follow the same syntax as the RETURN_*() macros you saw in Part One of thise series. In fact the RETURN_*() macros are just thin wrappers for RETVAL_*() and, by extension, ZVAL_*(). The following five versions are all identical:

RETURN_LONG(42);

RETVAL_LONG(42);
return;

ZVAL_LONG(return_value, 42);
return;

Z_TYPE_P(return_value) = IS_LONG;
Z_LVAL_P(return_value) = 42;
return;

return_value->type = IS_LONG;
return_value->value.lval = 42;
return;

If you're sharp, you're thinking about the impact of how these macros are defined on the way they're used in functions like hello_long(). "Where does return_value come from and why isn't it being allocated with ALLOC_INIT_ZVAL()?", you might be wondering.

While it may be hidden from you in your day-to-day extension writing, return_value is actually a function parameter defined in the prototype of every PHP_FUNCTION() definition. The Zend Engine allocates memory for it and initializes it as NULL so that even if your function doesn't explicitly set it, a value will still be available to the calling program. When your internal function finishes executing, it passes that value to the calling program, or frees it if the calling program is written to ignore it.

Arrays

Since you've used PHP in the past, you've already recognized an array as a variable whose purpose is to carry around other variables. The way this is represented internally is through a structure known as a HashTable. When creating arrays to be returned to PHP, the simplest approach involves using one of the functions listed in Table 2.

Table 2: zval array creation functions
PHP Syntax C Syntax (arr is a zval*) Meaning
$arr = array(); array_init(arr); Initialize a new array
$arr[] = NULL; add_next_index_null(arr); Add a value of a given type to
a numerically indexed array
$arr[] = 42; add_next_index_long(arr, 42);
$arr[] = true; add_next_index_bool(arr, 1);
$arr[] = 3.14; add_next_index_double(arr, 3.14);
$arr[] = 'foo'; add_next_index_string(arr, "foo", 1);
$arr[] = $myvar; add_next_index_zval(arr, myvar);
$arr[0] = NULL; add_index_null(arr, 0); Add a value of a given type to
a specific index in an array
$arr[1] = 42; add_index_long(arr, 1, 42);
$arr[2] = true; add_index_bool(arr, 2, 1);
$arr[3] = 3.14; add_index_double(arr, 3, 3.14);
$arr[4] = 'foo'; add_index_string(arr, 4, "foo", 1);
$arr[5] = $myvar; add_index_zval(arr, 5, myvar);
$arr['abc'] = NULL; add_assoc_null(arr, "abc"); Add a value of a given type to
an associatively indexed array
$arr['def'] = 711; add_assoc_long(arr, "def", 711);
$arr['ghi'] = true; add_assoc_bool(arr, "ghi", 1);
$arr['jkl'] = 1.44; add_assoc_double(arr, "jkl", 1.44);
$arr['mno'] = 'baz'; add_assoc_string(arr, "mno", "baz", 1);
$arr['pqr'] = $myvar; add_assoc_zval(arr, "pqr", myvar);

As with the RETURN_STRING() macro, the add_*_string() functions take a 1 or a 0 in the final parameter to indicate whether the string contents should be copied. They also have a kissing cousin in the form of an add_*_stringl() variant for each. The l indicates that the length of the string will be explicitly provided (rather than having the Zend Engine determine this with a call to strval(), which is binary-unsafe).

Using this binary-safe form is as simple as specifying the length just before the duplication parameter, like so:

add_assoc_stringl(arr, "someStringVar", "baz", 3, 1);

Using the add_assoc_*() functions, all array keys are assumed to contain no NULLs - the add_assoc_*() functions themselves are not binary-safe with respect to keys. Using keys with NULLs in them is discouraged (as it is already a technique used with protected and private object properties), but if doing so is necessary, you'll learn how you can do it soon enough, when we get into the zend_hash_*() functions later.

To put what you've just learned into practice, create the following function to return an array of values to the calling program. Be sure to add entries to php_hello.h and hello_functions[] so as to properly declare this function.

PHP_FUNCTION(hello_array)
{
    char *mystr;
    zval *mysubarray;

    array_init(return_value);

    add_index_long(return_value, 42, 123);

    add_next_index_string(return_value, "I should now be found at index 43", 1);

    add_next_index_stringl(return_value, "I'm at 44!", 10, 1);

    mystr = estrdup("Forty Five");
    add_next_index_string(return_value, mystr, 0);

    add_assoc_double(return_value, "pi", 3.1415926535);

    ALLOC_INIT_ZVAL(mysubarray);
    array_init(mysubarray);
    add_next_index_string(mysubarray, "hello", 1);
    add_assoc_zval(return_value, "subarray", mysubarray);    
}

Building this extension and issuing var_dump(hello_array()); gives:

array(6) {
  [42]=>
  int(123)
  [43]=>
  string(33) "I should now be found at index 43"
  [44]=>
  string(10) "I'm at 44!"
  [45]=>
  string(10) "Forty Five"
  ["pi"]=>
  float(3.1415926535)
  ["subarray"]=>
  array(1) {
    [0]=>
    string(5) "hello"
  }
}

Reading values back out of arrays means extracting them as zval**s directly from a HashTable using the zend_hash family of functions from the ZENDAPI. Let's start with a simple function which accepts one array as a parameter:

function hello_array_strings($arr) {

    if (!
is_array($arr)) return NULL;

   
printf("The array passed contains %d elements ", count($arr));

    foreach(
$arr as $data) {
        if (
is_string($data)) echo "$data ";
    }
}

Or, in C:

PHP_FUNCTION(hello_array_strings)
{
    zval *arr, **data;
    HashTable *arr_hash;
    HashPosition pointer;
    int array_count;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) == FAILURE) {
        RETURN_NULL();
    }

    arr_hash = Z_ARRVAL_P(arr);
    array_count = zend_hash_num_elements(arr_hash);

    php_printf("The array passed contains %d elements ", array_count);

    for(zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(arr_hash, &pointer)) {

        if (Z_TYPE_PP(data) == IS_STRING) {
            PHPWRITE(Z_STRVAL_PP(data), Z_STRLEN_PP(data));
            php_printf(" ");
        }
    }
    RETURN_TRUE;
}

In this function only those array elements which are of type string are output, in order to keep the function brief. You may be wondering why we didn't just use convert_to_string() as we did in the hello_greetme() function earlier. Let's give that a shot; replace the for loop above with the following:

    for(zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(arr_hash, &pointer)) {

        convert_to_string_ex(data);
        PHPWRITE(Z_STRVAL_PP(data), Z_STRLEN_PP(data));
        php_printf(" ");
    }

Now compile your extension again and run the following userspace code through it:

<?php

$a
= array('foo',123);
var_dump($a);
hello_array_strings($a);
var_dump($a);

?>

Notice that the original array was changed! Remember, the convert_to_*() functions have the same effect as calling set_type(). Since you're working with the same array that was passed in, changing its type here will change the original variable. In order to avoid this, you need to first make a copy of the zval. To do this, change that for loop again to the following:

    for(zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(arr_hash, &pointer)) {

        zval temp;

        temp = **data;
        zval_copy_ctor(&temp);
        convert_to_string(&temp);
        PHPWRITE(Z_STRVAL(temp), Z_STRLEN(temp));
        php_printf(" ");
        zval_dtor(&temp);
    }

The more obvious part of this version - temp = **data - just copies the data members of the original zval, but since a zval may contain additional resource allocations like char* strings, or HashTable* arrays, the dependent resources need to be duplicated with zval_copy_ctor(). From there it's just an ordinary convert, print, and a final zval_dtor() to get rid of the resources used by the copy.

If you're wondering why you didn't do a zval_copy_ctor() when we first introduced convert_to_string(), it's because the act of passing a variable into a function automatically performs a copy separating the zval from the original variable. This is only done on the base zval through, so any subordinate resources (such as array elements and object properties) still need to be separated before use.

Now that you've seen array values, let's extend the exercise a bit by looking at the keys as well:

for(zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(arr_hash, &pointer)) {

    zval temp;
    char *key;
    int key_len;
    long index;

    if (zend_hash_get_current_key_ex(arr_hash, &key, &key_len, &index, 0, &pointer) == HASH_KEY_IS_STRING) {
        PHPWRITE(key, key_len);
    } else {
        php_printf("%ld", index);
    }

    php_printf(" => ");

    temp = **data;
    zval_copy_ctor(&temp);
    convert_to_string(&temp);
    PHPWRITE(Z_STRVAL(temp), Z_STRLEN(temp));
    php_printf(" ");
    zval_dtor(&temp);
}

Remember that arrays can have numeric indexes, associative string keys, or both. Calling zend_hash_get_current_key_ex() makes it possible to fetch either type from the current position in the array, and determine its type based on the return values, which may be any of HASH_KEY_IS_STRING, HASH_KEY_IS_LONG, or HASH_KEY_NON_EXISTANT. Since zend_hash_get_current_data_ex() was able to return a zval**, you can safely assume that HASH_KEY_NON_EXISTANT will not be returned, so only the IS_STRING and IS_LONG possibilities need to be checked.

There's another way to iterate through a HashTable. The Zend Engine exposes three very similar functions to accommodate this task: zend_hash_apply(), zend_hash_apply_with_argument(), and zend_hash_apply_with_arguments(). The first form just loops through a HashTable, the second form allows a single argument to be passed through as a void*, while the third form allows an unlimited number of arguments via a vararg list. hello_array_walk() shows each of these in action:

static int php_hello_array_walk(zval **element TSRMLS_DC)
{
    zval temp;

    temp = **element;
    zval_copy_ctor(&temp);
    convert_to_string(&temp);
    PHPWRITE(Z_STRVAL(temp), Z_STRLEN(temp));
    php_printf(" ");
    zval_dtor(&temp);

    return ZEND_HASH_APPLY_KEEP;
}

static int php_hello_array_walk_arg(zval **element, char *greeting TSRMLS_DC)
{
    php_printf("%s", greeting);
    php_hello_array_walk(element TSRMLS_CC);

    return ZEND_HASH_APPLY_KEEP;
}

static int php_hello_array_walk_args(zval **element, int num_args, var_list args, zend_hash_key *hash_key)
{
    char *prefix = va_arg(args, char*);
    char *suffix = va_arg(args, char*);
    TSRMLS_FETCH();

    php_printf("%s", prefix);
    php_hello_array_walk(element TSRMLS_CC);
    php_printf("%s ", suffix);

    return ZEND_HASH_APPLY_KEEP;
}

PHP_FUNCTION(hello_array_walk)
{
    zval *zarray;
    int print_newline = 1;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &zarray) == FAILURE) {
        RETURN_NULL();
    }

    zend_hash_apply(Z_ARRVAL_P(zarray), (apply_func_t)php_hello_array_walk TSRMLS_CC);
    zend_hash_apply_with_argument(Z_ARRVAL_P(zarray), (apply_func_arg_t)php_hello_array_walk_arg, "Hello " TSRMLS_CC);
    zend_hash_apply_with_arguments(Z_ARRVAL_P(zarray), (apply_func_args_t)php_hello_array_walk_args, 2, "Hello ", "Welcome to my extension!");

    RETURN_TRUE;
}

By now you should be familiar enough with the usage of the functions involved that most of the above code will be obvious. The array passed to hello_array_walk() is looped through three times, once with no arguments, once with a single argument, and a third time with two arguments. In this design, the walk_arg() and walk_args() functions actually rely on the no-argument walk() function to do the job of converting and printing the zval, since the job is common across all three.

In this block of code, as in most places where you'll use zend_hash_apply(), the apply() functions return ZEND_HASH_APPLY_KEEP. This tells the zend_hash_apply() function to leave the element in the HashTable and continue on with the next one. Other values which can be returned here are: ZEND_HASH_APPLY_REMOVE, which does just what it says - removes the current element and continues applying at the next - and ZEND_HASH_APPLY_STOP, which will halt the array walk at the current element and exit the zend_hash_apply() function completely.

The less familiar component in all this is probably TSRMLS_FETCH(). As you may recall from Part One, the TSRMLS_* macros are part of the Thread Safe Resource Management layer, and are necessary to keep one thread from trampling on another. Because the multi-argument version of zend_hash_apply() uses a vararg list, the tsrm_ls marker doesn't wind up getting passed into the walk() function. In order to recover it for use when we call back into php_hello_array_walk(), your function calls TSRMLS_FETCH() which performs a lookup to find the correct thread in the resource pool. (Note: This method is substantially slower than passing the argument directly, so use it only when unavoidable.)

Iterating through an array using this foreach-style approach is a common task, but often you'll be looking for a specific value in an array by index number or by associative key. This next function will return a value from an array passed in the first parameter based on the offset or key specified in the second parameter.

PHP_FUNCTION(hello_array_value)
{
    zval *zarray, *zoffset, **zvalue;
    long index = 0;
    char *key = NULL;
    int key_len = 0;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "az", &zarray, &zoffset) == FAILURE) {
        RETURN_NULL();
    }

    switch (Z_TYPE_P(zoffset)) {
        case IS_NULL:
            index = 0;
            break;
        case IS_DOUBLE:
           index = (long)Z_DVAL_P(zoffset);
            break;
        case IS_BOOL:
        case IS_LONG:
        case IS_RESOURCE:
            index = Z_LVAL_P(zoffset);
            break;
        case IS_STRING:
            key = Z_STRVAL_P(zoffset);
            key_len = Z_STRLEN_P(zoffset);
            break;
        case IS_ARRAY:
            key = "Array";
            key_len = sizeof("Array") - 1;
            break;
        case IS_OBJECT:
            key = "Object";
            key_len = sizeof("Object") - 1;
            break;
        default:
            key = "Unknown";
            key_len = sizeof("Unknown") - 1;
    }

    if (key && zend_hash_find(Z_ARRVAL_P(zarray), key, key_len + 1, (void**)&zvalue) == FAILURE) {
        RETURN_NULL();
    } else if (!key && zend_hash_index_find(Z_ARRVAL_P(zarray), index, (void**)&zvalue) == FAILURE) {
        RETURN_NULL();
    }

    *return_value = **zvalue;
    zval_copy_ctor(return_value);
}

This function starts off with a switch block that treats type conversion in much the same way as the Zend Engine would. NULL is treated as 0, Booleans are treated as their corresponding 0 or 1 values, doubles are cast to longs (and truncated in the process) and resources are cast to their numerical value. The treatment of resource types is a hangover from PHP 3, when resources really were just numbers used in a lookup and not a unique type unto themselves.

Arrays and objects are simply treated as a string literal of "Array" or "Object", since no honest attempt at conversion would actually make sense. The final default condition is put in as an ultra-careful catchall just in case this extension gets compiled against a future version of PHP, which may have additional data types.

Since key is only set to non-NULL if the function is looking for an associative key, it can use that value to decide whether it should use an associative or index based lookup. If the chosen lookup fails, it's because the key doesn't exist, and the function therefore returns NULL to indicate failure. Otherwise that zval is copied into return_value.

Symbol Tables as Arrays

If you've ever used the $GLOBALS array before, you know that every variable you declare and use in the global scope of a PHP script also appears in this array. Recalling that the internal representation of an array is a HashTable, one question comes to mind: "Is there some special place where the GLOBALS array can be found?" The answer is "Yes". It's in the Executor Globals structure as EG(symbol_table), which is of type HashTable (not HashTable*, mind you, just HashTable).

You already know how to find associatively keyed elements in an array, and now that you know where to find the global symbol table, it should be a cinch to look up variables from extension code:

PHP_FUNCTION(hello_get_global_var)
{
    char *varname;
    int varname_len;
    zval **varvalue;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &varname, &varname_len) == FAILURE) {
        RETURN_NULL();
    }

    if (zend_hash_find(&EG(symbol_table), varname, varname_len + 1, (void**)&varvalue) == FAILURE) {
        php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Undefined variable: %s", varname);
        RETURN_NULL();
    }

    *return_value = **varvalue;
    zval_copy_ctor(return_value);
}

This should all be intimately familiar to you by now. The function accepts a string parameter and uses that to find a variable in the global scope which it returns as a copy.

The one new item here is php_error_docref(). You'll find this function, or a near sibling thereof, throughout the PHP source tree. The first parameter is an alternate documentation reference (the current function is used by default). Next is the ubiquitous TSRMLS_CC, followed by a severity level for the error, and finally there's a printf() style format string and associated parameters for the actual text of the error message. It's important to always provide some kind of meaningful error whenever your function reaches a failure condition. In fact, now would be a good time to go back and add an error statement to hello_array_value(). The Sanity Check section at the end of this tutorial will include these as well.

In addition to the global symbol table, the Zend Engine also keeps track of a reference to the local symbol table. Since internal functions don't have symbol tables of their own (why would they need one after all?) the local symbol table actually refers to the local scope of the userland function that called the current internal function. Let's look at a simple function which sets a variable in the local scope:

PHP_FUNCTION(hello_set_local_var)
{
    zval *newvar;
    char *varname;
    int varname_len;
    zval *value;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &varname, &varname_len, &value) == FAILURE) {
        RETURN_NULL();
    }

    ALLOC_INIT_ZVAL(newvar);
    *newvar = *value;
    zval_copy_ctor(newvar);
    zend_hash_add(EG(active_symbol_table), varname, varname_len + 1, &newvar, sizeof(zval*), NULL);

    RETURN_TRUE;
}

Absolutely nothing new here. Go ahead and build what you've got so far and run some test scripts against it. Make sure that what you expect to happen, does happen.

Reference Counting

So far, the only zvals we've added to HashTables have been newly created or freshly copied ones. They've stood alone, occupying their own resources and living nowhere but that one HashTable. As a language design concept, this approach to creating and copying variables is "good enough", but since you're accustomed to programming in C, you know that it's not uncommon to save memory and CPU time by not copying a large block of data unless you absolutely have to. Consider this userspace block of code:

<?php

    $a
= file_get_contents('fourMegabyteLogFile.log');
   
$b = $a;
    unset(
$a);

?>

If $a were copied to $b by doing a zval_copy_ctor() (which performs an estrndup() on the string contents in the process) then this short script would actually use up eight megabytes of memory to store two identical copies of the four megabyte file. Unsetting $a in the final step only adds insult to injury, since the original string gets efree()d. Had this been done in C it would have been a simple matter of: b = a; a = NULL;

Fortunately, the Zend Engine is a bit smarter than that. When $a is first created, an underlying zval is made for it of type string, with the contents of the log file. That zval is assigned to the $a variable through a call to zend_hash_add(). When $a is copied to $b, however, the Engine does something similar to the following:

{
    zval **value;

    zend_hash_find(EG(active_symbol_table), "a", sizeof("a"), (void**)&value);

    ZVAL_ADDREF(*value);

    zend_hash_add(EG(active_symbol_table), "b", sizeof("b"), value, sizeof(zval*));
}

Of course, the real code is much more complex, but the important part to focus on here is ZVAL_ADDREF(). Remember that there are four principle elements in a zval. You've already seen type and value; this time you're working with refcount. As the name may imply, refcount is a counter of the number of times a particular zval is referenced in a symbol table, array, or elsewhere.

When you used ALLOC_INIT_ZVAL(), refcount this was set to 1 so you didn't have to do anything with it in order to return it or add it into a HashTable a single time. In the code block above, you've retrieved a zval from a HashTable, but not removed it, so it has a refcount which matches the number of places it's already referenced from. In order to reference it from another location, you need to increase its reference count.

When the userspace code calls unset($a), the Engine performs a zval_ptr_dtor() on the variable. What you didn't see, in prior uses of zval_ptr_dtor(), is that this call doesn't necessarily destroy the zval and all its contents. What it actually does is decrease its refcount. If, and only if, the refcount reaches zero, does the Zend Engine destroy the zval...

Posted by kukuta
TAG extension, php

댓글을 달아 주세요

/**
 이 글의 원본(http://devzone.zend.com/node/view/id/1021)은 'Zend Devloper Zone'의 Sara Golemon님에 의해 작성 되었으며, 정확한 해석보다는 적절한 해석을 하고자 노력했습니다. 개인적으로 중간중간 별로 중요하다 싶지 않은 내용에 대해서는 건너 뛰었으니, 원본의 정확한 의미를 알고 싶으신 분들은 위의 링크를 따라 가보시는 것도 좋을 것 같습니다.
*/

Introduction
 이 튜토리얼은 PHP와 C에 대한 기본적인 지식을 가지고 있는 분들을 대상으로 합니다.
 시작하기 전에 여러분이 왜 PHP extension을 사용하려고 하는지 알아볼까요?

  1. PHP로 직접적인 호출 할 수 없는 라이브러리가 있기 때문
  2. PHP를 특이하게 사용하고 싶어서.
  3. PHP 코드의 성능개선 때문에.
  4. PHP 코드의 특정 부분을 감추고 싶어서.

What's an Extension?
 당신이 한번이라도 PHP를 사용해본 경험이 있다면, 당신은 이미 extension을 사용해 보았음이나 다름 없습니다. 몇가지 예외를 제외하고, 거의 모든 PHP의 userspace 함수들은 extension 모듈(module)에 그룹화 되어 들어 있습니다. 이 헤아릴 수 없이 많은 함수들은 '표준 익스텐션(standard extension)'이라고 불리며 총 400개가 넘습니다.

 PHP의 코어(core)는 크게 Zend Engine(ZE)과 PHP core, 두 부분으로 구성되어 있습니다. 하위 레벨에 자리 잡고 있는 ZE는 사람이 읽을 수 있는 스크립트를(human-readable script) 머신이 읽을 수 있는(machine-readable) 토큰으로 분리 하고, 각 토큰들을 프로세스 영역에서 실행하는 것을 담당합니다. 또한 메모리 매니지먼트(memory management), 변수 스코프(variable scope), 함수 호출(dispatching function calls) 등이 모두 ZE의 관리를 받습니다. 다른 절반인 PHP 코어는 SAPI(Server Application Programming Interface, also commonly used to refer to the host environment - Apache, IIS, CLI, CGI, etc) 레이어와의 통신과 바인딩을 담당합니다. 그리고 safe_mode와 open_basedir 체킹을 위한 일관성 있는 컨트롤 레이어(control layer) 뿐만 아니라 파일과 네트워크 I/O관련된 fopen(), fread(), fwrite() 유저스페이스(userspace) 함수를 제공 합니다.

Lifecycles
 SAPI가 시작 할 때, 예를 들자면 '/usr/local/apache/bin/apachectl start' 후, PHP는 각 extension의 코드를 로드하고 Module Initialization 루틴(MINIT)을 호출한다. 이 과정에서 각 extension은 내부 변수 초기화, 자원할당, 리소스 핸들러 등록과 ZE에 함수를 등록을 완료한다. 그래서 만일 스크립트에서 이 함수중 하나를 호출한다면, ZE는 어떤 함수가 실행 되어져야 할지 알 수 있다.

 다음, PHP는 페이지 처리 요청을 하기 위해서 SAPI 레이어를 기다립니다. 처리 요청 메시지가 발생하면 PHP는 ZE에게 스크립트를 실행 시킬 수 있는 환경을 만들도록 합니다. 이 때 PHP는 각 extension 모듈의 Request Initialization (RINIT) 함수를 호출 합니다. RINIT 는 특정 환경 변수를 셋팅하고, 특정 자원 할당, 또는 검사 같은 다른 작업을 수행 하기도 합니다. 예를 들어 session.auto_start 옵션이 켜져 있다면, RINIT 는 자동적으로 session_start() 함수를 호출하고 $_SESSION 변수를 미리 셋팅해 놓을 것입니다.

일단 요청이 초기화 되면, ZE는 PHP 스크립트를 토큰으로 잘게 쪼게고, 결국 opcode까지 쪼개어  opcode를 실행 시킵니다. opcode에서 extension 함수를 호출하면, ZE은 함수에 파라메터를 넘기고, 임시적으로 제어권을 extension 모듈로 넘깁니다.

스크립트의 실행이 완료되고 나면, PHP는 각 extension의 Request Shutdown (RSHUTDOWN) 함수를 호출하여 종료작업을 수행합니다(예를 들자면 세션변수를 디스크에 저장한다던지..). 다름으로 ZE가 모든 변수들에 대한 unset() 함수를 호출하면서 클린업 작업(cleanup process)을 수행 합니다(일반적으로 garbage collection 으로 잘 알려져 있지요).

모든 것이 끝나고 나면, PHP는 다른 요청을 처리하거나, shutdown 시그널을 처리 하기 위해 SAPI레이어를 기다립니다. 만일 shutdown 요청이 오면, PHP 는 각 extension의 Module Shutdown (MSHUTDOWN) 함수를 호출하고, 최종적으로 코어 서브시스템을 종료 시킵니다.

Memory Allocation
ZE는 영속성(persistence)를 표현 하는 플래그를 추가한, ZE만의 내부 메모리 관리 시스템을 가지고 있습니다. 영속적 할당(Persistent allocation) 이라는것은 단일 페이지 처리 요청보다 더 오래 지속되는 할당을 말합니다. 페이지 처리가 끝나도 이 메모리 영역은 없어지지 않습니다. 반대로 비-영속적 할당(Non-persistent allocation)이라는 것은, 페이지 처리 요청이 끝나면 자동적으로 메모리가 해제 되는 할당을 말하는 것입니다. 간단한 예를 들자면 사용자 역역의 변수(userspace variables)를 들 수 있겠습니다.

이론적으로 non-persistent 변수들의 해제가 자동적으로 이루어진다고 하더라도, 이것에만 모든 것을 맡기는 것은 그리 권장되지 않습니다. 할당된 메모리가 오랫동안 회수 되지 않은 채로 남아 있을 수도 있고, 이것이 종료 프로세스에도 영향을 미칠 수가 있습니다.

전동적으로 사용되는 메모리 할당 함수와 PHP/ZE에서 persistent, non-persistent 메모리 할당을 위해 사용되는 함수들을 비교 해보도록 하겠습니다.

Traditional Non-Persistent Persistent
malloc(count)
calloc(count, num)
emalloc(count)
ecalloc(count, num)
pemalloc(count, 1)*
pecalloc(count, num, 1)
strdup(str)
strndup(str, len)
estrdup(str)
estrndup(str, len)
pestrdup(str, 1)
pemalloc() & memcpy()
free(ptr) efree(ptr) pefree(ptr, 1)
realloc(ptr, newsize) erealloc(ptr, newsize) perealloc(ptr, newsize, 1)
malloc(count * num + extr)** safe_emalloc(count, num, extr) safe_pemalloc(count, num, extr)
* pemalloc() 함수군들은 'persistent' 플래그를 포함하고 있습니다. 예를 들어 emalloc(1234) 는  pemalloc(1234, 0)과 같습니다.
** safe_emalloc() 과 (in PHP 5) safe_pemalloc() 는 전수 오버플로우(integer overflows)를 대비하기 위해서 체크하는 작업을 추가로 하고 있습니다.


Settting Up a Build Environment
모든 작업들에 앞서, PHP가 먼저 설치 외어 있어야 합니다. 만일 여러분이 소스로 PHP를 설치하는 것이 어색하시다면 http://www.php.net/install.unix 를 살펴 보실 것을 권합니다. (윈도우에서의 extension개발은 다음장에서 다루도록 하겠습니다). 바이너리 버젼을 사용하는 것이 더 편하기는 하지만, 두 가지의 중요한 도움을 받을 수 없다는 단점이 있습니다. 첫번째는 --enable-debug. 이 옵션은 php를 컴파일 할 시 추가적인 심볼 정보를 이용하여 세그폴트가 발생할 경우 코어 덤프를 얻을 수 있습니다. 다른 옵션은 버젼에 따라 다른데, PHP 4.3에서는 이 옵션의 이름이 --enable-experimental-zts 이고, PHP 5 혹은 그 이후 버젼에서는  --enable-maintainer-zts 입니다. 이 옵션은 여러분이 단일 비-쓰레드 환경에서는 아무렇지 않지만 멀티 쓰레드 환경에서는 치명적일 수가 있는 실수들을 잡아 낼 수 있도록 도와 줍니다.

Hello World
이번 장에서는 전통적인 관례에 따라 "Hello world"라는 문자열을 리턴하는 extension함수를 하나 만들어 보도록 하겠습니다. 먼저 여러분이 PHP만을 이용해 코드를 작성한다면 아래와 같이 작성 할 수 있을 겁니다.

<?php

function hello_world() {
    return 'Hello World';
}

?>

이제 PHP extension으로 변경을 해보도록 하겠습니다. 처음 할 일은 PHP 소스 디렉토리 내부의 ext/ 디렉토리에 'hello'라는 디렉토리를 생성하는 것입니다. 사실 어느곳에 위치하더라도 상관은 없습니다만 일단은 이곳에 만들기를 권하겠습니다. 이제 이 'hello' 디렉토리 아래에 세개의 파일을 만들어야 합니다. hello_world 함수를 가지고 있는 소스 파일인 'hello.c', PHP가 extension을 로드하는데 사용되는 레퍼런스를 기록하고 있는 헤더 파일 'php_hello.h'. 그리고 컴파일 설정을 만들어 내기 위한 설정파일. 이 설정파일은 phpize 에 의해 사용됩니다.

config.m4

PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello   Enable Hello World support])
if test "$PHP_HELLO" = "yes"; then
  AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
  PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi

php_hello.h

#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1
#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"
PHP_FUNCTION(hello_world);
extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry
#endif

hello.c

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_hello.h"
static function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    {NULL, NULL, NULL}
};

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif
PHP_FUNCTION(hello_world)
{
    RETURN_STRING("Hello World", 1);
}

 위에서 보이는 대부분의 코드는 단지 extension을 PHP가 알아 볼 수 있도록 해주는 glue-protocol(다른 언어간에 호환성을 가지게 할 수있도록 도와 주는 프로토콜) 입니다. 우리가 관심을 가지고 보아야 하는 코드는 맨 아래의 네줄입니다. 정확하게는 아니지만 왠지 그 의미는 이해 할 수있을 것 같지 안나요? 간단히 의미를 따라가자면 :

  1. hello_world 라고 함수 이름을 선언 합니다.
  2. 함수는 "Hello World" 라는 문자열을 리턴 합니다.
  3. 흠..'1'? 이게 뭘까요?

ZE가 메모리를 관리 하는 방법을 다시 상기 시켜보자면, ZE는 스크립트 페이지에 대한 처리가 끝나면 자연적으로 변수들을 해제하는 메커니즘을 가지고 있습니다. 일반적으로 메모리를 해제 할 때 주로 하는 실수 중에 한가지가 이미 해제 했던 메모리를 또 한번 더 해제하는 경우가 있습니다. 이런 경우 세그폴트를 내면서 프로그램은 죽게 됩니다. 이것과 비슷하게 동적으로 할당 되지 않은 메모리를 해제 하려는 경우도 있습니다. 위 예제의 "Hello World"는 동적으로 메모리를 할당한 것이 아니지요. 만일 자동으로 변수들의 메모리를 해제하는 메커니즘에 따라 ZE가 "Hello world"역역의 메모리를 해제 하려 들면 오류가 발생하기 때문에 이것을 방지 하기 위해서 두번째 인자를 1(true)로 셋팅하게 되면, RETURN_STRING()으로 넘어가는 문자열은 추후 안전한 삭제를 위해서 복사되어 전달됩니다. 하지만 이미 메모리가 할당 된 후 파라메터로 넘어 오는 것도 종종 발생하지요. 그리고 ZE는 이것에 대해서 구분 할 방법이 없습니다. 이러한 이유로 RETURN_STRING() 은 우리에게 문자열에 대한 복사본이 필요한지 그렇지 않은지에 대한 지정을 할 수 있도록 한 것입니다. 파라메터로 넘어가는 문자열에 대한 복사가 필요하지 않은 경우를 이해하기 위해 아래의 코드를 살펴 보도록 하겠습니다.:

PHP_FUNCTION(hello_world)
{
    char *str;
    str = estrdup("Hello World");
    RETURN_STRING(str, 0);
}

 이 버젼에서 "Hello world"는 동적으로 할당 되어 졌고, RETURN_STRING()함수에 넘겨 질때는 두번째 인자가 0으로 셋팅되어 넘어갔습니다. 이럴경우 ZE는 문자열을 복사하지 않고, 나중에 자동적인 메모리 해제 메커니즘이 돌았을 때도 정상적인 해제 프로세스가 진행 될 수 있는 것입니다.

Building Your Extension
이제 마지막 단계 입니다. 여러분이 위의 세개의 코드를 잘 복사 했다면 아래의 세개의 명령을 ext/hello 디렉토리에서 실행 해보세요 :

$ phpize
$ ./configure --enable-hello
$ make

각 명령들이 정상적으로 수행 되었다면 ext/hello/modules/ 라는 디렉토리 밑에 hello.so라는 파일이 생성 되었을 것입니다. 이 파일을 여러분의 PHP extension 디렉토리로 복사하고(기본적으로 /usr/local/lib/php/extensions/ 로 지정되어 있습니다. php.ini  파일을 확인하세요) 시작시 이 모듈을 로드하기 위해 'php.ini'파일에 extension=hello.so 라는 라인을 추가하세요. 이제 아래의 명령을 실행 해 보세요:

$ php -r 'echo hello_world();'

모든 것이 정상적으로 진행되었다면, "Hello World"라는 문구를 볼 수 있을 겁니다.

이와 비슷하게 생긴 다른 리턴 함수들도 있도 있습니다. 정수를 리턴하기 위해서는 RETURN_LONG()를, 실수를 리턴하기 위해서는 RETURN_DOUBLE()를, true/false의 불리언 변수를 위해서는 RETURN_BOOL()를, NULL을 리턴하기 위해서는 RETURN_NULL()를 사용합니다. 'function_entry' 구조체에 각 액션에 대한 'PHP_FE()' 라인을 추가 하면서 이 기능들을 확인해 보도록 합시다. 그리고 파일 끝에 PHP_FUNCTION()들을 추가해야 하는것도 잊어서는 안되겠지요.

static function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    PHP_FE(hello_long, NULL)
    PHP_FE(hello_double, NULL)
    PHP_FE(hello_bool, NULL)
    PHP_FE(hello_null, NULL)
    {NULL, NULL, NULL}
};
PHP_FUNCTION(hello_long)
{
    RETURN_LONG(42);
}
PHP_FUNCTION(hello_double)
{
    RETURN_DOUBLE(3.1415926535);
}
PHP_FUNCTION(hello_bool)
{
    RETURN_BOOL(1);
}
PHP_FUNCTION(hello_null)
{
    RETURN_NULL();
}

위의 작업과 함께, 함수들의 원형을 'php_hello.h'에 추가하는 작업을 잊어서는 안되겠습니다. :

PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);

수정이 끝나고 나면 다시 빌드를 해야 합니다. 실질적으로 'config.m4'에 대한 변경은 아무것도  하지 않았으므로 'phpize'와 './configure' 과정을 생략하고 바로 'make'를 하도록 합니다. 하지만, 정확한 빌드를 위해 처음 부터 차근히 빌드를 다시 시작 할 것을 권하고 싶군요. 참고적으로 'make'에 앞서 'make clean'을 반드시 실행 할 것을 권장합니다. 모든 빌드 과정이 끝나고 나면 새로 생성된 'helllo.so'를 extension 디렉토리로 복사해 새로운 버젼의 모듈로 교채 합니다.

At this point you could call the PHP interpreter again, passing it simple scripts to test out the functions you just added. In fact, why don't you do that now? I'll wait here...

끝났나요? 좋습니다. 결과를 출력하기 위해 'echo' 함수 대신 'var_dump' 함수를 사용한다면 'hello_bool'함수가 'true'를 리턴하는 것을 보실 수 있을 겁니다. 'RETURN_BOOL'에서 1의 의미가 바로 'true'였기 때문이죠. 관례적으로 'true'에는 1을 'false'에는 0을 사용하기는 하지만 그것에 너무 얽메일 필요는 없습니다. 가독성(readability)을 위해서 'RETURN_TRUE'와 'RETURN_FALSE'라는 매크로도 준비되어 있습니다. 다시 한번 'hello_bool'함수로 돌아가서 동일한 의미를 가지는 'RETURN_TRUE'를 사용하는 코드를 살펴 보도록 하겠습니다 :

PHP_FUNCTION(hello_bool)
{
    RETURN_TRUE;
}

이 함수는 이전에 나왔던 'RETURN_STRING()'과는 달리 리턴되는 변수를 복사 해야 하는지 그렇지 않은지를 지정하는 플래그가 없다는 사실에 유의하세요. 동적으로 할당 되는 가능성이 없으니 당연한 현상입니다. 이에 대한 보다 자세한 내용은 Part 2에서 다뤄 보도록 하겠습니다.

리턴 타입은 'mysql_connect()', fsockopen()', 'ftp_connect()'와 같은 함수에 의해 리턴 되는 'RESOURCE', 해쉬로 잘 알려진 'ARRAY', new를 사용해 리턴되는 'OBJECT' 이렇게 세 가지로 분류가 가능합니다. 이에 대한 자세한 사항은 Part II에서 변수를 다룰 때 좀 더 깊게 알아 보도록 하겠습니다.

INI Settings

The Zend Engine provides two approaches for managing INI values. We'll take a look at the simpler approach for now, and explore the fuller, but more complex, approach later on, when you've had a chance to work with global values.

Let's say you want to define a php.ini value for your extension, hello.greeting, which will hold the value used to say hello in your hello_world() function. You'll need to make a few additions to hello.c and php_hello.h while making a few key changes to the hello_module_entry structure. Start off by adding the following prototypes near the userspace function prototypes in php_hello.h:

PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);

PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);

Now head over to hello.c and take out the current version of hello_module_entry, replacing it with the following listing:

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    PHP_MINIT(hello),
    PHP_MSHUTDOWN(hello),
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
PHP_INI_END()

PHP_MINIT_FUNCTION(hello)
{
    REGISTER_INI_ENTRIES();

    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(hello)
{
    UNREGISTER_INI_ENTRIES();

    return SUCCESS;
}

Now, you just need to add an #include to the rest of the #includes at the top of hello.c to get the right headers for INI file support:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "php_hello.h"

Finally, you can modify your hello_world function to use the INI value:

PHP_FUNCTION(hello_world)
{
    RETURN_STRING(INI_STR("hello.greeting"), 1);
}

Notice that you're copying the value returned by INI_STR(). This is because, as far as the PHP variable stack is concerned, this is a static string. In fact, if you tried to modify the string returned by this value, the PHP execution environment would become unstable and might even crash.

The first set of changes in this section introduced two methods you'll want to become very familiar with: MINIT, and MSHUTDOWN. As mentioned earlier, these methods are called during the initial startup of the SAPI layer and during its final shutdown, respectively. They are not called between or during requests. In this example you've used them to register the php.ini entries defined in your extension. Later in this series, you'll find how to use the MINIT and MSHUTDOWN functions to register resource, object, and stream handlers as well.

In your hello_world() function you used INI_STR() to retrieve the current value of the hello.greeting entry as a string. A host of other functions exist for retrieving values as longs, doubles, and Booleans as shown in the following table, along with a complementary ORIG counterpart which provides the value of the referenced INI setting as it was set in php.ini (before being altered by .htaccess or ini_set() statements).

Current Value Original Value Type
INI_STR(name) INI_ORIG_STR(name) char * (NULL terminated)
INI_INT(name) INI_ORIG_INT(name) signed long
INI_FLT(name) INI_ORIG_FLT(name) signed double
INI_BOOL(name) INI_ORIG_BOOL(name) zend_bool

The first parameter passed to PHP_INI_ENTRY() is a string containing the name of the entry to be used in php.ini. In order to avoid namespace collisions, you should use the same conventions as with your functions; that is, prefix all values with the name of your extension, as you did with hello.greeting. As a matter of convention, a period is used to separate the extension name from the more descriptive part of the ini setting name.

The second parameter is the initial value, and is always given as a char* string regardless of whether it is a numerical value or not. This is due primarily to the fact that values in an .ini file are inherently textual - being a text file and all. Your use of INI_INT(), INI_FLT(), or INI_BOOL() later in your script will handle type conversions.

The third value you pass is an access mode modifier. This is a bitmask field which determines when and where this INI value should be modifiable. For some, such as register_globals, it simply doesn't make sense to allow the value to be changed from within a script using ini_set() because the setting only has meaning during request startup - before the script has had a chance to run. Others, such as allow_url_fopen, are administrative settings which you don't want to allow users on a shared hosting environment to change, either via ini_set() or through the use of .htaccess directives. A typical value for this parameter might be PHP_INI_ALL, indicating that the value may be changed anywhere. Then there's PHP_INI_SYSTEM|PHP_INI_PERDIR, indicating that the setting may be changed in the php.ini file, or via an Apache directive in a .htaccess file, but not through the use of ini_set(). Or there's PHP_INI_SYSTEM, meaning that the value may only be changed in the php.ini file and nowhere else.

We'll skip the fourth parameter for now and only mention that it allows the use of a callback method to be triggered whenever the ini setting is changed, such as with ini_set(). This allows an extension to perform more precise control over when a setting may be changed, or trigger a related action dependant on the new setting.

Global Values

만일 PHP를 사용하다 특정 변수 혹은 인스턴스를 리퀘스트와는 상관 없이 계속 유지 해야  하는 경우가 있다면 본 장에서 설명하는 global values를 사용하면 된다. PHP는 멀티쓰레드 웹서버에서 구동 되도록 설계 되었기 때문에(Apache 2 and IIS),  전역변수에 대한 쓰레드 세이프를 지켜 줘야 한다.  PHP greatly simplifies this by using the TSRM (Thread Safe Resource Management) abstraction layer, sometimes referred to as ZTS (Zend Thread Safety). In fact, by this point you've already used parts of TSRM and didn't even know it. (Don't search too hard just yet; as this series progresses you'll come to discover it's hiding everywhere.)

The first part of creating a thread safe global is, as with any global, declaring it. For the sake of this example, you'll declare one global value which will start out as a long with a value of 0. Each time the hello_long() function is called you'll increment this value and return it. Add the following block of code to php_hello.h just after the #define PHP_HELLO_H statement:

#ifdef ZTS
#include "TSRM.h"
#endif

ZEND_BEGIN_MODULE_GLOBALS(hello)
    long counter;
ZEND_END_MODULE_GLOBALS(hello)

#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif

You're also going to use the RINIT method this time around, so you need to declare its prototype in the header:

PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);

Now let's go over to hello.c and add the following just after your include block:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "php_hello.h"

ZEND_DECLARE_MODULE_GLOBALS(hello)

Change hello_module_entry by adding PHP_RINIT(hello):

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    PHP_MINIT(hello),
    PHP_MSHUTDOWN(hello),
    PHP_RINIT(hello),
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

And modify your MINIT function, along with the addition of another couple of functions, to handle initialization upon request startup:

static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
}

PHP_RINIT_FUNCTION(hello)
{
    HELLO_G(counter) = 0;

    return SUCCESS;
}

PHP_MINIT_FUNCTION(hello)
{
    ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);

    REGISTER_INI_ENTRIES();

    return SUCCESS;
}

Finally, you can modify the hello_long() function to use this value:

PHP_FUNCTION(hello_long)
{
    HELLO_G(counter)++;

    RETURN_LONG(HELLO_G(counter));
}

In your additions to php_hello.h, you used a pair of macros - ZEND_BEGIN_MODULE_GLOBALS() and ZEND_END_MODULE_GLOBALS() - to create a struct named zend_hello_globals containing one variable of type long. You then conditionally defined HELLO_G() to either fetch this value from a thread pool, or just grab it from a global scope - if you're compiling for a non-threaded environment.

In hello.c you used the ZEND_DECLARE_MODULE_GLOBALS() macro to actually instantiate the zend_hello_globals struct either as a true global (if this is a non-thread-safe build), or as a member of this thread's resource pool. As extension authors, this distinction is one we don't need to worry about, as the Zend Engine takes care of the job for us. Finally, in MINIT, you used ZEND_INIT_MODULE_GLOBALS() to allocate a thread safe resource id - don't worry about what that is for now.

You may have noticed that php_hello_init_globals() doesn't actually do anything, yet we went to the trouble of declaring RINIT to initialize the counter to 0. Why?

The key lies in when the two functions are called. php_hello_init_globals() is only called when a new process or thread is started; however, each process can serve more than one request, so using this function to initialize our counter to 0 will only work for the first page request. Subsequent page requests to the same process will still have the old counter value stored here, and hence will not start counting from 0. To initialize the counter to 0 for every single page request, we implemented the RINIT function, which as you learned earlier is called prior to every page request. We included the php_hello_init_globals() function at this point because you'll be using it in a few moments, but also because passing a NULL to ZEND_INIT_MODULE_GLOBALS() for the init function will result in a segfault on non-threaded platforms.

INI Settings as Global Values

If you recall from earlier, a php.ini value declared with PHP_INI_ENTRY() is parsed as a string value and converted, as needed, to other formats with INI_INT(), INI_FLT(), and INI_BOOL(). For some settings, that represents a fair amount of unnecessary work duplication as the value is read over and over again during the course of a script's execution. Fortunately it's possible to instruct ZE to store the INI value in a particular data type, and only perform type conversions when its value is changed. Let's try that out by declaring another INI value, a Boolean this time, indicating whether the counter should increment, or decrement. Begin by changing the MODULE_GLOBALS block in php_hello.h to the following:

ZEND_BEGIN_MODULE_GLOBALS(hello)
    long counter;
    zend_bool direction;
ZEND_ENG_MODULE_GLOBALS(hello)

Next, declare the INI value itself by changing your PHP_INI_BEGIN() block thus:

PHP_INI_BEGIN()
    PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
    STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()

Now initialize the setting in the init_globals method with:

static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
    hello_globals->direction = 1;
}

And lastly, use the value of the ini setting in hello_long() to determine whether to increment or decrement:

PHP_FUNCTION(hello_long)
{
    if (HELLO_G(direction)) {
        HELLO_G(counter)++;
    } else {
        HELLO_G(counter)--;
    }

    RETURN_LONG(HELLO_G(counter));
}

And that's it. The OnUpdateBool method you specified in the INI_ENTRY section will automatically convert any value provided in php.ini, .htaccess, or within a script via ini_set() to an appropriate TRUE/FALSE value which you can then access directly within a script. The last three parameters of STD_PHP_INI_ENTRY tell PHP which global variable to change, what the structure of our extension globals looks like, and the name of the global scope container where they're contained.

Sanity Check

By now our three files should look similar to the following listings. (A few items have been moved and grouped together, for the sake of readability.)

config.m4

PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello   Enable Hello World support])

if test "$PHP_HELLO" = "yes"; then
  AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
  PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi

php_hello.h

#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1

#ifdef ZTS
#include "TSRM.h"
#endif

ZEND_BEGIN_MODULE_GLOBALS(hello)
    long counter;
    zend_bool direction;
ZEND_END_MODULE_GLOBALS(hello)

#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif

#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"

PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);

PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);

extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry

#endif

hello.c

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "php_hello.h"

ZEND_DECLARE_MODULE_GLOBALS(hello)

static function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    PHP_FE(hello_long, NULL)
    PHP_FE(hello_double, NULL)
    PHP_FE(hello_bool, NULL)
    PHP_FE(hello_null, NULL)
    {NULL, NULL, NULL}
};

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    PHP_MINIT(hello),
    PHP_MSHUTDOWN(hello),
    PHP_RINIT(hello),
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif

PHP_INI_BEGIN()
    PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
    STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()

static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
    hello_globals->direction = 1;
}

PHP_RINIT_FUNCTION(hello)
{
    HELLO_G(counter) = 0;

    return SUCCESS;
}

PHP_MINIT_FUNCTION(hello)
{
    ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);

    REGISTER_INI_ENTRIES();

    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(hello)
{
    UNREGISTER_INI_ENTRIES();

    return SUCCESS;
}

PHP_FUNCTION(hello_world)
{
    RETURN_STRING("Hello World", 1);
}

PHP_FUNCTION(hello_long)
{
    if (HELLO_G(direction)) {
        HELLO_G(counter)++;
    } else {
        HELLO_G(counter)--;
    }

    RETURN_LONG(HELLO_G(counter));
}

PHP_FUNCTION(hello_double)
{
    RETURN_DOUBLE(3.1415926535);
}

PHP_FUNCTION(hello_bool)
{
    RETURN_BOOL(1);
}

PHP_FUNCTION(hello_null)
{
    RETURN_NULL();
}

What's Next?

In this tutorial we explored the structure of a simple PHP extension which exported functions, returned values, declared INI settings, and tracked its internal state during the course of a request.

In the next session we'll explore the internal structure of PHP variables, and how they're stored, tracked, and maintained within a script environment. We'll use zend_parse_parameters to receive parameters from a program when a function is called, and explore ways to return more complicated results, including the array, object, and resource types mentioned in this tutorial.

http://blog.naver.com/evonit?Redirect=Log&logNo=80001210173
http://devzone.zend.com/node/view/id/1021

Posted by kukuta
TAG extension, php

댓글을 달아 주세요

  1. 조성진 2008.03.13 22:57  댓글주소  수정/삭제  댓글쓰기

    php extension 좋은 번역 감사합니다.

    • Favicon of https://kukuta.tistory.com BlogIcon kukuta 2008.03.13 23:39 신고  댓글주소  수정/삭제

      헛..좋은 번역이라니요..다 완성하지도 못했는데
      민망합니다.

      오늘은 댓글 풍년이군요. 하루에 댓글이 두 개나 달렸어요~

  2. 아리랑 2010.10.25 18:26  댓글주소  수정/삭제  댓글쓰기

    정말 좋은 글이였습니다.

  3. 아리랑 2010.10.25 18:27  댓글주소  수정/삭제  댓글쓰기

    정말 좋은 글이였습니다.

 일반적으로 UNIX/Linux 환경에서 간단한 일들을 처리 하기 위해 쉘 스크립트를 많이 사용하게 된다. 하지만 C++같은 문법에만 익숙해져 있는 사람들은 아무래도 쉘 스크립트를 사용하는데 어색함이 없지 않아 있다.  하지만 PHP의 경우에는 C와 문법이 거의 유사하므로 작성하는데 어색함이 그리 크게 느껴지지 않는다.
 
이 문서는 PHP를 쉘 스크립트로 이용 할 수 있는 방법에 대해서 간단히 기술 하도록 하겠다.

1. php를 쉘 스크립트로 사용하기 위한 요구 사항
php를 쉘 언어로 사용하기 위해서는 아파치 모듈 대신 CGI 바이너리로 설치 해야 한다.

2. #!/usr/local/bin/php -q 선언
일반적인 PHP 페이지와 PHP 쉘 스크립트 사이의 단 한가지 차이점은 PHP스크립트의 제일 첫 번째 줄에 위의 제목과 같이 쉘 호출을 해주어야 하는 점이다. 첫 번째 줄의 '#!'은 주석을 나타냄과 동시에 이 쉘 스크립트가 실행 되면서 이용하게 되는 바이너리 엔진의 위치와 옵션을 타나낸다. (사용하게 될 바이너리의 위치는 사용자의 환경에 따라 다르니 자신의 php 엔진이 어느 디렉토리에 있는지 확인 하도록 한다. 'whereis' 같은 명령어가 도움이 될 것이다.)

-q 옵션은 HTTP헤더를 사용하지 않겠다는 뜻이며 나머지 보다 자세한 사항은 php 도움말을 참조 하도록 한다.
그 외의 부분은 일반적인 php 파일을 작성하는 것과 전혀 다르지 않다.

#!/usr/local/bin/php -q
<?php
   .....
?>

3. 쉘 스크립트에 매개변수 전달하기
일반적으로 쉘 스크립트는 프로세스를 실행 시키면서 매개변수를 전달하는 경우가 많다. php 스크립트에서 매개변수를 전달하는 것은 $argv 배열을 통해서 이루어 진다. $argv 배열을 사용하기 위해서는 php.ini의 register_argc_argv , register_globals 항목들을 On으로 셋팅 해주어야 한다.

register_argc_argv = On
register_globals = On

 아래 스크립트 예제는 프로세스 시작시 인자로 받은 두 변수를 출력 할 것이다.

#!/usr/local/bin/php -q
<?php
    $first_name = $argv[1];
    $last_name = $argv[2];
    print("Hello, $first_name $last_name! How are you today?n");
?>

4. 프로그램 실행 하기 

$ phpShellTest.ph arg1 arg2

결과는 알아서 확인 하도록 하자.

Ref
 http://blog.naver.com/leons78/130006617748

'도구의발견' 카테고리의 다른 글

putty 한글 사용하기  (0) 2008.02.20
CppUnit Cookbook  (0) 2008.01.28
PHP - 쉘 스크립트로 활용 하기  (5) 2007.11.03
vim 문자열 검색 및 치환  (2) 2007.10.25
서비스 포트 변경  (0) 2007.05.09
gcc 라이브러리 만들기와 사용  (0) 2007.04.24
Posted by kukuta

댓글을 달아 주세요

  1. Favicon of http://www.xinublog.com BlogIcon Andrew 2008.06.04 00:43  댓글주소  수정/삭제  댓글쓰기

    관련해서 참고글 남깁니다. php로 쉘 스크립트를 짠다면 php cli를 이용하겠지요. ^^
    http://kirrie.springnote.com/pages/689575

  2. 안녕하세요 2014.09.04 17:58  댓글주소  수정/삭제  댓글쓰기

    오 에러하나 없앴어요
    감사합니다.
    담아갈게요~~


php는 아주 간편하게 사용할 수 있는 스크립트 언어다. 문법도 C랑 비슷해서 새로 공부해야 할 것도 그렇게 많지는 않다. 하지만 그 간편함 속에 숨겨진 php의 악마성이 있으니...

그것은 바로 문자든 숫자든 구분을 하지 않는다는 것이다...

먼저 아래의 코드를 보자. 디렉토리에 있는 내용을 단순히 출력 해주는 루틴이다.

$dirHandle = opendir("/home/kukuta/") or die("error opendir()")

while($filename = readdir($dirHandle))
{
    echo $filename."\n";
}
정말 간단한 소스다. 그냥 단순히 읽고 출력하는 것이 전부다.

하지만 만일 /home/kukuta 디렉토리에 파일이름이 '0' 인 파일이 들어 있다고 한다면, 상황은 어떻게 변할까?
filename 변수는 0으로 채워질 것이고 while문에서는 filename이 0이기 때문에 문자열이든 숫자이든 상관하지 않고 루프를 끝내 버릴것이다.

물론 파일이름이 0으로 될일은 거의 없지만 이런 상황이 없으리라는 법은 없다. 그래서 위의 소스를 아래와 같이 수정 해보았다.
$dirHandle = opendir("/home/kukuta/") or die("error opendir()")

while(false !==($filename = readdir($dirHandle)))
{
    echo $filename."\n";
}
이렇게 수정 된다면 파일이름이 뭐가 나오든 opendir()이나 readdir()함수가 실패 하지 않는다면 파일들을 출력하다가 중간에 멈추는 일은 없을 것이다.

참고 서적 : PHP4 웹프로그래밍 가이드 : 마이트press : 이승혁 저
               (위의 오류에 소지가 있는 코드가 쓰여져 있는 책이다 -_-)

'진리는어디에' 카테고리의 다른 글

Edge trigger vs Level trigger  (2) 2007.01.05
const vs mutable  (4) 2006.12.21
PHP - 문자열 취급하기  (0) 2006.12.14
가변인자를 이용한 함수(va_list)  (10) 2006.12.13
SQL command  (0) 2006.12.13
Writing a Windows Service Program  (8) 2006.12.11
Posted by kukuta
TAG php

댓글을 달아 주세요