0. 개요


파이썬 개발을 위해서 굳이 쥬피터를 사용하지 않아도 상관없다. 오히려 웹 상에서 개발이 진행되기 때문에 불편한 점이 많은 것이 사실이다. 예를 들어, 다른 IDE 툴에서 유용하게 사용하는 [ctrl + z]가 쥬피터에서는 유용하지 않다. 또한 동일한 단어를 찾아서 모두 교체하는 기능 역시 웹 상에서 동작하지 않기 때문에 불편하다. 그렇다면 쥬피터를 사용해서 얻을 수 있는 장점은 무엇이 있을까? 파이썬 강의 첫 번째 글에 작성한 것 처럼 하나의 문서내에 그래프, 인풋, 아웃풋, 노트 등의 기능을 모두 담을 수 있다는 점이 가장 큰 장점이다. 오늘 강의는 그런 쥬피터 환경이 가지고 있는 이점을 최대한 정리하려한다.



1. Magic Command


쥬피터 환경에서 제공하는 매직 커맨드는 수십가지가 있다. 이들 모두를 다룰 것은 아니고 눈에 띄는 몇 가지 명령어만 다뤄 볼 참이다. 기본적으로 리눅스 쉘에서 사용하는 명령어들이 몇몇 제공되고 있다. 간단히 확인해 볼 명령어는 ls 명령어와 pwd 명령어다. 리눅스 쉘에서 자주 사용하는 명령어며, 내용이 특히 간단하다. 매직 커맨드 목록을 보기 위해 아래의 명령어를 입력하자.

%lsmagic


매직 커맨드는 앞에 % 문자를 기본적으로 붙여주며, 이어서 ls 명령어와 pwd 명령어를 실행한 결과를 확인할 수 있다.


필자가 조금 더 인상깊었던 매직 커맨드는 %timeit 이라는 커맨드인데 이는 코드의 수행시간을 알려준다. 단순히 한 번 동작한 시간을 알려주는 것이 아니라 수 백, 수 천번 이상을 수행한 뒤 평균적인 시간을 출력해준다는 점이 특징이다. 수행 시간이 상당히 긴 코드의 경우 적게 반복하고, 수행 시간이 짧은 코드의 경우 수 만번 이상 반복하여 평균한 시간을 알려준다. 코드의 수행시간에 따라서 상당히 유연하게 동작하는 것을 알 수 있다. 지난 시간에 만들었던 리스트의 모든 요소들을 제곱하는 함수의 수행 시간을 알아보자.



List Comprehension으로 반복문을 구동한 시간을 알아봤다. 내용을 보면 간단한 코드인만큼 1,000,000 번 반복해서 가장 느렸던 시간과 함께 1회 반복마다 걸린 시간을 알 수 있다. 성능을 비교할 때 유용한 명령어이므로 기억해두자.



2. Interactive Widgets


쥬피터의 가장 큰 장점을 확인할 수 있는 부분인데, 인터렉티브한 문서를 작성가능하다는 점이다. 하나의 문서내에 유투브 영상 및 파라미터를 인터렉티브하게 조절할 수 있는 바를 제작해보자. 먼저 유투브 영상을 문서에 포함하기 위해서는 아래처럼 유투브 비디오를 import 해줘야한다.

from Ipython.display import YouTubeVideo

YouTubeVideo('j9YpkSX7NNM')


위 코드의 첫 번째라인에서 유투브 비디오를 import하고 있고, 두 번째 라인에서 유투브 영상을 삽입하고 있다. 괄호안에 j9YpkSX7NNM 는 유투브 영상주소에 포함되어 있는 고유값으로 해당 값을 입력해주면 정상적으로 문서에 해당 영상을 포함시킬 수 있다.



이어서 볼 위젯은 인터렉티브하게 값을 변경할 수 있는 Bar 기능이다. 이 역시 import 과정이 필요하다. 코드를 통해 확인해보면 다음과 같다.

from ipywidgets import interact

@interact(x=(0, 10))

def square(x):

return ("The square of %d is %d.", % (x, x**2))


위 코드 첫 번째 라인에서는 필요한 부분을 import하고 있고, 두 번째 라인에서 square 함수에 인수로 전달되는 x의 값을 0에서부터 10까지 조절할 수 있도록 위젯를 형성하고 있다. 코드를 수행한 결과 아래의 화면을 보면 가로로 긴 Bar가 생긴 것을 확인할 수 있다.


비록 사진에서는 멈춰있지만 실제로 마우스를 통해 바를 움직여보면 그에 따라서 출력되는 값이 실시간으로 변하는 것을 확인할 수 있다. 



3. 마치며...


오늘 알아본 매직 커맨드와 인터렉티브 위젯들은 Jupyter/IPython 환경에서 제공하는 다양한 내용 중에 극히 일부에 불과하다. 제공하고 있는 많은 매직 커맨드를 경험해보고, 본 글에서 다루지 않았던 다양한 위젯을 응용해 본다면 더욱 매력적인 문서를 작성할 수 있을 것이다. 더불어 디버깅 툴처럼 개발자들이 유용하게 사용할 수 있는 기능을 제공하니 참고하여 효율적으로 활용하도록 하자.




0. 개요


이전까지 아나콘다, 쥬피터 상에서 파이썬 3.5 버전을 다룰 수 있도록 환경을 세팅했다. 오늘 포스팅은 파이썬 문법 중 기억할만한 것들에 대해서 간략하게 정리해볼까 한다. 세팅했던 환경을 이용하기 위해서 쥬피터 노트북을 실행해보자. 이전 포스팅에서 다뤘던 것처럼 윈도우 환경에서는 파워쉘을 실행시켜 jupyter notebook 명령어를 입력하거나, 탐색기에서 Jupyter Notebook을 검색하여 실행하면 된다. 그러면 아래처럼 웹 브라우저가 시행될 것인데 여기서 몇 가지 파이썬 문법을 직접 수행하면서 알아볼 참이다. 상당히 기본적으로 치부되는 문법들은 과감하게 생략하고, 정리가 필요한 내용만 포스팅할 예정이므로 기본적인 문법을 위해서는 다음을 참조하는 편이 유익할 것이다.


열린 브라우저 창에서 우측 상단에 있는 [New] 버튼을 클릭하여 연습할 파이썬 파일을 생성해주자. 필자는 파일명을 Memorable로 정했다. 지난 시간에 개발 환경이 적절하게 설치되었는지 확인해보기 위해 다시 한 번 Hello World를 출력해봤다. 정상적으로 동작하는 것을 확인했으며, 이제부터 정리가 필요한 내용들을 하나씩 알아보자.


1. 리스트(List)


파이썬에서 유용하게 쓰이는 자료구조 중에 리스트가 있는데, 특징이 남달라서 정리 해볼만하다. 필자가 처음 파이썬을 접했을 때에는 리스트 = 배열인줄 알았다. 그러나 유사한 면이 있는 것은 맞지만 엄밀히 말하면 둘은 다르다. Java 혹은 C언어에서 다루는 배열은 동일한 타입만 요소로 다뤄질 수 있었는데 리스트는 서로 다른 타입의 데이터도 다룰 수 있다는 점이 가장 큰 차이다. 


1.1 리스트의 환형구조


리스트를 배열처럼 선언하여 사용하다보면 재미난 점이 있는데, 음수 인덱스를 이용한 접근이 가능하다는 점이다. 예시를 보면 금방 이해가 될 내용이다.




화면을 확인하면 알 수 있듯이 리스트는 [A, B, C, D, E]로 구성되어있는데 items[-1]로 접근하면 E를 출력하고, items[-2]로 접근하면 D를 출력하는 것을 확인할 수 있다. 0 번째 아이템이 A임을 감안하면 -1 번째 E, -2 번째 D가 나오는 것을 이해할 수 있는데 이는 곧 리스트의 구조가 환형(Circular)임을 파악할 수 있다. 이 부분을 잘 기억한다면 번잡한 함수-콜 없이 접근을 자유롭게 할 수 있을 것 같다.


1.2 slice


리스트를 일정 범위만큼 자르기 위해서 사용하는 방법이다. 대부분 언어에서는 이 기능을 함수나 메소드로 구현하고 있지만 파이썬에서는 인덱스에 특별한 입력을 통해 잘린 리스트를 반환할 수 있다.



items[1:3]으로 배열을 접근하여, 출력된 결과를 보면 B와 C가 출력된 것을 알 수 있다. 언뜻보면 '인덱스 1을 포함하고, 인덱스 3을 포함하지 않는 범위까지'라고 애매하게 이해될 수 있다. 필자도 이 부분이 항상 체계적이지 못하다고 생각했지만 수강했던 강의에서 확실하게 이해할 수 있었다. 위 인덱스의 내용 [1:3]이 의미하는 바를 다음처럼 기억하자. 

"리스트 요소(element) 사이사이를 지칭하는 인덱스 "


위 리스트의 요소 사이사이는 다음처럼 표현할 수 있다. [ ^ A ^ B ^ C ^ D ^ E ^ ]. 바로 ^로 표현한 사이사이를 지칭하는 것이 바로 [1:3]인 것이다. 1이 의미하는 것은 [A 뒤, B 앞]의 ^이고(왜냐하면 인덱스는 0부터 시작하니까), 3이 의미하는 것은 [C 뒤, D 앞]의 ^이다. 이 두 인덱스 사이를 출력해보면 결과적으로 B, C가 출력되는 것을 파악할 수 있다. 어려운 내용은 아니지만 기억하기에는 헷갈릴만한 내용이므로 정리해봤다.


1.3 List Comprehension


가장 기억 해야할만한 내용으로 리스트 전체에 대해 일괄적으로 동일한 작업을 반복하는 명령어다. 웹 프로그래밍할 때 자바스크립트 언어에서 동일한 기능이 있었지만 파이썬과는 문법이 사뭇 달랐다. 아래의 화면을 통해 이해해보자.



List Comprehension 확인을 위해서 이전에 선언했던 리스트를 [A. B, C ,D ,E] → [1, 2, 3, 4, 5]로 변경했다. 그리고 그 다음 라인을 통해 리스트 내에 모든 요소들에게 동일한 명령어를 수행하고 있다.

[temp * temp for temp in items]

 

위 명령어를 풀어서 설명하자면 items 리스트 안에 있는 모든 요소를 제곱하라는 의미다. 출력된 결과를 보면 [1, 4, 9, 16, 25]로 모든 요소가 제곱된 것을 확인할 수 있다. 강사님의 말로는 해당 명령어를 통해 구현한 반복문이 다른 언어에서 쓰이는 전형적인 for문, while문보다 빠른 속도를 제공한다고 한다. 내부적으로 어떤 원리인지는 모르겠지만 파이썬을 배우는 과정에서 기억할만한 내용이다.


2. 함수


이전까지 기본 문법은 생략하고, 그나마 리스트가 가진 특징에 대해서만 언급했는데 함수에 대해서도 정리하고 넘어가야할 내용이 있어 정리해본다. 파이썬의 함수는 임의의 갯수의 인수를 수용할 수 있다. 이를 Positional Arguments라 칭하고, 또한 이미 정의된 인수도 수용할 수 있는데 이를 Keyword Arguments라 한다. 이들에 대해서만 간단히 정리해보자.


2.1 Positional & Keyword Arguments


위에서 Positional Arguments와 Keyword Arguments에 대한 설명을 했으니 화면을 통해 실제적으로 어떻게 사용되는지 확인해보자.



첫 번째로 remainder 함수를 선언했다. remainder 함수는 숫자를 하나 입력받으면 미리 설정된 인수(Keyword Arguments)로 나누는 간단한 함수다. remainder(5)의 결과가 1 임을 확인할 수 있고, 그 바로 아랫 라인에서는 (5, 3)을 인수로 전달하여 number = 5, divisor = 3으로 대입된 것을 확인할 수 있다. 실제로 remainder(5, 3)과 remainder(5, divisor = 3)은 정확하게 일치하지만 코드 분석이나 가독성을 위해서 후자를 이용하는 것이 바람직하다. remainder 함수를 통해 Keyword Arguments에 대해서 알아봤다면 f 함수를 통해 둘 다 명확하게 알아보자. f 함수는 인수로 Positional Arguments와 Keyword Arguments를 받고 있다. 따라서 f(1, 2, c = 3, d = 4)를 출력해보면  Positional Arguments로 입력된 1, 2가 튜플의 자료구조 형태로 출력되는 것을 알 수 있고, Keyword Arguments로 입력된 c, d가 딕셔너리의 자료구조 형태로 출력되는 것을 확인할 수 있다.



오늘 알아본 것을 사람들이 이해하기 쉽게, 그리고 훗날 내용을 까먹을 내가 더 잘 리마인드 할 수 있도록 포스팅을 했지만 실제로 다룬 내용을 보면 참 부실하다. 강의를 진행하면 할수록 더 다룰 내용이 풍성해 지겠지만, 기본 문법을 생략에 생략을 거듭하다보니 정작 다룬 것은 아래 내용이 전부다.




3. 마치며...


개요에서 언급한 것처럼 언어를 시작할 때는 가장 기본적인 문법부터 익히는 것이 바람직할 것이다. 그러나 필자는 다양한 언어를 경험해봤기 때문에 기초적이고 다른 언어와 공통점이 많은 문법들은 따로 정리할 필요가 없다. 때문에 기본적인 문법을 익히기 위해서는 다음을 참조하길 추천한다. 필자가 학습한 내용 중에 기억할 필요한 내용을 추려서 정리하기 때문에 좋은 참고자료가 되긴 어렵겠지만, 나와 비슷한 수준을 지닌 사람들에게 도움이 되길 기대해본다.













파이썬은 1991년 Guido Van Rossum이 퍼블리싱한 언어로 인터프리터로 해석되는 스크립트 언어다. 필자는 Matlab, C, Python 중에 하나의 강의를 수강하게 되었는데 웹에 대해서 공부할 때 스크립트 언어에 대해 재미있게 학습한 기억이 있고, 연구실에서도 파이썬을 수강할 것을 추천했기에 파이썬을 선택했다. 강의의 초반부에는 재미있는 얘기를 접할 수 있었는데, 파이썬이 공식적으로 퍼블리싱되던 91년 즈음에는 현재 학생들이 자신만의 모바일 애플리케이션을 제작하는 것처럼 직접 스크립트 언어를 제작하는 것이 유행이었다고 한다. 때문에 비슷한 시기에 다양한 스크립트 언어가 발매되었다고 한다. 비록 지금까지 살아남은 것은 드물지만.


현재 다양한 연구실과 산업분야에서 비싼 값을 치루면서까지 Matlab이 사용되고 있다. 그러나 프로그래밍 언어론적인 측면에서 매틀랩은 파이썬에 비하면 상당히 낮은 수준의 언어라고 한다. 한 때 객체지향 패러다임의 유행으로 산업계에 cpp가 투입됐었지만, 그 동안 유지해 온 절자지향적인 사고방식을 객체지향적인 패러다임으로 전환하는 것이 실패했고, 그 틈새시장을 매틀랩이 차지하게 되었다는 후문이다.


다양한 단점에도 불구하고 인정할 수 밖에 없는 매틀랩의 장점은 계산과 코멘트, 그래프 및 특수문자 삽입이 하나의 파일내에서 작업가능하다는 점이다. 코딩은 IDE에서, 그래프 작업은 웹에서, 문서작업은 워드에서 등 하나의 논문이나 문서를 작성할 때 병행해야 하던 것을 대부분 매틀랩 내에서 처리할 수 있었기 때문에 작업 사이의 동기가 잘 맞는다는 것이다.



매틀랩과 관련된 얘기로 인해 서두가 길었다. 앞으로 주로 사용하게 될 파이썬에 대해서 좀 더 자세히 알아보자. 위에 있는 표는 프로그래밍 언어의 트렌드를 조사한 것인데, 한 동안 꾸준히 성장세를 보이던 자바가 주춤하고, 파이썬이 2위에 위치한 것과 비교적 최근 발매된 스위프트라는 언어가 급상승 중이라는 것을 알 수 있다. 위 표를 통해 앞으로 학습할 파이썬이라는 언어가 상당히 긍정적으로 평가되고 있다는 것을 기억하자.


Pros & Cons


하지만 장점으로 꼽았던 모든 기능들을 최근에는 파이썬에서 처리가능하다. 그리고 덧 붙여 파이썬이 가지고 있는 가장 큰 장점은 쉽다는 점이다. 자바스크립트처럼 타입에도 유연하고, 제공하는 라이브러리도 다양하고 많다. 비록 이전까진 싱글코어 환경으로 제한되어 쉽지만 느린 랭귀지란 생각이 있었는데, 이마저도 최근에는 많이 개선되었다고 한다.


현재 2.7버전과 3.5버전이 있는데, 대게 언어는 점진적으로 개선을 거듭하여 발전하는 법이지만 파이썬은 조금 다르다. 2.7버전과 3.5버전 둘 다 계속 개발중에 있으며, 현재까지는 2.7 버전 사용이 조금 우세하거나 비등한 수준일 것이다. 그도 그럴만한 것이, 파이썬이 가지고 있는 한계점을 대폭 개선하기위해 하위 호환을 적극적으로 고려하지 않고 3.5버전을 출시했다. 때문에 3.5버전 퍼블리싱 당시에는 옹호하지 않는 의견도 다분히 많았다고 한다. 하지만 점차 3.5의 비중이 증가하면서 미래에는 파이썬 버전 3.5가 리드할 것으로 예상한다.


또한 주목 할 만한 점은 최근 프로그래밍 언어들의 추세에 맞게 파이썬 역시 멀티-패러다임을 지원한다. 즉 파이썬 내에서 object-oriented, imperative, functional, procedural, reflective 형식의 프로그래밍이 가능하다는 것이다.

 



1. 개발 환경 설정


파이썬에 대해서 간단히 알아봤으니 이제 실질적으로 프로그래밍 할 수 있는 환경을 구축해보자. 필자는 수강하고 있는 강의 내용과 동일한 개발 환경을 구축할 것이다. 파이썬 버전 3.5를 사용하며, 파이썬 언어를 지원하는 웹 애플리케이션 주피터(Jupyter)를 사용할 것이다. 또한 대학원에서 자주 접할 수학, 공학, 데이터 분석에 최적화 된 파이썬 배포판인 (아나콘다)Anaconda를 사용할 것이다. 아마도 이름을 아나콘다로 작명한 것은 파이썬이 비단뱀을 의미하기 때문일 것이다. 그럼 아나콘다 웹 페이지로 이동하여 자신의 운영체제에 맞는 버전으로 설치해주자. 필자는 윈도우 환경에서 작업하므로 윈도우 64-bit 버전을 사용할 것이다.



2. 주피터 사용


아나콘다 설치가 완료되었다면, 윈도우 기준으로 윈도우 파워쉘(Windows PowerShell)을 켜주자. 일반적인 CMD 창에서는 명령어가 정상적으로 동작하지 않는다고 하니 반드시 파워쉘을 사용 해야한다. 파워쉘에서 아래의 명령어를 입력하면 로컬 서버가 설정되고 웹 환경에서 파이썬 프로그래밍을 진행할 수 있게 된다.

$ jupyter notebook


명령어를 입력하면 자동으로 웹 브라우저가 실행되며, 파워쉘 창은 끄지 말고 내려두자. 만약 종료하면 서버 역시 종료되어 웹에서 작업을 이어갈 수 없다. 또한 파워쉘에서 직접 명령어를 입력할 필요 없이 jupyter notebook이라는 파일을 실행해줘도 동일하다. 정상적으로 실행하면 아래와 같은 파워쉘 화면을 확인할 수 있다.



그리고 자동으로 실행된 웹 브라우저를 확인해보면 쥬피터가 구동 중이라는 것을 파악할 수 있다. 본격적인 프로그래밍은 웹 브라우저 상에서 진행된다.





Github를 사용해 본 사람에게는 매우 익숙한 UI이기 때문에 사용에 크게 어려움은 없을 것 같다. 만약 익숙하지 않더라도, 상당히 직관적이며 실제 윈도우나 리눅스의 디렉토리 구조도 동일하므로 금새 적응할 수 있다. 우측 위에 [New] 버튼을 통해 폴더를 생성할 수 있고, Python3 파일을 생성할 수 있다. 필자는 /Desktop/python 폴더를 생성하고, 그 안에 intro.ipynb 파일을 생성했다. 아마 쥬피터에서 작업하면 확장자가 ipynb로 지정되는 것 같다. 파일이 실행되고 나서 위에 메뉴바를 보면 파일 이름을 변경할 수 있고, 쥬피터 로고 옆에 파일 이름을 클릭해도 이름을 변경할 수 있다. 






Intro라는 파일 제목에 걸맞게 Hello world!를 출력해보고, 정상적으로 출력이 된다면 파이썬 프로그래밍을 위한 개발환경은 구축되었다고 볼 수 있다. 가볍게 아래의 명령어를 입력하고 [Shift + Enter]를 눌러서 정상적으로 출력되는지 확인해보자.

print ("Hello world!")



위의 사진을 확인해보면 검은색 글씨로 출력된 것을 알 수 있다. 이번 포스팅은 여기서 마치지만, 실질적인 파이썬 프로그래밍은 이제부터 시작이다. 개발환경이 이전과 상이하게 다른 만큼 개발 환경인 쥬피터의 기능에 대해서 알아봐야하고, 이어서 파이썬 문법과 기능에 대해서도 알아봐야 한다. 앞으로 적어도 30시간 동안은 꾸준히 사용할 개발 환경인만큼 'h'를 눌러서 쥬피터가 갖고 있는 기능에 대해서라도 한 번 훑어보는 편이 좋을 것이다.



3. 마치며...


필자도 짧게나마 파이썬 프로그래밍을 해 본 경험이 있지만, 쥬피터나 아나콘다 환경은 처음 접해 봤다. 물론 처음이라 낯선 면도 있고, 다른 IDE에 비해서 매우 불편한 것처럼 느껴지지만 최근에는 웹 상에서 이와 같은 작업을 많이 하는 트렌드같다. 한 번은 경험해볼만하고, 익숙해진다면 나쁘지 않을 것 같다. 앞으로 진행될 30시간의 강의동안 최대한 능숙하게 다루기 위해서는 빨리 적응해나갈 필요가 있다.





리눅스 커널에 대해서 공부하면서, 이론과 더불어 함께 진행해볼 수 있는 실습들을 직접 시행해보고 있다. 책으로만 공부하면 금세 잊어버리기 때문에 실습을 겸하여 공부하고 있으며, 또한 실습한 내용을 보다 효과적으로 기억하기 위해 블로그에 정리하고 있다. 오늘은 이전에 진행했던 모듈 프로그래밍에 연장선인 문자 디바이스 드라이버를 제작해봤다. 가장 간단한 형태의 캐릭터 디바이스 드라이버지만 기본적인 골격을 확인하기에는 충분했다. 개발 환경은 이전에 모듈 프로그래밍 실습에서의 환경과 동일하게 버추얼박스 버전 5.0, 우분투 버전 14.04.3 LTS, 그리고 커널 버전 3.19.0-25-generic를 활용했다.


0. 준비


실습에 앞서 디바이스 드라이버에 대해 공부한 내용을 간략하게 정리해봤다. 가장 먼저 알아야 할 것은 '디바이스 드라이버'라는 용어다. 이를 명확하게 표현한 글이 있어서 인용했다. 기본적으로 디바이스는 하드웨어를 의미하고, 드라이버는 그 하드웨어(디바이스)를 컨트롤하는 소프트웨어를 의미한다. 예를 들어 UART 칩이 16개 달려있다고 했을 때 이를 제어하는 드라이버(소프트웨어)는 하나이다. 하지만 UART 칩은 16개이므로 디바이스는 16개다.

Device(Hardware)를 조종하는 Driver(Software) = Device Driver


                                   


흔히 디바이스 드라이버를 설명할 때 좌측 그림이 자주 인용된다. 응용프로그램이 하드웨어를 직접 컨트롤 하는 것이 아니라 디바이스 드라이버를 통해서 하드웨어를 조종한다. 하지만 장치별로 각각 제공하는 디바이스 드라이버가 다른 점이 문제다. 하드웨어 제작사에서 디바이스 드라이버를 제공하는 것은 기분 좋은 일이지만 사용 방법이 회사별로 다르다면 응용 프로그래머들이 모든 하드웨어를 다루기엔 부담이 너무 크다. 하지만 다행히 리눅스 시스템은 디바이스를 /dev 디렉토리에 하나의 파일로써 취급한다. 디바이스 드라이버도 마찬가지로 파일처럼 취급하는데, 이는 리눅스가 VFS(Virtual File System)을 제공하기 때문이다. 이 때 /dev 디렉토리에 있는 디바이스 파일은 사용자가 액세스 할 수 있는 드라이버의 인터페이스 부분이다. 디바이스 드라이버가 파일로써 취급되기 때문에 open, close, read, write 등의 연산을 통해 액세스 할 수 있다. 주목할 점은 각각의 디바이스 파일들은 고유한 번호와 이름을 할당받는 점이다. 때문에 디바이스 드라이버를 제작하고 등록하기 위해서는 번호 및 이름을 지정해줘야만 한다.  


디바이스 드라이버의 종류는 캐릭터(문자) 디바이스 드라이버/ 블록 디바이스 드라이버/ 네트워크 디바이스 드라이버로 구분된다. 대게 일반적으로 캐릭터 디바이스 드라이버 작성을 주로하고, 블록이나 네트워크 드라이버는 보다 전문적인 곳에서 작성된다고 한다. 먼저 터미널에서 해당 드라이버가 위치한 /dev 디렉토리를 확인해보자. 

$ls -al /dev



파일들이 꽤 많은 것을 알 수 있는데, 확인해 볼 것은 맨 좌측의 문자다. 네, 다섯개의 문자를 확인할 수 있는데 문자 c는 캐릭터 디바이스 드라이버 파일을 의미하고 문자 b는 블록 디바이스 드라이버 파일을 의미한다. 문자 'n'이 눈에 띄지 않는 것은 네트워크 디바이스 드라이버는 캐릭터 혹은 블록 디바이스 드라이버와는 다르게 취급되기 때문인데, 네트워크 디바이스 드라이버에 대해서는 추후에 알아보도록 하고, 지금은 문자 디바이스 드라이버에 집중해보자.


좌측 문자에 이어서 가운데 위치한 두 개의 숫자는 Major Number와 Minor Number를 의미하는데 디바이스 드라이버 제작 및 등록을 위해서는 Major Number 및 디바이스 네임 설정이 필수적이라는 것만 기억하자.


1. 캐릭터(문자) 디바이스 드라이버

위에서 본 것처럼 캐릭터 디바이스 드라이버는 VFS에서 하나의 노드 형태로 존재한다. 때문에 추후에 실습할 때 mknod 명령어를 통해 노드를 생성해줘야 한다. 자료의 순차성을 가지고 있는 하드웨어를 다룰 때 사용하며, 데이터를 문자 단위(또는 연속적인 바이트 스트림)로 전달하고 읽어들인다. 대표적인 하드웨어로는 터미널, 콘솔, 키보드, 사운드카드, 스캐너, 프린터, 직렬/병렬 포트, 마우스, 조이스틱 등이 있다. 하드 드라이브나 플래시 메모리와 같은 저장자치가 포함되지 않는 것에 유의하자.


2. 문자 디바이스 드라이버 제작

문자 디바이스 드라이버를 제작하고 등록하는 과정은 모듈 프로그래밍의 연장선으로 볼 수 있다. 모듈 프로그래밍과 공통된 부분은 생략하니 만약 생략된 부분에 대한 상세한 설명을 원한다면 클릭하여 확인하도록 하자. 



먼저 소스 코드 작성에 대해서 설명하자면, 기본적으로 모듈 프로그래밍 과정을 통해 디바이스 드라이버를 등록하기 때문에 모듈 초기화 루틴(module_init) 매크로를 통해 virtual_device_init 함수를 구동하고, 이 함수 내에서 register_chrdev 함수를 통해 드라이버를 등록한다. 이 때 파라미터에 대해서 알아볼 필요가 있는데, 필자가 작성한 코드는 다음과 같다.

register_chrdev(250, "virtual_device", &vd_fops);


여기서 첫 번째 인수 250은 위에서 언급했던 Major Number를 의미하고, 두 번째 인수 virtual_device는 등록할 디바이스의 이름을 의미한다. 마지막 인수는 디바이스 드라이버의 오퍼레이션들의 집합인데 그 구성에 대해서 알아보면 다음과 같다.

static struct file_operations vd_fops = {

.read = virtual_device_read,

.write = virtual_device_write,

.open = virtual_device_open,

.release = virtual_device_release

};


동작들(R,W,O,R)은 각각 virtual_device_*들과 맵핑되어 있고, virtual_device_* 들은 함수로 구현되어 있다. 이들이 어떻게 동작하는지 아래의 그림을 통해서 조금 더 자세히 알아보도록하자.


응용프로그램에서 open, read, write, close와 같은 연산을 수행한다면 이는 커널 영역에서 시스템 콜을 호출한다. 각각의 시스템 콜은 VFS에 등록된 연산(vd_fops의 좌)들을 호출하고, 이들은 맵핑된 디바이스 드라이버의 함수(vd_fops의 우)들을 호출하는 매커니즘이다. 지금까지 이해한 정보를 토대로 실제 디바이스 드라이버를 제작해보자.


[virtual_device.c]

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>

static char *buffer = NULL;

int virtual_device_open(struct inode *inode, struct file *filp) {
	printk(KERN_ALERT "virtual_device open function called\n");
	return 0;
}

int virtual_device_release(struct inode *inode, struct file *filp) {
	printk(KERN_ALERT "virtual device release function called\n");
	return 0;
}

ssize_t virtual_device_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) {
	printk(KERN_ALERT "virtual_device write function called\n");
	strcpy(buffer, buf);
	return count;
}

ssize_t virtual_device_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) {
	printk(KERN_ALERT "virtual_device read function called\n");
	copy_to_user(buf, buffer, 1024);
	return count;
}

static struct file_operations vd_fops = {
	.read = virtual_device_read,
	.write = virtual_device_write,
	.open = virtual_device_open,
	.release = virtual_device_release
};

int __init virtual_device_init(void) {
	if(register_chrdev(250, "virtual_device", &vd_fops) < 0 )
		printk(KERN_ALERT "driver init failed\n");
	else 
		printk(KERN_ALERT "driver init successful\n");
	buffer = (char*)kmalloc(1024, GFP_KERNEL);
	if(buffer != NULL) 
		memset(buffer, 0, 1024);
	return 0;
}

void __exit virtual_device_exit(void) {
	unregister_chrdev(250, "virtual_device");
	printk(KERN_ALERT "driver cleanup successful\n");
	kfree(buffer);
}

module_init(virtual_device_init);
module_exit(virtual_device_exit);
MODULE_LICENSE("GPL");

이전에 언급했던 모듈 프로그래밍과 위에서 주목하여 봤던 함수들만 주의깊게 본다면 코드를 이해하는 것은 크게 어렵지 않을 것이다.


3. 모듈 컴파일

제작한 코드를 모듈때와 마찬가지로 Makefile을 통해 컴파일 해주도록하자.


[Makefile]

KERNELDIR = /lib/modules/$(shell uname -r)/build

obj-m = virtual_device.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:
	rm -rf *.ko
	rm -rf *.mod.*
	rm -rf .*.cmd
	rm -rf *.o



4. 모듈 등록 및 등록 확인

정상적으로 컴파일이 완료되었다면 폴더에 virtual_device.ko라는 파일이 생성되었을 것이다. 해당 파일을 커널에 등록하기 위해 아래의 명령어를 이용하자.

$sudo insmod virtual_device.ko


등록을 마쳤다면, 정상적으로 모듈이 등록되었는지 확인 한 후 다음 단계를 진행해보자. 아래의 명령어를 이용하면 등록 여부를 확인할 수 있다. 아래의 화면처럼 출력된다면 등록이 정상적으로 진행된 것이다.

$sudo lsmod | grep virtual_device



5. 노드 추가

위에서 언급한 것처럼 문자 디바이스 드라이버는 /dev 디렉토리 내부에 노드로써 존재한다. 따라서 더미 디바이스를 mknod 명령어를 통해 등록해줘야 한다.

$sudo mknod /dev/virtual_device c 250 0


파라미터에 대해서 간단히 알아보면 좌측에서부터 순서대로 디바이스 네임, 디바이스 타입(문자 or 블록), Major Number, Minor Number다. 노드 추가가 정상적으로 진행됐는지 아래의 명령어로 확인해보자.

$sudo ls -al /dev | grep virtual_device



위의 화면을 보면, 타입은 'c'로 등록되었고, Major Number와 Minor Number가 각각 250, 0으로 할당됐으며, 노드의 이름은 virtual_device임을 확인할 수 있다. 내용이 길어져 진행사항을 확인해 볼 필요가 있다. 아래의 그림을 통해 지금까지의 과정과 앞으로 남은 과정에 대해서 짚고 넘어가자. 현재까지 진행된 것은 디바이스 드라이버를 작성하고, 디바이스 파일(노드)를 생성하고, insmod 명령어를 통해 디바이스 드라이버를 커널에 적재까지 완료했다. 즉 아래의 네 개의 박스 중에 좌측 세 개의 과정이 일단락되었고, 우리가 제작한 문자 디바이스 드라이버를 활용한 테스트 응용프로그램이 남았다.



6. 응용프로그램 제작 및 테스트

그럼 커널에 적재한 디바이스 드라이버가 정상적으로 동작하는지 확인하기 위해 간단한 테스트용 애플리케이션을 제작해보자.


[test.c]

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
	int dev;
	char buff[1024];

	printf("Device driver test.\n");

	dev = open("/dev/virtual_device", O_RDWR);
	printf("dev = %d\n", dev);

	write(dev, "1234", 4);
	read(dev, buff, 4);
	printf("read from device: %s\n", buff);
	close(dev);

	exit(EXIT_SUCCESS);
}

	

코드 내용에 대해서 주목할만한 부분을 추려보면 아래와 같다. 마치 일반 파일을 액세스할 때와 같은 연산(open, read ...)들을 통해 디바이스를 조작하고 있다. 

dev = open("/dev/virtual_device", O_RDWR);

write(dev, "1234", 4);

read(dev, buff, 4);

close(dev);


소스 코드를 모두 작성했다면 아래의 순서대로 명령어를 입력하여 컴파일 후 실행시켜보자. 맨 아래에 화면처럼 나온다면 정상적으로 동작하는 것이다.

$gcc -o test test.c


$./test




7. 마치며...

지금까지 모듈 프로그래밍 안에서 문자 디바이스 드라이버를 제작하고, 등록했으며 이를 활용한 응용프로그램을 제작하여 테스트해봤다. 테스트 결과 정상적으로 동작하는 것을 확인했으며 커널이 제공하는 VFS 서비스를 통해서 디바이스를 마치 파일처럼 접근할 수 있다는 것을 알 수 있었다. 모듈 프로그래밍 보다 아주 조금 더 내용이 깊어진 기분이다. 하지만 이론을 익히고 실습을 통해서 이해하는데 큰 어려움은 없었다. 지금까지의 과정을 한 눈에 보기쉽게 설명된 그림을 찾아, 공유하고자 첨부한다.




그림을 자세히 들여다보며 정리하면 각 번호는 다음의 과정을 의미한다.

① 디바이스 드라이버가 포함된 모듈을 컴파일하고 insmod 명령어를 통해 커널에 등록

② 모듈 초기화 루틴에 의해 module_init() 매크로가 실행

③ module_init() 내부의 register_chrdev 함수에 의해 디바이스가 등록

④ 디바이스 연산이 매핑되는 과정


위 그림의 과정을 이해할 수 있다면 가장 기본적인 문자 디바이스 드라이버의 동작에 대해서 이해했다고 볼 수 있다. 실습해 볼 당시에는 시간이 얼마 안 걸렸는데 오히려 설명과 함께 실습한 내용을 전달하려다보니 부쩍 시간이 오래걸린다. 그래도 실습한 과정을 토대로 포스팅을 작성해보니 복습하면서 보다 오래 기억에 남을 것 같다. 차츰차츰 쌓이는 포스팅만큼 아는 것이 많아지고, 알고 있는 내용을 많은 사람들과 공유할 수 있기를 희망한다.



VM(Virtual Machine)구현이 가능한 소프트웨어에는 대표적으로 VirtualBox, VMware, Parallels 등이 있다. 그 중에 필자는 ORACLE사의 VirtualBox(이하 버추얼박스)를 사용한다. 위에서 언급한 소프트웨어 중 VMware의 경우 설치 후에 정상적으로 해상도가 설정되지만 버추얼박스의 경우에는 그렇지 않다. 때문에 보다 편한 환경에서 작업하기 위해 간단한 작업을 해야한다.


굳이 기재할 필요는 없겠지만, 버추얼박스 버전 5.0과 가상머신에서 사용하는 OS는 우분투 버전 14.04.3 LTS을 이용했다. 아마도 버추얼박스 자체의 해상도 설정이 요구되는 것이므로 우분투가 아닌 다른 리눅스 배포판 이미지를 사용하더라도 아래의 작업이 필요할 것으로 예상된다. 


여담이지만, 필자가 이전에 맥북을 사용할 때에는 VM 구현을 위해 패러럴즈를 사용했었다. 당시 패러럴즈의 경우 우분투 최신 버전이 아닌 구 버전(12.xx)을 설치했을 때 쾌적한 환경에서 우분투를 이용할 수 있었다. 비록 확인은 하지 않았으나, 소프트웨어의 종류, 리눅스 배포판, 버전차이에 따라서 환경이 조금씩 변하는 것 같다. 쓰고보니 너무 당연한 소리같아서 민망하다. 하지만 귀찮더라도 세 가지의 조건을 고려하여 자신에게 적합한 환경을 구축하는 것이 바람직할 것이다.



1. 초기화면


한 눈에 확인할 수 있듯이 화면이 너무 작다. 터미널 창을 하나만 켜도 밖으로 벗어날 정도로 작업하기에는 영 불편하지 않을 수 없다. 모니터 화면에 비해 약 1/4도 안되는 크기의 화면에서는 누가 작업하든 답답함을 느낄 수 밖에 없을 것이다. 간단한 과정을 통하면 금방 개선될 수 있으니 혹여나 버추얼박스에 우분투를 설치한다면 가장 먼저 해상도부터 설정해주도록 하자.




2. 게스트 확장 CD이미지 삽입


다른 블로그에 게시된 글을 보면 최근에 [게스트 확장 설치]에서 [게스트 확장 CD 이미지 삽입...]으로 텍스트가 변경된 듯 하다. 



각설하고 해당 버튼을 누르면 팝업이 뜨는데 Yes를 눌러주면 설치가 자동으로 진행된다. 필자의 경험을 말해보자면, 블로그에 게시하기 위해서 캡쳐를 위해 팝업에서 Cancel를 눌렀는데, 다시 [게스트 확장 CD 이미지 삽입...]을 눌러도 팝업이 안 떠서 터미널에서 수동으로 설치해야 했다. 버추얼박스에서 이용하는 게스트 확장기능은 해상도 설정 및 공유 폴더 마운트 등 각종 기능을 동반하고 있으니 설치하는 편이 여러모로 유익하다. 



게스트 확장 설치가 자동으로 진행된 이후에는 재부팅을 해주자. 우측 위에 톱니바퀴를 누르거나 버추얼박스 메뉴바에서 재부팅을 할 수 있지만 좀 더 멋지게 재부팅하기 위해서는 터미널에 아래의 명령어를 입력하면 된다. (터미널도 [ctrl + alt + t]로 열어준다면 멋짐이 더 증폭된다.) 사실, 필자도 이전까지는 종료나 재부팅을 위해서 똑같은 방법으로 했었다. 그러나 최근 왠지 마우스 사용을 지양하고 키보드 기반으로 모든 작업을 진행해보고자 이런 방법을 애용하고 있다.


$sudo shutdown -r now    (재부팅)

$sudo shutdown -P now        (종료)



3. 설정 이후 화면



재부팅이 되고나서 화면으로 이전화면이 비하면 너무나 쾌적하다는 것을 느낄 수 있다. 자신의 모니터가 제공하는 해상도에 따라서 최적의 해상도를 찾아주는 것 같다. 굳이 게스트 확장 설치의 필요성을 못 느낀다면 할 말 없지만, 누군가가 버추얼박스에서 VM을 구동하려 한다면 속이 다 타버리기 전에 적극적으로 이 방법을 알려줘야겠다.



 



리눅스 커널에 대해서 공부를 진행하면서, 이론 뿐만아니라 실제적으로 해볼 수 있는 실습들에 주목하고 있다. 한 번 해보고 지나가면 금방 잊어버리기 때문에 보다 오래 기억하고자, 그리고 다른 사람들과 공유하고자 공부한 내용을 정리하기로 했다. 오늘은 모듈 프로그래밍이다. 리눅스 커널에 대해서 공부하고 있는 다른 사람들에게도 좋은 예시가 될 것이라 생각하며 공부하는 동안 알게된 내용들을 정리해본다. 이전에 게시했던 버추얼박스 공유폴더 설정과 마찬가지로 버추얼박스 버전 5.0과 우분투 버전 14.04.3 LTS을 이용했음을 미리 알린다. 이와 더불어 아래에 진행되는 실습은 커널 3.19.0-25-generic 버전을 활용했다.



0. 준비




모듈 프로그래밍 실습을 하기 앞서 모듈 프로그래밍을 왜 사용하는지, 어떤 특징이 있는지 알아보자. 이를 파악하기 이전에 커널의 종류에 대해서 먼저 알아볼 필요가 있다. 위 그림을 참조해보면 커널을 크게 세 가지의 형태로 구분할 수 있다. Monolithic Kernel, Microkernel 그리고, Hybrid Kernel. 위 그림에서 빨간색으로 표시된 영역이 커널 영역인데 종류에 따라서 관장하는 부분이 각기 다르다.



0.1. Monolithic Kernel

Monolithic Kernel은 흔히 일체형 커널이라고 부른다. 모든 컴포넌트(VFS, System Call, IPC, File System, Scheduler, VM, Device Driver ...)들이 커널에 포함되어 있다. 각 커널 계층이 하나의 커널 프로그램으로 통합되고, 현재 프로세스를 대신하여 커널 모드에서 동작한다. 마이크로 커널에 비하여 성능이 좋은데, 그 이유는 이미 모든 컴포넌트를 포함하고 있기 때문에 계층 사이에 메시지 전달에 비용이 적게 소모된다. 대표적으로 Linux, BSD, Solaris, MS-DOS가 일체형 커널이다. 



0.2. Microkernel

마이크로커널은 일반적으로 매우 적은 기능만을 포함한다. 따라서 몇 가지 시스템 프로세스를 실행하여 Memory Management, Device Driver, System Call Handler 등의 일체형 커널에 적재되어 있는 컴포넌트를 구현한다. 가장 큰 장점은 불필요한 기능을 구현하는 시스템 프로세스를 스왑 아웃하거나 없앨 수 있어서일체형 커널보다 RAM 사용을 효율적으로 하는 경향이 있다. 그럼에도 불구하고 일체형 커널에 비해 성능이 떨어지는 단점을 안고있다. Mach, GNU hurd, Minix가 대표적인 마이크로커널이다.



0.3. Hybrid Kernel

하이브리드라는 단어에서 의미하는 것처럼 일체형 커널과 마이크로 커널의 혼합된 형태로 이해하면 될 것 같다. 일체형 커널의 장점과 마이크로 커널의 장점을 모으려한 의도를 엿볼 수 있다. 핵심적인 컴포넌트들은 일체형 커널처럼 포함시키며, 그 외의 자주 변경될만한 부분은 모듈로 분리해 놓은 방식이다. 커널 자체의 핵심 서비스가 변경되지 않는 다면 커널 자체를 재컴파일할 필요가 없다. 또한 핵심 서비스는 커널에 포함되어 있으므로 괜찮은 성능을 보장한다. 최근 발행되는 신형 운영체제들이 이와같은 방식을 따르고 있다고 한다.


하이브리드 커널에서 잠시 언급한 것처럼 커널의 핵심 서비스가 변경된다면 커널 자체를 재컴파일 해야한다. 실제로 이를 진행해봤지만 커널 크기가 상당한 관계(특히, 일체형 커널)로 시간을 많이 잡아먹는 작업이다. 이런 불편함을 개선하기 위해 커널 전체를 재컴파일 할 필요 없이 필요한 기능을 적재하고, 필요없어지면 다시 제거하는 방식으로 커널의 서비스를 부분적으로 수정할 수 있다. 이를 위해 필요한 것이 바로 모듈 프로그래밍이다.


비로소 왜 모듈 프로그래밍을 해야하는지 이유가 나왔다. 하지만 위의 이유는 모듈 프로그래밍을 해야하는 큰 이유 중에 곁가지에 불과하다. 모듈 프로그래밍을 통해서 얻을 수 있는 가장 큰 이점은 모듈이 정적으로 링크된 커널의 오브젝트와 동등하게 취급되므로 함수를 호출할 때 별도의 메시지 전달이 필요없다는 것이다. 한 마디로 성능이 떨어지지 않는다. 또한 실행 중에 링크/언링크가 가능하여 커널의 각 컴포넌트에 모듈화된 접근을 가능케하며, 덩달아 주 메모리를 효율적으로 쓸 수 있다.


그럼 이제부터 본격적인 모듈 프로그래밍 실습을 해보자.



1. 모듈 프로그래밍 과정




모듈 프로그래밍은 다음과 같은 과정을 통해 진행된다.

[1] 모듈 프로그램 작성

[2] 모듈 프로그램 컴파일

[3] 모듈 로드 - insmod

[4] 모듈 확인 - lsmod

[5] 모듈 제거 - rmmod



[1] 모듈 프로그램 작성


함수가 정의되어 있는 callee와 이를 호출하여 사용하는 caller를 제작해보자. 우선 callee부터 작성해보면,


[callee.c]

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

int __init init_callee(void) {
	return 0;
}

void __exit exit_callee(void) {

}

int add(int a, int b) {
	printk(KERN_ALERT "[callee] add called...\n");
	return a + b;
}

int sub(int a, int b) {
	printk(KERN_ALERT "[callee] sub called...\n");
	return a - b;
}

EXPORT_SYMBOL(add);
EXPORT_SYMBOL(sub);
module_init(init_callee);
module_exit(exit_callee);
MODULE_LICENSE("GPL");


[caller.c]

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

int add(int, int);
int sub(int, int);

int __init init_caller(void) {
	printk(KERN_ALERT "[caller] I'll call add(), sub() from callee.\n");
	printk(KERN_ALERT "[caller] add: %d\n", add(3, 2));
	printk(KERN_ALERT "[caller] sub: %d\n", sub(3, 2));
	return 0;
}

void __exit exit_caller(void) {

}

module_init(init_caller);
module_exit(exit_caller);
MODULE_LICENSE("GPL")


간단한 모듈 callee와 caller 작성이 완료됐다. 코드에 대해서 필요한 설명을 추가하자면 callee의 23~27라인, caller의 19~21라인에 주목할 필요가 있다. EXPORT_SYMBOL의 경우 모듈에서 작성한 함수(add, sub)들을 심볼 테이블에 등록하는 것인데, 커널 내부의 모든 함수와 전역변수, 모듈에서 작성한 함수와 전역변수는 모두 심볼로 제공된다. 더불어 모듈을 등록하기 위해서는 모듈 작성 당시 모듈의 초기화 루틴과 종료 루틴을 설정해야 한다. module_init, module_exit가 바로 그 부분에 해당한다. 이제 이들을 적재할 수 있도록 컴파일 해보자



[2] 모듈 프로그램 컴파일


컴파일 하기 위해 Makefile을 이용하자. 위에서 작성한 callee와 caller와 같은 위치에 Makefile을 다음과 같이 작성한다.


[Makefile]

KERNELDIR = /lib/modules/$(shell uname -r)/build

obj-m = callee.o caller.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:
	rm -rf *.ko
	rm -rf *.mod.*
	rm -rf .*.cmd
	rm -rf *.o


Makefile을 통해 두 모듈을 적재가능한 형태로 컴파일하고 있다. 이때 주의할 점은 ##행과 ##행 이후의 부분은 반드시 탭으로 띄어쓰기를 해야한다. 컴파일이 완료되면 폴더에 callee.ko 파일과 caller.ko 파일이 생성된 것을 확인할 수 있다. 그럼 이제 모듈을 직접 커널에 적재하고 확인해 볼 차례다.



[3] 모듈 로드 - insmod


이전 과정들에 비하면 비교적 간단하게 적재할 수 있다. 다만 한 가지 주의할 점은 두 모듈을 순서에 맞게 적재해야 한다. 만약 caller 모듈을 먼저 적재한다면 다음과 같은 에러 메세지를 확인할 수 있다.

$sudo insmod caller.ko

insmod: ERROR: could not insert module caller.ko: Unknown symbol in module


caller 모듈에는 callee의 함수를 호출하고 있는데 callee가 없다면 적재가 불가능하기 때문이다. 이런 이유로 callee → caller 순으로 적재 해야함을 명심하자.

$sudo insmod callee.ko

$sudo insmod caller.ko


이제 정상적으로 두 모듈이 커널에 적재되었다.


[4] 모듈 확인 - lsmod


적재된 모듈을 확인하기 위해 아래의 명령어를 쉘에 입력한다.

$sudo lsmod | grep call



위의 명령어를 입력하면 정상적으로 두 모듈이 적재된 것을 확인할 수 있다. 두 숫자를 통해 모듈의 사이즈, 해당 모듈을 참조하는 모듈의 이름과 개수를 파악할 수 있다. caller 모듈의 경우 '0'. 즉 caller 모듈을 참조하는 다른 모듈이 없음을 알 수 있으며, callee의 경우 하나의 모듈이 callee를 참조하고 있는데 그것이 바로 caller 임을 표시하고 있다.


모듈이 정상적으로 적재된 것을 lsmod 명령어를 통해 확인할 수 있지만, 다른 방법을 통해서 확인할 수 있다.

$dmesg | grep calle


위 명령어 dmesg는 커널의 메세지 버퍼를 출력하는 것인데 작성한 모듈 코드의 printk 함수를 통해 커널 메세지 영역에 작성한 것을 쉘 상에서 볼 수 있는 명령어다. 따라서 위 명령어를 입력해보면 아래와 같이 출력되는 것을 확인할 수 있다.



뿐만아니라 앞서 설명할 때 모듈 내에서 작성한 함수는 EXPORT_SYMBOL 명령어를 통해 심볼로써 심볼 테이블에 등록된다고 언급했다. 정상적으로 심볼 테이블에 등록이 되었는지 확인해볼 수 있다. /proc/kallsyms 내에서 등록한 심볼들을 확인할 수 있다.

$sudo cat /proc/kallsyms | grep calle


위 명령어를 이용하면 아래와 같이 정상적으로 심볼 테이블에 등록되었음을 알 수 있다.




[5] 모듈 제거 - rmmod


모듈은 필요할 때 적재하여 사용하고, 더이상 사용하지 않을 때 제거할 수 있는 특장점이 있었다. 지금까지 성공적으로 커널에 모듈을 적재했다면, 이번에는 반대로 제거해보자. 모듈을 제거하는 명령어는 다음과 같다.

$sudo rmmod caller

$sudo rmmod callee


이 때, 커널에 모듈을 적재할 때와 반대로 진행해야 한다는 점에 주목할 필요가 있다. 앞선 이유와 마찬가지로 참조되는 모듈을 먼저 제거하려고 하면 아래와 같은 에러 메세지를 확인할 수 있다. caller라는 모듈에서 참조되고 있으므로 제거할 수 없다는 의미다. 따라서 이번에는 caller → callee 순으로 제거하자.

$sudo rmmod callee

rmmod: ERROR: Module callee is in use by: caller



이론적으로는 각 커널의 특징부터 모듈 프로그래밍의 필요성을 알아봤고, 실습을 통해 모듈을 작성, 적재, 확인, 제거까지 하는 과정을 완료했다. 일회성 실습에 그치지 않고 꾸준히 학습한 내용을 기억하여 보다 유연하게 커널을 조작할 수 있기를 희망한다.



[참고 자료]

http://tmdgus.tistory.com/114

http://goo.gl/sTC4pX

https://en.wikipedia.org/wiki/Kernel_(operating_system)




-13. 05. 02

아침일찍 떠난 대전여행, 발표는 만족스럽지 못했지만 좋은 사람들과.


금의한양-

희민이형, 원경이, 승주, 마음이.


-16.01.27

연락이 뜸한 희민이형은 현대로

취업을 절대 못할 것이라 생각했던 원경이는 SK로

어디서든 잘할 것 같았던 마음이는 LINE으로

난 광주과학기술원으로.


나만 잘되면 되겠다.