메인 컨텐츠로 가기

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

리눅스의 x86 용 인라인 어셈블리(Inline assembly) (한글)

조각 맞추기

Bharata RaoIBM Linux Technology Center, IBM Software Labs, India
Bharata B. Rao는 인도 Mysore University에서 전자 통신 엔지니어링을 전공했다. 1999년부터 IBM Global Services에서 일하고 있다. IBM Linux Technology Center의 멤버로서, 리눅스 RAS (Reliability, Availability, Serviceability) 분야를 담당하고 있다. 운영 체계 내부 구조와 프로세스 아키텍처에 관심이 많다. (rbharata@in.ibm.com)

요약:  Bharata B. Rao는 리눅스 플랫폼 상에서 x86용 인라인 어셈블리의 사용법과 구조를 설명합니다. 인라인 어셈블리의 기초와 다양한 사용법을 설명하고, 기본적인 인라인 어셈블리 코딩 가이드도 제시하며, 리눅스 커널에서 인라인 어셈블리 코드의 인스턴스도 설명합니다.

원문 게재일:  2007 년 11 월 27 일
난이도:  고급 원문:  보기
페이지뷰:  2763 회
의견:  


여러분이 리눅스 커널 개발자라면, 아키텍처에 의존적인 함수를 코딩하거나, 코드 경로를 최적화 하는 일을 많이 하게 된다. 어셈블리 언어 명령어를 C 문장(인라인 어셈블리로 알려진 메소드) 중간에 삽입하는 것이 대부분이다. 리눅스에서 인라인 어셈블리의 사용법에 대해 알아보자. (이 글에서는 IA32 어셈블리에 대해서만 다루겠다.)

GNU 어셈블러 문법

먼저, 리눅스에 사용되는 기본적인 어셈블러 문법에 대해 알아보자, GCC(리눅스용 GNU C Compiler)는 AT&T 어셈블리 문법을 사용한다. 이 문법의 기본 규칙들 중 일부는 아래 소개된 것과 같다. (이 리스트가 결코 완벽한 것은 아니다. 인라인 어셈블리와 관련된 규칙들만 포함시켰다.)

레지스터 네이밍
레지스터 이름 앞에는 %가 붙는다. eax가 사용되어야 한다면, %eax로 해야 한다.

소스 및 목적지 순서
명령어에서, 소스가 먼저 오고 목적지가 뒤따른다. 이것은 Intel 문법과는 다르다. Intel 에서는 소스가 목적지 뒤에 온다.

    mov %eax, %ebx, transfers the contents of eax to ebx.

피연산자의 크기
피연산자 byte, word, long에 따라서, 명령어 뒤에 b, w, l이 접미사로 붙는다. 의무적인 것은 아니다. GCC는 피연산자를 읽음으로써 올바른 접미사를 제공하려고 할 것이다. 접미사를 수동으로 설정하면 코드의 가독성을 높이고, 컴파일러가 잘못 추측할 가능성을 줄인다.

    movb %al, %bl -- Byte move
	movw %ax, %bx -- Word move
	movl %eax, %ebx -- Longword move

Immediate operand
Immediate operand는 $를 사용하여 지정된다.

    movl $0xffff, %eax -- will move the value of 0xffff into eax register.

간접 메모리 참조
메모리의 간접 참조는 ( )를 사용한다.

 movb (%esi), %al -- will transfer the byte in the memory 
                     pointed by esi into al  register


Inline assembly

GCC는 인라인 어셈블리를 위해 특별한 구조체 "asm"을 제공하는데, 다음과 같은 포맷이다.

			
		
	asm ( assembler template
	    : output operands			 	(optional)
	    : input operands			 	(optional)
	    : list of clobbered registers 	  	(optional)
	    );	

이 예제에서, 어셈블러 템플릿은 어셈블리 명령어로 구성된다. 인풋 피연산자는 명령어에 대한 인풋 피연산자로 작동하는 C 식이다. 아웃풋 피연산자는 어셈블리 명령어의 아웃풋이 수행될 C 식이다.

    asm ("movl %%cr3, %0\n" :"=r"(cr3val));
 

  a	%eax
  b 	%ebx
  c 	%ecx
  d 	%edx
  S	%esi
  D	%edi

메모리 피연산자 제약 조건(m)
피연산자가 메모리에 있을 때, 이에 대해 수행되는 연산은 메모리 위치에서 직접 발생한다. 이것은 먼저 값을 레지스터에 저장하여 수정되도록 한 다음, 이것을 다시 메모리 위치에 작성하는 레지스터 제약 조건에는 반대된다. 레지스터 제약 조건은 명령어에 반드시 필요할 경우 또는 프로세스의 속도를 많이 높일 경우에만 사용된다. 메모리 제약 조건은 C 변수가 "asm" 내에서 업데이트 되어야 하고, 레지스터에 값을 저장하고 싶지 않을 경우에 가장 효율적으로 사용된다. 예를 들어, idtr의 값은 메모리 위치 loc에 저장된다:

     ("sidt %0\n" : :"m"(loc));
  

매칭(Digit) 제약 조건
어떤 경우, 하나의 변수가 인풋과 아웃풋 피연산자로 작용한다. 이 같은 경우는 매칭 제약 조건을 사용하여 "asm"으로 지정된다.

    asm ("incl %0" :"=a"(var):"0"(var));

우리 예제에서는, 레지스터 %eax가 인풋과 아웃풋 변수로서 사용된다. var 인풋은 %eax에 읽히고, 업데이트 된 %eax는 증분 후에 var에 저장된다. 여기에서 "0"은 0번째 아웃풋 변수와 같은 제약 조건을 지정한다. var의 아웃풋 인스턴스가 %eax에만 저장되어야 한다는 것을 지정한다. 이러한 제약 조건은 다음과 같은 경우에 사용될 수 있다.

  • 인풋이 변수에서 읽히거나, 변수가 수정되고 수정이 다시 같은 변수에 작성될 경우
  • 인풋과 아웃풋 피연산자의 개별 인스턴스들이 필요하지 않을 경우

매칭 제약 조건이 가장 큰 효과는 가용 레지스터들을 효율적으로 사용할 수 있다는 점이다.


일반적인 인라인 어셈블리 사용 예제

다음 예제에서는 다양한 피연산자 제약 조건들의 사용법을 설명한다. 너무 많은 제약 조건들이 있어서 다 설명할 수 없지만, 지금 소개하는 것은 가장 자주 사용되는 제약 조건 유형들이다.

"asm"과 레지스터 제약 조건 "r"
먼저, 레지스터 제약 조건 'r'을 가진 "asm"을 보자. 우리 예제는 GCC가 레지스터를 할당하는 방법과 이것이 아웃풋 변수의 값을 업데이트 하는 방법을 설명한다.

		
	
int main(void)
{
	int x = 10, y;
	
	asm ("movl %1, %%eax;
	     "movl %%eax, %0;"
		:"=r"(y)	/* y is output operand */
		:"r"(x)		/* x is input operand */
		:"%eax");	/* %eax is clobbered register */
}


이 예제에서, x의 값은 "asm" 안에 있는 y로 복사된다. x와 y는 레지스터에 저장됨으로써 "asm"으로 전달된다. 이 예제를 위해 생성된 어셈블리 코드는 다음과 같다:

		
	
    main:
        pushl %ebp
        movl %esp,%ebp
        subl $8,%esp
        movl $10,-4(%ebp)	 
        movl -4(%ebp),%edx	/* x=10 is stored in %edx */
#APP	/* asm starts here */	
        movl %edx, %eax		/* x is moved to %eax */
        movl %eax, %edx		/* y is allocated in edx and updated */

#NO_APP	/* asm ends here */
        movl %edx,-8(%ebp)	/* value of y in stack is updated with 
				   the value in %edx */ 

GCC는 "r" 제약 조건이 사용될 때 레지스터를 자유롭게 할당한다. 우리 예제에서는 x를 저장할 때 %edx를 선택했다. %edx에서 x의 값을 읽은 후에, y에도 같은 레지스터를 할당했다.

y가 아웃풋 피연산자 섹션에 지정되기 때문에, %edx에 업데이트 된 값은 스택 상의 y의 위치인 -8(%ebp)에 저장된다. y가 인풋 섹션에 지정되었다면, y(%edx)의 임시 레지스터 스토리지에서 업데이트 되더라도, 스택 상의 y의 값은 업데이트 되지 않는다.

%eax가clobbered 리스트에 지정되기 때문에, GCC는 데이터를 저장할 때 다른 곳에서 이것을 사용하지 않는다.

인풋 x와 아웃풋 y가 같은 %edx 레지스터에서 할당되었고, 아웃풋이 생성되기 전에 인풋이 소비된 것으로 간주한다. 여러분이 많은 명령어를 갖고 있다면, 상황은 달라진다. 인풋과 아웃풋이 다른 레지스터에 할당되었는지를 확인하려면, & 제약 조건 수정을 지정한다. 다음은 제약 조건 수정이 추가된 예제이다.

	int main(void)
{
	int x = 10, y;
	
	asm ("movl %1, %%eax;
	     "movl %%eax, %0;"
		:"=&r"(y)	/* y is output operand, note the	
				   & constraint modifier. */
		:"r"(x)		/* x is input operand */
		:"%eax");	/* %eax is clobbered register */
}

다음은 이 예제를 위해 생성된 어셈블리 코드이다. x와 y가 "asm"을 통해 다른 레지스터에 저장되었음이 분명하다.

    main:
        pushl %ebp
        movl %esp,%ebp
        subl $8,%esp
        movl $10,-4(%ebp)
        movl -4(%ebp),%ecx	/* x, the input is in %ecx */
#APP
	movl %ecx, %eax
	movl %eax, %edx		/* y, the output is in %edx */

#NO_APP
        movl %edx,-8(%ebp)
 


특정 레지스터 제약 조건 사용하기

이제, 피연산자용 제약 조건으로서 개별 레지스터를 지정하는 방법에 대해 알아보자. 다음 예제에서, cpuid 명령어는 %eax 레지스터에서 인풋을 가져다가, 네 개의 레지스터 %eax, %ebx, %ecx, %edx에 아웃풋을 준다. cpuid에 대한 인풋(변수 "op")는 eax 레지스터에 있는 "asm"으로 전달된다. a, b, c, d 제약 조건은 아웃풋에 사용되어 네 개의 레지스터에서 각각 값들을 모은다.

    asm ("cpuid"
                : "=a" (_eax),
                  "=b" (_ebx),
                  "=c" (_ecx),
                  "=d" (_edx)
                : "a" (op));
  

이를 위해 생성된 어셈블리 코드는 다음과 같다. (_eax, _ebx 등의 변수들이 스택에 저장된 것으로 간주함.):

    movl -20(%ebp),%eax	/* store 'op' in %eax -- input */
#APP
        cpuid
#NO_APP
        movl %eax,-4(%ebp)	/* store %eax in _eax -- output */
        movl %ebx,-8(%ebp)	/* store other registers in
        movl %ecx,-12(%ebp)	   respective output variables */  
        movl %edx,-16(%ebp)

strcpy 함수는 다음과 같은 방식으로 "S"와 "D" 제약 조건을 사용하여 구현될 수 있다:

    asm ("cld\n
	      rep\n
	      movsb"
	      : /* no input */
	      :"S"(src), "D"(dst), "c"(count));

소스 포인터 src는 "S" 제약 조건을 사용하여 %esi에 놓이고, 목적지 포인터인 dst는 "D" 제약 조건을 사용하여 %edi에 놓인다. 카운트 값은 %ecx에 놓인다. rep 접두사에 필요하기 때문이다.

다음은 두 개의 레지스터 %eax와 %edx를 사용하여 두 개의 32-bit 값을 결합하고 64-bit 값을 생성하는 제약 조건이다:

#define rdtscll(val) \
     __asm__ __volatile__ ("rdtsc" : "=A" (val))

The generated assembly looks like this (if val has a 64 bit memory space).

#APP
        rdtsc
#NO_APP
        movl %eax,-8(%ebp)	/* As a result of A constraint 
        movl %edx,-4(%ebp)	   %eax and %edx serve as outputs */

Note here that the values in %edx:%eax serve as 64 bit output.


매칭 제약 조건 사용하기

다음은 네 개의 매개변수들을 가진 시스템 호출용 코드이다:

#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
          "d" ((long)(arg3)),"S" ((long)(arg4))); \
__syscall_return(type,__res); \
}

위 예제에서, 시스템 호출에 대한 네 개의 인자들은 제약 조건 b, c, d, S를 사용함으로써, %ebx, %ecx, %edx, %esi에 놓인다. "=a" 제약 조건은 아웃풋에 사용되어, %eax에 있는 시스템 호출의 리턴 값이 __res 변수에 놓이게 한다. 매칭 제약 조건 "0"을 인풋 섹션의 첫 번째 피연산자 제약 조건으로서 사용함으로써, syscall 넘버 __NR_##name은 %eax에 놓이고, 시스템 호출에 대한 인풋으로서 작동한다. 따라서, %eax는 여기에서 인풋과 아웃풋 레지스터로서 작동한다. 어떤 개별 레지스터들도 이러한 목적으로 사용되지 않는다. 또한 인풋(syscall 넘버)는 아웃풋(syscall의 리턴 값)이 만들어 지기 전에 소비(사용)된다.


메모리 피연산자 제약 조건의 사용

다음과 같은 원자 감소 연산을 생각해 보자:

    __asm__ __volatile__(
                "lock; decl %0"
                :"=m" (counter)
                :"m" (counter));

이것을 위해 생성된 어셈블리는 다음과 같다:

    #APP
	lock
	decl -24(%ebp) /* counter is modified on its memory location */
#NO_APP.

이 카운터를 위해 레지스터 제약 조건을 사용하는 것을 생각할 수도 있다. 만약 그렇다면, 카운터의 값은 먼저 레지스터에 복사되고, 감소된 다음, 메모리로 업데이트 되어야 한다. 하지만, 잠금과 원자성이라는 순수한 목표를 잃었다. 이것은 메모리 제약 조건을 사용해야 할 필요성을 증명하는 것이다.


clobbered 레지스터 사용하기

메모리 카피의 기본적인 구현에 대해 생각해 보자.

	asm ("movl $count, %%ecx;
	      up: lodsl;	
	      stosl;
	      loop up;"
		: 			/* no output */
		:"S"(src), "D"(dst)	/* input */
		:"%ecx", "%eax" );	/* clobbered list */	

lodsl이 %eax를 수정하는 동안, lodsl과 stosl 명령어는 이를 모호하게 사용한다. %ecx 레지스터는 카운트를 명확하게 로딩한다. 하지만, GCC는 우리가 알려주지 않는 한 이것을 모른다. 이것은 clobbered 레지스터 세트에 %eax와 %ecx를 포함시킴으로써 우리가 하는 일이다. 이것이 수행되지 않는 한, GCC는 %eax와 %ecx가 비어있다고 간주하고, 다른 데이터를 저장하는데 사용하기로 결정한다. %esi와 %edi 는 "asm"에 의해 사용되고 clobbered 리스트에 없다. 이것은 "asm"이 인풋 피연산자 리스트에서 이들을 사용하기로 선언했기 때문이다. 밑에 있는 라인은 레지스터가 "asm" 안에서(명확하게 또는 모호하게) 사용되고, 이것이 인풋 또는 아웃풋 피연산자 리스트에 없다면, 여러분이 이것을 clobbered 레지스터로서 리스팅 해야 한다.


결론

인라인 어셈블리는 이 글에서 다 다루지 못할 정도로 거대하고 많은 기능을 갖고 있다. 이 글에서 기초적인 것을 공부했으니, 여러분이 직접 인라인 어셈블리를 코딩 해 보는 것도 좋을 것 같다.


참고자료

필자소개

Bharata B. Rao는 인도 Mysore University에서 전자 통신 엔지니어링을 전공했다. 1999년부터 IBM Global Services에서 일하고 있다. IBM Linux Technology Center의 멤버로서, 리눅스 RAS (Reliability, Availability, Serviceability) 분야를 담당하고 있다. 운영 체계 내부 구조와 프로세스 아키텍처에 관심이 많다. (rbharata@in.ibm.com)

잘못된 도움말 신고

부정사용 신고

감사합니다. 이 항목은 운영자가 관심을 표시했습니다.


잘못된 도움말 신고

부정사용 신고

제출실패 신고. 나중에 다시 실행해주세요.


디벨로퍼웍스 로그인


IBM ID가 필요하세요?
IBM ID를 잊으셨습니까?


비밀번호를 잊으셨습니까?
비밀번호 변경

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

화면상에 보여지는 닉네임을 정하세요.

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

3개의 &이나 대쉬를 포함해주시고 31글자내로 제한해주세요.


developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


아티클 순위

의견

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=20
Zone=리눅스
ArticleID=271312
ArticleTitle=리눅스의 x86 용 인라인 어셈블리(Inline assembly) (한글)
publish-date=11272007
author1-email=rbharata@in.ibm.com
author1-email-cc=

태그

Help
검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오.

태그를 더 많이 보거나 적게 보기 위해 슬라이더 막대를 사용하십시오.

인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다.

내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.

검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오. 인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다. 내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.