

디바이스 드라이버 소스파일 내의 init(), exit(), open(), release() 함수에 대해서 말하는 것이다.
먼저 접근 가능한 메모리인지 확인을 해보고 접근을 해야한다. 안그러면 segmentation fault가 나버린다. 가장 fatal한 상황이다. 그래서 커널에서 pointer를 사용할 때는 정말 신중하게 사용해야 한다.
#include <linux/ioport.h>하면 아래 함수를 통해서 체크해볼 수 있다.

이거 말고도 다양하게 더 있다.
우분투 가장머신에서 아래 명령어를 쳐보자.

라즈베리파이 커널에서 아래 명령어를 쳐보자.

정말 중요한 개념도이다. 여기서도 핵심이 되는 것은 put_user(), copy_to_user(), get_user(), copy_from_user() 이 4개의 함수이다.

사용자 어플리케이션은 그 프로세스 주소 공간에서만 제어 가능하고 커널 영역의 메모리 공간에 접근할 수 없다. 마찬가지로 커널에서도 함부로 사용자 어플리케이션 주소공간에 맘대로 접근하면 안된다.
즉 서로 자기 영역의 메모리 공간이 아니기 때문에, 그 메모리 공간이 가르키는 정보에 직접 접근하여 읽거나 쓰지 않고, 중간에 버퍼를 두고 서로의 주소 공간을 복사하여 사용하는 식으로 운영하게 되는 것이다.
그래서 필요한 함수들이 put_user(), copy_to_user(), get_user(), copy_from_user()이다. 꼭 이 4개의 함수를 가지고 어플리케이션과 디바이스드라이버가 서로 읽기/쓰기가 이루어져야 한다.
교제 페이지 232쪽부터 읽어보면 왜 그런지 그 이유를 알 수 있다.
우리는 우선 open(), close(), read(), write()만 가지고 해보겠다.
/dev/ledkey c 230 0이라고 만들면 되도록read(dev, buf, size)로 key값 읽기write(dev, buf, size)로 key값 쓰기#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>
#include <linux/gpio.h> // gpio
#define LEDKEY_DEV_NAME "ledkey_dev"
#define LEDKEY_DEV_MAJOR 230 // 디바이스 주번호
#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 ledkey_open(struct inode *inode, struct file *filp)
{
int num0 = MINOR(inode->i_rdev);
int num1 = MAJOR(inode->i_rdev);
printk("ledkey open-> minor : %d\n", num0);
printk("ledkey open-> major : %d\n", num1);
try_module_get(THIS_MODULE);
return 0;
}
static loff_t ledkey_llseek(struct file *filp, loff_t off, int whence)
{
printk("ledkey llseek -> off : %08X, whence : %08X\n", (unsigned int)off, whence);
return 0x23;
}
static ssize_t ledkey_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
printk("ledkey read -> buf : %08X, count : %08X \n", (unsigned int)buf, count);
char kval;
kval = gpioKeyGet();
printk("%d\n", kval);
put_user(kval, buf);
// result = copy_to_user(buf, &kval, count);
return count;
}
static ssize_t ledkey_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
// 밖에서 저수준 입출력 함수로 write()할때는 void* 형으로 buf를 전달했는데, 이 함수와 연결되면서 buf가 const char* 형으로 강제 형변환되었다.
// const char *buf라고 선언을 해둬야 여기 코드 안에서 실수로 *buf가 가리키는 공간을 바꿔버리는 것을 방지하기 위함이다.
// 즉, 함수의 파라미터를 구성할 때 원본데이터가 되는 src를 const로 선언하여 수정되는 것을 방지해주는 것은 일반적인 방법이다. 아래의 strcpy() 함수를 보면 제대로 알 수 있다.
/*
char * strcpy(char * dest,const char *src)
{
char *tmp = dest;
while ((*dest++ = *src++) != '\0')
;
return tmp;
}
*/
// 이러한 개념 원리가 이 코드에도 적용되어 있는 것이다.
// const로 선언 안하고 Readonly 영역에 write할려고 하면 그 순간 segmentation fault가 일어나면서 시스템이 죽어버린다. 너무나 fatal한 상황인 것이다. 하지만 코드상에 Readonly 영역 포인터를 const로 선언해놓고 거기에 무언가 값을 write하려고 하면 컴파일 순간에 컴파일 에러가 난다. 즉 프로그램이 돌면서 segmentaion fault가 나기 전에 컴파일 타임에 error를 발생시켜서 좀더 graceful하게 실수를 방지할 수 있다는 것이다.
printk("ledkey write -> buf : %08X, count : %08X \n", (unsigned int)buf, count);
#if 0
gpioLedSet(*buf); // 어플리케이션에서 할당한 값을 참조하여 불을 켬
// buf라는 포인터 변수는 커널 공간에 할당된 것이 맞다.
// 하지만 buf라는 포인터 변수 안에 담긴 주소 값은 사용자 공간이라는 것이 핵심이다.
// 근데 이렇게 직접접근 하지 말라고 했다. 반드시 값을 복사 받아서 사용하라고 했다.
// 물론 그냥 이렇게 해도 실행이 되긴 한다. 근데 이러면 안된다는 것이다. 그 이유는 교재232쪽을 보면 알수 있다.
#else
// 버퍼가 단 1바이트라면...
char kbuff;
get_user(kbuff, buf); // kbuff는 '일반 변수'이고, buf는 '포인터'이다.
// 만약 버퍼가 1바이트가 아니라 여러 바이트였다면...
/* char kbuff[10];
int i;
for(i=0;i<count;i++)
get_user(kbuff[i],buf++);
*/
/* char kbuff[10];
copy_from_user(kbuff,buf,count);
*/
/* int result;
result = copy_from_user(&kbuff, buf, count);
*/
gpioLedSet(kbuff); // gpioLedSet은 long을 받기를 기대하고 있는데 char을 넣어줬다. 이건 문제가 안된다.
#endif
return count; // 실제로 읽은 바이트 수를 리턴해야 한다.
}
static long ledkey_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
printk("ledkey ioctl -> cmd : %08X, arg : %08X\n", cmd, (unsigned int)arg);
return 0x53;
}
static int ledkey_release(struct inode *inode, struct file *filp)
{
printk("ledkey release \n");
module_put(THIS_MODULE);
return 0;
}
// 구조체다
struct file_operations ledkey_fops = {
//.owner = THIS_MODULE,
.llseek = ledkey_llseek,
.read = ledkey_read,
.write = ledkey_write,
.unlocked_ioctl = ledkey_ioctl,
.open = ledkey_open,
.release = ledkey_release, // 저수준 입출력함수에서 close()가 .release에 대응한다.
};
static int ledkey_init(void)
{
int result;
printk("ledkey ledkey_init \n");
result = gpioLedInit();
if(result < 0)
return result;
result = gpioKeyInit();
if(result < 0)
return result;
// 캐릭터 디바이스를 메모리에 적재시키는 명령
result = register_chrdev(LEDKEY_DEV_MAJOR, LEDKEY_DEV_NAME, &ledkey_fops); // ledkey_fops의 주소값을 등록
if (result < 0)
return result;
return 0;
}
static void ledkey_exit(void)
{
printk("ledkey ledkey_exit \n");
unregister_chrdev(LEDKEY_DEV_MAJOR, LEDKEY_DEV_NAME);
gpioLedFree();
gpioKeyFree();
}
module_init(ledkey_init);
module_exit(ledkey_exit);
MODULE_AUTHOR("KCCI");
MODULE_DESCRIPTION("Device Driver pracite~!");
MODULE_LICENSE("Dual BSD/GPL");
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#pragma GCC diagnostic ignored "-Wunused-result" // scanf()쓸 때 리턴값 안받는걸로 경고 띄위지 말라는 것
int main(int argc, char *argv[])
{
int i;
int ledkey_fd;
int key_data, key_data_old = 0;
unsigned long val = 0;
char buff;
// 예외처리1
if(argc < 2)
{
printf("USAGE : %s ledVal[0x00~0xff]\n", argv[0]);
return 1;
}
// 예외처리2
val = strtoul(argv[1], NULL,16);
if(val < 0 || 0xff < val)
{
printf("Usage : %s ledValue[0x00~0xff]\n", argv[0]);
return 2;
}
// 실제 프로그램 실행 후 예외처리3
ledkey_fd = open("/dev/ledkey", O_RDWR | O_NONBLOCK);
if(ledkey_fd < 0)
{
perror("open()");
return 3;
}
// 우선 LED 켜보기
buff = (char)val; // 버퍼에 val값을 담아서 led를 켜는 디바이스 드라이버 호출
write(ledkey_fd, &buff, sizeof(buff)); // 여기서 &buff는 void* 파라미터에 대응하는 것이다.
do
{
usleep(100000);
// 이 함수를 읽어오는거랑 쓰는걸로 찢어야 한다.
// key_data = syscall(__NR_mysyscall, val);
read(ledkey_fd, &buff, sizeof(buff));
key_data = buff;
if(key_data != key_data_old)
{
key_data_old = key_data;
if(key_data)
{
write(ledkey_fd, &buff, sizeof(buff)); // 여기서 &buff는 void* 파라미터에 대응하는 것이다.
puts("0:1:2:3:4:5:6:7");
for(i=0;i<8;i++)
{
if(key_data & (0x01 << i))
putchar('O');
else
putchar('X');
if(i != 7 )
putchar(':');
else
putchar('\n');
}
putchar('\n');
}
if(key_data == 0x80)
break;
}
} while(1);
printf("mysyscall return value = %#04x\n",key_data);
return 0;
}

지금까지의 어플리케이션 프로그램은 안에서 do~while()문이 매우 빠른 속도로 무한루프 돌면서 폴링하면서 하다보니까 $top으로 CPU 점유율을 찍어보면, 우리의 프로그램이 하나의 코어를 거의 100% 점유하고 있는 상황을 볼 수 있었다.
그래서 blocking I/O를 통해서 프로세스를 sleep모드로 바꿔버릴 것이다.
그리고 HW interrupt가 들어왔을 때만 sleep모드에서 탈출하도록 하는 것이 올바른 구현 방향이다.