요즘도 쓰는 언어 어셈블리

x86_64, AMD64, 64bit

리눅스 커널에있는 어셈블리 코드 소개

세계 최고의 프로그래머들이 개발하는 제품이 더 있겠지만, 그 소스를 볼 수 있고, 그 소스의 변해가는 과정도 볼 수 있고, 의논하는 과정도 볼 수 있고, 따라서 왜 이렇게 바뀌었는지 이렇게 바뀌면 뭐가 더 좋은지를 알 수 있는 제품은 몇개 없습니다. 오픈소스 프로젝트 중 대규모 프로젝트들이 그런데요 그 중에서도 리눅스 커널에는 어떤 어셈블리 코드가 있는지 살짝 소개를 하겠습니다.

https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/

이 사이트에 들어가서 소스를 봐도 되지만 소스 검색이 안되니까

http://lxr.free-electrons.com/

이 사이트에 들어가시면 소스 검색 기능이 있으서 함수 이름 검색을 할 수 있습니다.

우리는 x86_64 아키텍쳐의 어셈블리 코드를 보려하는 것이니 arch/x86/ 디렉토리에 들어가야겠지요.

http://lxr.free-electrons.com/source/arch/x86/

그 중에서도 어셈블리로 만든 라이브러리 소스를 보는게 조금이라도 더 이해하기 쉬울것 같습니다.

http://lxr.free-electrons.com/source/arch/x86/lib/

확장자가 .S로 되어있는 파일들이 몇개 보입니다. 하나씩 열어보세요. at&t 문법으로 되어있지만 이제 별로 두렵지 않으실거에요. 파일 이름에 386이나 32라는 이름이 붙어있으면 32비트용입니다. 필요하시면 참고하시고 아니라면 64비트용을 주로 보세요.

clear_page_64.S을 보면 파일 시작 부분에 이런 글귀가 있습니다.

/*
 * Most CPUs support enhanced REP MOVSB/STOSB instructions. It is
 * recommended to use this when possible and we do use them by default.
 * If enhanced REP MOVSB/STOSB is not available, try to use fast string.
 * Otherwise, use original.
 */

이전 강좌에서 우리가 왜 필요한지 몰랐던 rep movs 명령어에 대한 설명이 있습니다. 그리고 clear_page라는 함수에 rep stos 명령을 쓰네요. rep movs는 메모리에서 메모리로 복사를 하는데 rep stos는 레지스터의 값을 메모리에 쓰기만 하는 명령입니다. 연속적인 메모리에 데이터를 쓰는 것은 동일합니다.

뭔가 더 좋은 것이라는 의미이긴한데 뭐가 왜 좋은지는 않나와있네요. clear_page 함수가 어떻게 정의되어있는지 ENTRY()나 ENDPROC()같은 지시어는 뭔지 검색해보세요.

그 다음 다른 어셈블리 파일을 찾아보니 copy_user_64.S 파일이 있습니다. 뭔가 또 메모리 카피가 나타날것 같으네 열어보니 진짜 메모리 복사하는 함수들이 있습니다. copy_user가 뭔지는 생각할 필요가 없고 중간쯤에 copy_user_generic_string 함수의 주석을 보세요.


/* Some CPUs run faster using the string copy instructions.
 * This is also a lot simpler. Use them when possible.
 *

드디어 왜 rep movs를 쓰는지 알게되었습니다. 다른거 없고 빠르니까 그렇답니다. 다른 함수들의 주석에도 string copy 명령을 쓰는게 좋다는 주석이 있네요.

그럼 왜 64바이트 구조체의 복사에는 rep movs를 안썼을까요? 저도 정확히는 모르겠습니다. 인텔 프로세서 메뉴얼에도 설명이 없었습니다. 제 추측으로는 rep movs가 작은 크기 복사에는 빠르지 않을것 같습니다. 복사하는 크기가 커지면서 복사 시간의 증가폭이 적고, mov를 쓰면 작은 크기 복사는 빠르지만 복사 시간의 증가폭이 커지는게 아닐까 추측만 합니다. gcc 커뮤니티에 문의하는게 가장 정확한 답을 얻을 수 있는 방법일것 같습니다. 한번 메일을 보내보세요. 커뮤니티가 좋은게 누구나 참여할 수 있는 것이니까요.

 

그 다음으로 delay.c를 보겠습니다. 이전에 딜레이를 만들기 위해서 만든 C 함수가 최적화 때문에 사라진 것을 봤습니다. 리눅스 커널은 딜레이를 어떻게 만들고 있을까요?

delay_loop()이라는 함수를 보니 인라인 어셈블리로 루프를 만들걸 알 수 있습니다. 인라인 어셈블리로 만드어버리면 컴파일러가 최적화를 못하겠지요. 그래서 루프가 사라지지않고 원하는 횟수만큼 루프를 돌게됩니다. 물론 그게 다가 아닙니다. 딜레이하나에도 복잡한 기술이 동원됩니다. 가장 단순하게 기본적인 딜레이가 delay_loop()인데 옛날 프로세서에서 커널이 동작할 때 사용합니다. 최신 프로세서에서 사용하는 딜레이는 전력 소모를 줄이는 등 최신 프로세서의 하드웨어에서 제공하는 기술을 추가합니다.

 

다른 파일들을 찾다보니 드디어 memcpy_64.s가 나옵니다. 이제 안봐도 rep movs를 사용한 버전과 mov만 사용한 버전이 있을 거라는게 짐작됩니다. rep movs를 안쓴 버전이 memcpy_orig인데 이상하게 깁니다. 왜냐면 좀더 속도를 내기위해 주소값을 8바이트로 정렬하도록 맞추기 때문입니다.

 

메모리 복사만 예로 들어서 다른 예가 궁금하실텐데요 다른 소스들에있는 인라인 어셈블리 코드들을 보셔도 좋습니다만 아마 이해가 안되는 코드가 많을 겁니다. 커널 자체를 알아야 이해가 되는 코드들입니다. 메모리 복사만 설명드린 것도 커널을 몰라도 참고할 수 있기 때문입니다.

왜 어셈블리가 필요한지 약간은 이해가 되셨으니라 믿습니다. 

 

 

 

 

 

 

 

댓글

댓글 본문
작성자
비밀번호
버전 관리
gurugio
현재 버전
선택 버전
graphittie 자세히 보기