<전날 과제 리뷰>
gets 대신 fgets를 사용하자
gets는 시작주소만 넘겨주지만 fgets는 시작주소와 사이즈를 같이 넘겨준다
그래서 버퍼 오버플로가 안일어난다
시스템 콜 함수, 디바이스 드라이버 모두 커널에서 동작해서 같은 함수를 사용할 수 있다
setup 하드웨어 초기화(init)
loop 반복적으로 상태 폴링, writting(do~while)
실행 속도와 코드 최적화를 생각하면서 코드를 작성할 것
현재는 모듈 프로그램 기법을 사용해야한다
시스템 콜 함수는 비표준이기 때문이다(내가 직접 만든 것)
어플리케이션 쓰려면 주번호로 매핑을 해야함(file operations)
지금 코드는 do~while문으로 무한루프 도는데 사실 좋은 방법은 아니다
gpio_get_value는 커널api에서 제공하는 하나의 gpio가 켜진지 꺼진지 확인하는 함수이다
시리얼통신은 직렬이라 8비트 데이터를 1비트씩 보내는데 shift 연산자를 이용해서 레지스터에 값을 쓴다
/mnt/ubuntu_nfs$ sudo cat /proc/kmsg
이 명령어로 백그라운드에서 돌면서 현재 써지는 내용을 볼 수 있다
교수님 코드
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#define OFF 0
#define ON 1
#define GPIOLEDCNT 8
#define GPIOKEYCNT 8
static int gpioLed[GPIOLEDCNT] = {6,7,8,9,10,11,12,13};
static int gpioKey[GPIOKEYCNT] = {16,17,18,19,20,21,22,23};
static int gpioLedInit(void);
static void gpioLedSet(long);
static void gpioLedFree(void);
static int gpioKeyInit(void);
static int gpioKeyGet(void);
static void gpioKeyFree(void);
static int gpioLedInit(void)
{
int i;
int ret=0;
char gpioName[10];
for(i=0;i<GPIOLEDCNT;i++)
{
sprintf(gpioName,"led%d",i);
ret = gpio_request(gpioLed[i],gpioName);
if(ret < 0) {
printk("Failed gpio_request() gpio%d error \n",i);
return ret;
}
ret = gpio_direction_output(gpioLed[i],OFF);
if(ret < 0) {
printk("Failed gpio_direction_output() gpio%d error \n",i);
return ret;
}
}
return ret;
}
static void gpioLedSet(long val)
{
int i;
for(i=0;i<GPIOLEDCNT;i++)
{
gpio_set_value(gpioLed[i],(val>>i) & 0x1);
}
}
static void gpioLedFree(void)
{
int i;
for(i=0;i<GPIOLEDCNT;i++)
{
gpio_free(gpioLed[i]);
}
}
static int gpioKeyInit(void)
{
int i;
int ret=0;
char gpioName[10];
for(i=0;i<GPIOKEYCNT;i++)
{
sprintf(gpioName,"key%d",gpioKey[i]);
ret = gpio_request(gpioKey[i], gpioName);
if(ret < 0) {
printk("Failed Request gpio%d error\n", 6);
return ret;
}
}
for(i=0;i<GPIOKEYCNT;i++)
{
ret = gpio_direction_input(gpioKey[i]);
if(ret < 0) {
printk("Failed direction_output gpio%d error\n", 6);
return ret;
}
}
return ret;
}
static int gpioKeyGet(void)
{
int i;
int ret;
int keyData=0;
for(i=0;i<GPIOKEYCNT;i++)
{
// ret=gpio_get_value(gpioKey[i]) << i;
// keyData |= ret;
ret=gpio_get_value(gpioKey[i]);
keyData = keyData | ( ret << i );
}
return keyData;
}
static void gpioKeyFree(void)
{
int i;
for(i=0;i<GPIOKEYCNT;i++)
{
gpio_free(gpioKey[i]);
}
}
static int hello_init(void)
{
int i, ret, key_data, key_data_old=0;
char ledStatus[16]=" : : : : : : : ";
printk("Hello, world \n");
ret=gpioLedInit();
if(ret < 0) {
return ret; }
ret=gpioKeyInit();
if(ret < 0) {
return ret; }
do {
key_data=gpioKeyGet();
if(key_data != key_data_old)
{
if(key_data)
{
mdelay(100);
gpioLedSet(key_data);
printk("0:1:2:3:4:5:6:7\n");
for(i=0;i<8;i++)
{
if((key_data >> i) & 0x01)
ledStatus[i*2] = 'O';
else
ledStatus[i*2] = 'X';
}
printk("%s\n\n", ledStatus);
}
key_data_old = key_data;
if (key_data == 0x80)
break;
}
} while(1);
return 0;
}
static void hello_exit(void)
{
printk("Goodbye, world \n");
gpioLedSet(0x00);
printk("all off \n");
gpioLedFree();
gpioKeyFree();
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
모듈 만들 때 다음 헤더가 필요하다
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
Makefile 해석
컴파일은 make로 한다
:=와 =의 차이 : :=는 이전의 내용을 초기화 한다는 뜻
default 전까지는 모두 변수 선언한 것
모듈 프로그램은 커널소스를 가지고 컴파일한다
그래서 KDIR이 꼭 들어가야함
외부 명령 pwd를 PWD에 저장
default, clean은 라벨(옵션)이다
형태 -> 목적파일 : 필요파일
그 아래의 명령어들은 반드시 sp가 아닌 tap으로 해야한다
-C : 뒤의 디렉토리에 있는 것으로 make 하라는 뜻
M : 소스는 현재 경로에 있는 것을 모듈로 컴파일 하라는 뜻
ls -lt : 최근 순으로 보여줌
소스 파일이 오브젝트 파일보다 먼저 수정되었으면 make 하지 않고, 소스파일이 더 최근에 수정되었으면 make를 한다
현재는 gcc로 되어있지만 ARM용으로 하고싶으면 위와 주석을 바꾸면 된다
-D_REENTRANT : 스레드에 안전한 함수로 링크되게 함(동기식)
간단하게 말하면 복잡한 gcc 과정을 make 단어 하나로 대체할 수 있다는 것
의존성 관계만 잘 파악해서 컴파일 해준다
sudo cat /proc/kallsyms | grep hello
hello라는 이름의 적재된 주소를 확인 가능
우분투의 아까 사용했던 코드에
이렇게 추가한다
그러면 파이에 modinfo를 통해 확인할 수 있다
p106_ledkey 폴더를 복사하여
p122_modparam으로 만들고
그 안의 파일 구성을
이렇게 바꾸고 Makefile 내의 변수 이름도 수정했다
그리고 c 파일을 수정한다
#include <linux/moduleparam.h>
static int onevalue=1;
static char * twostring = NULL;
module_param(onevalue, int, 0);
module_param(twostring, charp, 0);
추가한 내용은 위와 같다
그리고 do~while을 지우고 나머지 indentation을 맞춰준다(v -> =)
static int hello_init(void)
{
int i, ret, key_data;
char ledStatus[16]="0:0:0:0:0:0:0:0";
printk("Hello, world onevalue:%d, twostring:%s \n", onevalue, twostring);
ret=gpioLedInit();
if(ret < 0) return ret;
ret=gpioKeyInit();
if(ret < 0) return ret;
gpioLedSet(onevalue);
key_data=gpioKeyGet();
if(key_data)
{
gpioLedSet(key_data);
printk("0:1:2:3:4:5:6:7\n");
for(i=0;i<8;i++)
{
if((key_data >> i) & 0x01)
ledStatus[i*2] = 'O';
else
ledStatus[i*2] = 'X';
}
printk("%s\n\n", ledStatus);
}
return 0;
}
이렇게
make하고 다시 실행해본다
/mnt/ubuntu_nfs$ sudo insmod ledkey_modparam.ko onevalue=0xff twostring=hi
그런데 twostring에 white space가 있으면 안올라간다(큰 따옴표로 묶어도 안된다)
항상 순서는 insmod -> dmesg -> rmmod 로 할 것
키가 안눌리면 if문 진입이 안된다
그래서 키를 누른 상태에서 insmod를 해야 불이 원하는 대로 켜진다
변수 자료형은 왼쪽에 있는 대로 쓸 것
printk 함수
표준 입출력 함수가 아님
커널은 표준 입출력 함수가 필요가 없다
원형 큐 메모리에 저장됨
상태를 볼 때는 꼭 \n이 있어야 한다
로그레벨을 지정할 것인데
직접 printk("<4>"); 이렇게 써도 되지만 상수로 써도 된다
원형 큐는 꽉 차면 overwritting된다
dmesg를 했을 때 0초부터 나온다는 것은 아직 버퍼가 꽉 차지 않은 것이다
모듈로 만드는 이유
커널에 빌트인하면 사용안해도 메모리를 소모한다
모듈로 하면 사용할 때만 올리고, 필요 없을 때 내려서 메모리 소모를 줄일 수 있다
나중에 갑자기 메모리가 필요할 때를 대비해서 메모리풀을 하나 만들어 놓는다
동적 메모리 할당 : kmalloc, kfree 등 나중에 디바이스드라이브에서 다룰 것이다
중복 함수명과 변수명 방지
변수와 함수 모두 static으로 사용한다
type definition
이렇게 해도 make 된다
이런식으로 쓰는 이유 :
1. typedef는 구조체에서 많이 쓴다
struct student{
int;
char;..
};
main()
{struct student st;}
이렇게 길게 선언해야하는데
#typedef struct student ST;
main()
{ST st}
이렇게 짧게 선언과 동시에 사용할 수 있다
바이트 순서
4바이트중 1바이트만 쓰는 char는 3바이트를 버리게 되므로 데이터 정렬에 의해 뒤 내용이 땡겨온다
리틀엔디안방식
volatile : 레지스터 변수로 쓰지 않음
cpu는 빠르고 메모리는 느리다
레지스터는 cpu와 속도가 동일하다
스택에 변수가 있으면 읽어오는 데 너무 많은 클럭이 소모되고 느리다
그래서 이런 것은 레지스터 변수를 잡는다-> 컴파일러 최적화
물리 메모리에서 정확한 것을 가져와야 할 때 이것이 방해가 된다
펌웨어에서는 메인루프와 인터럽트가 변수를 공유할 때 사용한다
커널은 이것이 필요가 없다
GFP_KERNEL
동적메모리를 할당할 수 없으면 잠들음
GFP_ATOMIC
커널에 할당 가능한 메모리가 있으면 무조건 할당하고 없으면 NULL 반환
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#define CALL_DEV_NAME "calldev"
#define CALL_DEV_MAJOR 230
static int call_open(struct inode *inode, struct file *filp)
{
int num = MINOR(inode->i_rdev);
printk("call open-> minor : %d\n", num);
num = MAJOR(inode->i_rdev);
printk("call open-> major : %d\n", num);
return 0;
}
static loff_t call_llseek(struct file *filp, loff_t off, int whence)
{
printk("call llseek -> off : %08X, whence : %08X\n", (unsigned int)off, whence);
return 0x23;
}
static ssize_t call_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
printk("call read -> buf : %08X, count : %08X \n", (unsigned int)buf, count);
return 0x33;
}
static ssize_t call_write(struct file *filp, const char *buf, size_t count, loff_t *f_ops)
{
printk("call write -> buf : %08X, count : %08X \n", (unsigned int)buf, count);
return 0x43;
}
static long call_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
printk("call ioctl -> cmd : %08X, arg : %08X\n", cmd, (unsigned int)arg);
return 0x53;
}
static int call_release(struct inode *inode, struct file *filp)
{
printk("call release \n");
return 0;
}
struct file_operations call_fops =
{
.owner = THIS_MODULE,
.llseek = call_llseek,
.read = call_read,
.write = call_write,
.unlocked_ioctl = call_ioctl,
.open = call_open,
.release = call_release,
};
static int call_init(void)
{
int result;
printk("call call_init \n");
result = register_chrdev(CALL_DEV_MAJOR, CALL_DEV_NAME, &call_fops);
if(result < 0) return result;
return 0;
}
static void call_exit(void)
{
printk("call call_exit \n");
unregister_chrdev(CALL_DEV_MAJOR, CALL_DEV_NAME);
}
module_init(call_init);
module_exit(call_exit);
MODULE_LICENSE("Dual BSD/GPL");
call_dev.c 파일
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#define DEVICE_FILENAME "/dev/calldev"
int main(void)
{
int dev;
char buff[128];
int ret;
printf("1) device file open\n");
dev = open(DEVICE_FILENAME, O_RDWR | O_NDELAY);
if(dev >= 0)
{
printf("2) seek function call dev:%d\n", dev);
ret = lseek(dev, 0x20, SEEK_SET);
printf("ret = %08X\n", ret);
printf("3) read function call\n");
ret = read(dev, (char *)0x30, 0x31);
printf("ret = %08X\n", ret);
printf("4) write function call\n");
ret = write(dev, (char *)0x40, 0x41);
printf("ret = %08X\n", ret);
printf("5) ioctl function call\n");
ret = ioctl(dev, 0x51, 0x52);
printf("ret = %08X\n", ret);
printf("6) device file close\n");
ret = close(dev);
printf("ret = %08X\n", ret);
}
else
perror("open");
return 0;
}
call_app.c 파일
make를 하고
sudo cat /proc/devices로 사용중인 것을 확인 후 아닌 것으로 설정한다
우리는 230으로 할 것이다
pi로 와서(반드시 순서 지킬 것)
1. 디바이스 드라이버 파일을 만든다
여기 설정한 이름으로 지어야함
/mnt/ubuntu_nfs$ sudo mknod /dev/calldev c 230 32
register_chrdev가 등록시키는 함수이다
이것을 하지 않으면 no such file or directory 라는 말이 나온다
/mnt/ubuntu_nfs$ sudo insmod call_dev.ko
이것을 하지 않으면 no such device or address 라는 말이 나온다
다른 창에서
dmesg
/mnt/ubuntu_nfs$ sudo ./call_app
다른 창에서 dmesg
실행 결과는 위와 같다
파일 비교 프로그램
sudo apt install meld
meld
GUI가 있기 때문에 우분투 터미널에서 할 것(putty x)
#include <stdio.h>
struct student {
int num;
char * name;
};
struct student test = {
.num = 1,
.name = "test"
};
int main()
{
printf("num : %d, name : %s\n",test.num, test.name);
struct student st = {1, "ksh"};
printf("num : %d, name : %s\n",st.num, st.name);
struct student sa[2] = {{1, "ksh"},{2,"aaa"}};
printf("num : %d, name : %s\n",sa[0].num, sa[0].name);
printf("num : %d, name : %s\n",sa[1].num, sa[1].name);
struct student * pSt;
pSt = &st;
printf("num : %d, name : %s\n",pSt->num, pSt->name);
}
8바이트 구조체를 정의했다
문자열 상수는 데이터 RO에 저장된다
여기서는 test에서
구조체를 선언함과 동시에 변수를 선언하고 초기화한다
.num이라 하면 멤버 변수를 말한다
가독성을 높이기 위해 사용했다
변수의 속성이 변하지만 디바이스 드라이버에서는 상관 없다
구조를 자세히 알고 싶으면 함수 포인터 변수를 공부할 것
pSt는 4바이트
여기에 st의 주소를 넣음
open을 하면 바로 여는 것이 아니고 커널이 정보를 가공해서 가져온 뒤에 연다
디바이스 드라이버.pdf 6장을 읽고 올 것