드라이버 작성시 file operation 에 사용되는 자료형의 형태

디바이스 드라이버를 작성하다 보면 주면 read, write, ioctl 을 통해 유저 어플리케이션과 
데이타를 주고 받게 된다.
이 때 데이타에 대한 유효성과 커널공간으로 사용자 영역으로부터의 데이타를 가져오는
작업을 수행하게 되는데 이때 주의 해야할 점과 회피하는 방법이다.



물론 내가 드라이버를 짜고 어플리케이션을 짜면 어떤 자료형을 넘기는지 알기 때문에
여러가지를 고려하지 않고 작성할수도 있다.
하지만....
한달도 지나지 않아 그런 코드를 기억하는 것은 사람으로서 불가능하다.

이제 대표적인 형태의 file operation 과 그 자료형을 검사하는 방법을 보자.

static int fn_open(struct inode *inode, struct file *file)
{
return 0;
}

static int fn_release(struct inode *inode, struct file *file)
{
return 0;
}

static struct file_operations fn_fops = {
.ioctl = fn_ioctl,
.open = fn_open,
.release = fn_release,
};

#define IOCTL_MAGIC_FN 'F'

#define FN_IOCTL_GET _IOW(IOCTL_MAGIC_FN, 0, unsigned int)
#define FN_IOCTL_PUT _IOR(IOCTL_MAGIC_FN, 1, unsigned int)

#define ALARM_IOCTL_MAXNR 2

static int extio_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int param_size;
unsigned int value;

if(_IOC_TYPE(cmd) != IOCTL_FN_ALARM) return -EINVAL;
if(_IOC_NR(cmd) >= ALARM_FN_MAXNR) return -EINVAL;

param_size = _IOC_SIZE(cmd);
if(param_size) {
if(_IOC_DIR(cmd) & _IOC_READ) {
if (unlikely(!access_ok(VERIFY_WRITE, (void *)arg, param_size)))
return -EFAULT;
}
if(_IOC_DIR(cmd) & _IOC_WRITE) {
if (unlikely(!access_ok(VERIFY_READ, (void *)arg, param_size)))
return -EFAULT;
}
}

switch (cmd)
{
caseFN_IOCTL_GET:
{
copy_to_user((unsigned int *)arg, &value, sizeof(unsigned int));
return 0;
}
caseFN_IOCTL_PUT:
{
copy_from_user(&value, (unsigned int *)arg, sizeof(unsigned int));
return 0;
}
}

return -ENOTTY;
}


이제 하나 하나 뜯어 보자.
#define IOCTL_MAGIC_FN 'F'

#define FN_IOCTL_PUT _IOW(IOCTL_MAGIC_ALARM, 0, unsigned int)
#define FN_IOCTL_GET _IOR(IOCTL_MAGIC_ALARM, 1, unsigned int)

#define ALARM_IOCTL_MAXNR 2


_IOW, _IOR 매크로는 주어진 인자를 가지고 32비트의 데이타를 만들어 내는데 
magic, read or write, 명령의 번호를 가지고 데이타를 만들어낸다.
수많은 드라이버와 겹치지 않는다고 보장할수는 없지만 
꽤 쓸만한 복잡한 수를 만들어낸다.

if(_IOC_TYPE(cmd) != IOCTL_FN_ALARM) return -EINVAL;
--- ioctl 을 통한 요청이 IOCTL_MAGIC_FN 을 사용해서 만들어 낸 것인지 검사한다.
  잘못된 파일 오픈에 의한 오퍼레이션을 막기 위함이다.
if(_IOC_NR(cmd) >= ALARM_FN_MAXNR) return -EINVAL;
    --- 최대 명령어의 갯수를 넘어선 요구가 들어오는지 확인한다.
       이것은 현재의 버전에서는 지원하지 않는 더 많은 명령어에 대한 어플리케이션
       요청이 있을수 있기 때문에 잘못된 연산을 막기 위함이다.  

param_size = _IOC_SIZE(cmd);
if(param_size) {
}
--- 요청된 연산에 사용되는 자료의 크기를 검사한다.

if(_IOC_DIR(cmd) & _IOC_READ) {
if (unlikely(!access_ok(VERIFY_WRITE, (void *)arg, param_size)))
return -EFAULT;
}
--- 요청된 연산이 READ 명령이면 사옹자 영역의 데이타 사이즈만큼 쓰기가 가능한지 확인한다.

if(_IOC_DIR(cmd) & _IOC_WRITE) {
if (unlikely(!access_ok(VERIFY_READ, (void *)arg, param_size)))
return -EFAULT;
}
--- 요청된 연산이 WRITE 명령이면 사옹자 영역의 데이타 사이즈만큼 일기가 가능한지 확인한다.

동일하게 read 함수와 write 함수에도 적용할수 있다.
단지  cmd 에대한 검사는 없이 
access_ok 매크로를 이용해서 검사를 할수 있다.

처음 드라이버를 작성할 당시에는 아무런 문제가 없이 동작하지만 
이후 버전 없과 유지보수시에 여러가지 버전에서 자료의 호환성으로 이유를 찾기 힘든
이상한 동작을 마주하게 된다.
그런한 것을 방지하기 위해서는 위와 같은 형태의 자료형을 반드시 사용하고
어플리케이션에서는 에러발생시 assert 문을 사용함으로써 동작시에 발생하는 버그를
추적하는데 어려움을 겪지 않도록 예방할수 있다.