시스템 콜 함수 구현

kenGwon·2024년 2월 15일
0

[Embedded Linux] BSP

목록 보기
12/36

우리가 저번 시간에 부트로드 소스 안에서 하드웨어를 제어해보있다.
이제는 커널 소스 안에서 하드웨어를 제어해보고자 하는 것이다.
어플리케이션에서 우리가 만든 시스템 콜 함수를 통해서 하드웨어를 제어하는 것이 종국적인 목표이다.
1. 어플리케이션 작성 필요
2. 시스템 콜 함수 작성 필요
(우리가 시스템 콜 함수를 직접 만들어 보는 것은 원리를 학습하는 것에 의미를 두는 것이다. 나중에 실무에서는 이렇게 직접 시스템 콜 함수를 만들지는 않는다. 왜냐하면 이렇게 커스텀 시스템 콜 함수를 만드는 순간 POSIX API를 벗어난 비표준 커널이 되기 때문이다.)

시스템 콜

  • POSIX(Portable Operating System Interface) API: 이식 가능한 운영 체제 인터페이스
  • POSIX 표준 API를 사용하는 시스템 콜 덕분에 호환성이 좋아져서 프로그래밍이 수월해졌다.

  • int 0x80: 0x80 인터럽트(interrupt)를 발생시켰다는 뜻이다.
  • 그러면 0x80에 정의되어있는 시스템 콜이 호출되어 실행된다.
  • 우리가 시스템 콜 함수를 만들면 그 커널은 전세계에서 하나뿐인 커널이 된다. 그 즉시 비표준 커널이 되는 것이다. 그래서 우리가 앞으로 실습하려는 내용처럼 현업에서 일하지는 않는다고 한다. 우리는 시스템 콜 함수의 작동원리를 이해하기 위해서 앞으로의 실습을 하는 것이다.



사전작업: GPIO driver 삭제

(2/16 추가) GPIO driver를 disale하니까 7인치 LCD 화면을 비롯한 데스크탑이 안떠서 다시 enable시켜주었다.

디바이스 드라이버를 만들라고 하는데, 어딘가에서 GPIO를 사용하고 있는 작업이 돌고 있다면 해당 GPIO에 대해서 접근이 불가능해진다.

그래서 만에 하나의 가능성이라도 원천적으로 차단하기 위해서 sys파일 및에서 GPIO를 제거해서 아예 그 어떤 프로그램도 GPIO를 사용하지 못하도록 하겠다.

(꼭 이렇게 안해도 되는데, 진짜 혹시 몰라서 해놓는 것이다. Wiring Pi 같은거 돌아가다가 충돌이 일어날수도 있다. )

ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

옵션접근 순서
1. Device Drivers
2. GPIO Support
3. /sys/class/gpio/... (sysfs interface) 체크 해제
Character device (/dev/gpiochipN) support 체크 해제



이 디바이스 드라이버 수업이 끝나고 나서 이 옵션을 다시 복원해줘야 Wiring Pi 같은 라이브러리를 다시 사용할 수 있게 된다!!!!



나만의 시스템 콜 함수 만들기

목적

기존 kernel에서 제공하지 않는 service를 user application에 제공(= 새로운 System Call을 만든다.)

  1. 커널 수정
    • System call 번호 할당
    • System call 호출 테이블 등록
    • System call 호출 함수 구현
    • Kernel 컴파일 및 보드 퓨징, 재부팅
      • (참고) 플래시메모리에서 erase를 한다고 하면, 모든 플래시메모리에 0을 쓸 것 같지만, 실제로는 모두 1을 쓴다. 플래시메모리는 오직 1에서 0으로 바뀌는 방향만 가능한 식으로 정보를 저장한다. 0에서 1로 바뀌는 방향의 저장은 불가능하다. 그렇기 때문에 플래시메모리 정보를 모두 날릴 때는 전부 1로 초기화하는 것이다.
      • (참고) 보드 퓨징: 플래시메모리에 내용을 write하는 과정(옛날에 퓨즈가 끊어지는 것을 자주 봤을 것이다. 퓨즈가 끊어지면 1에서 0으로 바뀌는 것과 같은 느낌이다. 그래서 플래시메모리에 내용을 write하는 과정을 퓨징이라고 한다. 약간의 개발자들의 슬랭느낌이다.)
  2. user application 제작

(1-1) System call 번호 할당

시스템 콜 번호 및 함수를 등록해야 한다. 아래 명령을 통해 헤더파일에 들어가자.

ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ vi include/uapi/asm-generic/unistd.h

이 헤더파일 안에는 모든 시스템 콜 함수의 콜 넘버가 등록되어있다. 우리가 앞에서 실습하면서 썼던 open(), close(), read(), write() 등등의 함수도 여기에 등록되어있는 것을 알 수 있다.

어쨌든 이 파일에서 888번째 아래라인에다가 아래 내용을 추가했다.(451 호출번호를 가진 나만의 시스템 콜을 추가하면서 전체 갯수가 한개 증가했으므로 893번째 라인의 코드도 451에서 452로 수정해주었다.)

(1-2) System call 호출 테이블 등록

ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ vi arch/arm/tools/syscall.tbl

위 명령을 통해 접근한 호출 테이블을 보면 사용 가능한 시스템 콜들을 전부 볼 수 있다.
위 파일의 맨 아래쪽에다가 아까 추가한 시스템 콜을 등록해준다.

자 이렇게 호출 테이블에 등록해놓은 시스템 콜 함수를 나중에 어플리케이션을 만들 때 실질적으로 쓸라면, 어플리케이션에서 include하는 시스템콜 관련 헤더파일을 수정해줘야 한다.(해당 헤더파일의 맨 마지막 줄에 새로 만든 시스템 콜을 등록해줘야 한다는 것이다.)

ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ vi include/linux/syscalls.h

(1-3) System call 호출 함수 구현

# 소스파일 생성

아래 명령어를 통해 커널 함수를 작성할 새로운 파일 하나를 생성한다.

ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ vi kernel/test_mysyscall.c

그리고 그 파일에 아래의 코드를 작성한다.
이 커널 함수는 파라미터로 들어온 값을 제곱해서 리턴해주는 코드이다.

어플리케이션에서는 printf()를 사용했다.
하지만 커널에서는 printk()를 사용해야 한다.

KERN_INFO라는 것은 커널 메세지의 레벨을 나타내는 것이다.
(예전에 인텔 프로젝트 할 때 log level에서 한번 봤던 그것이다.)

이 함수를 작성하는 영역은 커널 영역이기 때문에 모~~~든 하드웨어 주소에 접근 가능한 곳이다!!!

# Makefile 수정

ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ vi kernel/Makefile

13번째 라인에 "test_mysyscall.o"를 다이렉트로 등록해줘서 바로 사용가능하도록 만들었다.

수정하고 있는 파일이 의존성이 깊으면 깊을수록 컴파일 시간이 길어진다. 왜냐하면 수정한 파일에 의존하고 있는 파일들을 전부다 새로 컴파일 해야 하기 때문이다.

(1-4) Kernel 컴파일 및 보드 퓨징, 재부팅

ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ ./build.sh

우리가 수정한 syscalls.h을 비롯한 시스템 콜 함수 관련된 파일들은 엄청나게 중요한 파일이라서 의존성이 매우 깊다. 그래서 아랫단계에서 ./build.sh를 통해 다시 빌드를 하면 엄청나게 많은 파일들을 다시 컴파일 하느라 엄청 오랜 시간이 걸리는 것을 확인할 수 있다.

ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ ls kernel/test_mysyscall.*
kernel/test_mysyscall.c  kernel/test_mysyscall.o

그리고 위 명령을 통해서 제대로 컴파일 되어서 object file(.o)가 만들어졌는지 확인해본다. 제대로 만들어졌다면 된 것이다.


(2-0) 빌드된 커널 라즈베리파이로 옮기기

빌드된 커널 이미지를 nfs를 통해 라즈베리파이로 옮기고, 어플리케이션은 라즈베리파이 쪽에서 만들어보도록 하겠다.

ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ cp arch/arm/boot/zImage /srv/nfs/
pi@pi14:/mnt/ubuntu_nfs$ sudo cp zImage /boot/firmware/kernel7l.img
pi@pi14:/mnt/ubuntu_nfs$ sync
pi@pi14:/mnt/ubuntu_nfs$ sudo reboot
pi@pi14:~$ uname -a
Linux pi14 6.1.77-v7l+ #3 SMP Thu Feb 15 16:15:30 KST 2024 armv7l GNU/Linux

제대로 방금 빌드한 커널 이미지가 적용된 것을 볼 수 있다.

(2-1) 어플리케이션 만들기

이제 내가 만든 시스템 콜 함수를 사용하는 간단한 어플리케이션을 만들어보도록 하겠다. 라즈베리파이에서 만들어보겠다.

pi@pi14:~$ mkdir systemcall_test
pi@pi14:~$ cd systemcall_test/
pi@pi14:~/systemcall_test$ vi syscall_app.c

위 명령으로 새 파일을 만들어서 아래 코드를 적어준다.

이미 우리 커널은 표준을 벗어났다.

그래서 그냥 위 파일을 컴파일할라고 하면 컴파일 에러가 나면서 안된다.
그래서 아까 우리가 우분투에서 만들어놨던 시스템콜 헤더파일을 라즈베리파이 쪽으로 가져와야 한다. 그래야만 컴파일이 된다.
("unistd.h" 이 파일을 옮겨야 하는 것이다.)

<우분투>

ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ cp include/uapi/asm-generic/unistd.h /srv/nfs/

<라즈베리파이>
우선 원본 unistd.h를 백업한다.

pi@pi14:/usr/include/asm-generic$ sudo mv unistd.h unistd.h_org

그리고 nfs로 가져온 것을 위 경로에 덮어쓴다.

pi@pi14:/usr/include/asm-generic$ sudo cp /mnt/ubuntu_nfs/unistd.h .

이제 비로소 컴파일이 가능하다.

pi@pi14:~/systemcall_test$ gcc syscall_app.c -o syscall_app

컴파일을 하고 실행해보자.

시스템 콜 함수를 통해서 제곱 연산을 했다.

시스템 콜 함수에서 printk() 한 것을 보고 싶다면 아래 명령어를 쳐보자.
그러면 맨 아래에 "Welcome to KCCI's Embedded System!! app value=5"라는 메세지가 적혀 있을 것이다.

pi@pi14:~/systemcall_test$ dmesg

(2-2) 똑같은 과정을 크로스 컴파일로 해보자.

<라즈베리파이>

pi@pi14:~$ cp -r systemcall_test/ /mnt/ubuntu_nfs/

<우분투>

ubuntu@ubuntu14:~/pi_bsp/kernel$ mv /srv/nfs/systemcall_test/ .
ubuntu@ubuntu14:~/pi_bsp/kernel$ cd systemcall_test/
ubuntu@ubuntu14:~/pi_bsp/kernel/systemcall_test$ ls
syscall_app  syscall_app.c
ubuntu@ubuntu14:~/pi_bsp/kernel/systemcall_test$ arm-linux-gnueabihf-gcc syscall_app.c -o syscall_app2
syscall_app.c: In function ‘main’:
syscall_app.c:11:21: error: ‘__NR_mysyscall’ undeclared (first use in this function)
   11 |         i = syscall(__NR_mysyscall,i);
      |                     ^~~~~~~~~~~~~~
syscall_app.c:11:21: note: each undeclared identifier is reported only once for each function it appears in

역시나 컴파일이 안된다. 당연하다. 헤더파일을 수정해주지 않았기 때문이다. 수정해주자.

수정을 해줘야 하는데 도대체 크로스컴파일용 gcc(arm-linux-gnueabihf-gcc)가 참조하는 unistd.h가 우분투 시스템의 어디에 저장되어있는지 찾기가 너무 힘들다. 그래서 잠깐 옆길로 빠져서 파일을 찾는 트릭을 알아보겠다.

그냥 find 명령어를 써서 unistd.h를 찾아도 되는데, 파일이 너무 많아서 느리다. 그래서 좀 빠르게 하기 위해서 locate 패키지를 인스톨 받고 그걸 통해서 db파일을 만들고, 거기다 대고 "찾기"를 해보겠다. 그러면 엄청나게 빠른 속도로 검색이 가능하다. find 대신에 locate 명령을 사용하는 것이다.

ubuntu@ubuntu14:~$ sudo apt install locate
ubuntu@ubuntu14:~$ sudo updatedb
ubuntu@ubuntu14:~$ locate unistd.h

이 과정을 거쳐서 내가 찾는 unistd.h 파일이 "/usr/arm-linux-gnueabihf/include/asm-generic/unistd.h" 경로에 저장되어 있다는 것을 확인하게 되었다.

// 백업파일 만들기
ubuntu@ubuntu14:~$ sudo cp /usr/arm-linux-gnueabihf/include/asm-generic/unistd.h /usr/arm-linux-gnueabihf/include/asm-generic/unistd.h_org

// 헤더파일 덮어쓰기
ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ sudo cp include/uapi/asm-generic/unistd.h /usr/arm-linux-gnueabihf/include/asm-generic/unistd.h

그리고 나서 다시 교차 컴파일 해보자. 그러면 컴파일이 정상적으로 된다.

ubuntu@ubuntu14:~/pi_bsp/kernel/systemcall_test$ arm-linux-gnueabihf-gcc syscall_app.c -o syscall_app2




내일 우리는 GPIO 커널 API를 만들 것이다.

GIO 커널 API를 나중에 디바이스 드라이버를 만들 때 그대로 복붙해서 그대로 사용할 것이기 때문에, 내일 할 내용은 굉장히 중요한 부분이 될 것이다.

profile
스펀지맨

0개의 댓글