우리가 저번 시간에 부트로드 소스 안에서 하드웨어를 제어해보있다.
이제는 커널 소스 안에서 하드웨어를 제어해보고자 하는 것이다.
어플리케이션에서 우리가 만든 시스템 콜 함수를 통해서 하드웨어를 제어하는 것이 종국적인 목표이다.
1. 어플리케이션 작성 필요
2. 시스템 콜 함수 작성 필요
(우리가 시스템 콜 함수를 직접 만들어 보는 것은 원리를 학습하는 것에 의미를 두는 것이다. 나중에 실무에서는 이렇게 직접 시스템 콜 함수를 만들지는 않는다. 왜냐하면 이렇게 커스텀 시스템 콜 함수를 만드는 순간 POSIX API를 벗어난 비표준 커널이 되기 때문이다.)
(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을 만든다.)
시스템 콜 번호 및 함수를 등록해야 한다. 아래 명령을 통해 헤더파일에 들어가자.
ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ vi include/uapi/asm-generic/unistd.h
이 헤더파일 안에는 모든 시스템 콜 함수의 콜 넘버가 등록되어있다. 우리가 앞에서 실습하면서 썼던 open(), close(), read(), write() 등등의 함수도 여기에 등록되어있는 것을 알 수 있다.
어쨌든 이 파일에서 888번째 아래라인에다가 아래 내용을 추가했다.(451 호출번호를 가진 나만의 시스템 콜을 추가하면서 전체 갯수가 한개 증가했으므로 893번째 라인의 코드도 451에서 452로 수정해주었다.)
ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ vi arch/arm/tools/syscall.tbl
위 명령을 통해 접근한 호출 테이블을 보면 사용 가능한 시스템 콜들을 전부 볼 수 있다.
위 파일의 맨 아래쪽에다가 아까 추가한 시스템 콜을 등록해준다.
자 이렇게 호출 테이블에 등록해놓은 시스템 콜 함수를 나중에 어플리케이션을 만들 때 실질적으로 쓸라면, 어플리케이션에서 include하는 시스템콜 관련 헤더파일을 수정해줘야 한다.(해당 헤더파일의 맨 마지막 줄에 새로 만든 시스템 콜을 등록해줘야 한다는 것이다.)
ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ vi include/linux/syscalls.h
아래 명령어를 통해 커널 함수를 작성할 새로운 파일 하나를 생성한다.
ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ vi kernel/test_mysyscall.c
그리고 그 파일에 아래의 코드를 작성한다.
이 커널 함수는 파라미터로 들어온 값을 제곱해서 리턴해주는 코드이다.
어플리케이션에서는 printf()를 사용했다.
하지만 커널에서는 printk()를 사용해야 한다.
KERN_INFO라는 것은 커널 메세지의 레벨을 나타내는 것이다.
(예전에 인텔 프로젝트 할 때 log level에서 한번 봤던 그것이다.)
이 함수를 작성하는 영역은 커널 영역이기 때문에 모~~~든 하드웨어 주소에 접근 가능한 곳이다!!!
ubuntu@ubuntu14:~/pi_bsp/kernel/linux$ vi kernel/Makefile
13번째 라인에 "test_mysyscall.o"를 다이렉트로 등록해줘서 바로 사용가능하도록 만들었다.
수정하고 있는 파일이 의존성이 깊으면 깊을수록 컴파일 시간이 길어진다. 왜냐하면 수정한 파일에 의존하고 있는 파일들을 전부다 새로 컴파일 해야 하기 때문이다.
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)가 만들어졌는지 확인해본다. 제대로 만들어졌다면 된 것이다.
빌드된 커널 이미지를 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
제대로 방금 빌드한 커널 이미지가 적용된 것을 볼 수 있다.
이제 내가 만든 시스템 콜 함수를 사용하는 간단한 어플리케이션을 만들어보도록 하겠다. 라즈베리파이에서 만들어보겠다.
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
<라즈베리파이>
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
GIO 커널 API를 나중에 디바이스 드라이버를 만들 때 그대로 복붙해서 그대로 사용할 것이기 때문에, 내일 할 내용은 굉장히 중요한 부분이 될 것이다.