
응용 프로그램의 하드웨어 제어는 위와 같은 방식으로 제어된다
응용 프로그램이 하드웨어를 제어하기 위해 저수준 파일 입출력 함수를 사용해 디바이스 파일에 데이터를 쓰거나 읽고, 그 결과로 하드웨어를 제어하는 디바이스 드라이버 함수가 호출된다
커널이 어떻게 디바이스 파일과 디바이스 드라이버의 함수를 연결할까?
방법은 디바이스 파일에 기록된 디바이스 타입과 주 번호를 이용하여 등록한다
fs/char_dev.c에 chrdevs라는 전역 변수가
struct char_device_struct chrdevs[MAX_PROBE_HASH];와 같이 정의되어 있다
해당 전역 변수는 struct file_operation *fops;라는 필드를 포함한 문자 디바이스 드라이버를 관리하는 구조체로, 응용 프로그램이 디바이스 파일에 적용한 저수준 파일 입출력 함수에 대응하는 디바이스 드라이버의 함수 주소를 지정하는 필드를 포함시킨다
응용 프로그램에서 open() 함수로 디바이스 파일을 열어 타입 정보와 주 번호를 얻고, 해당 정보를 이용해 chrdevs 배열에 등록된 디바이스 드라이버의 인덱스를 얻은 다음, 인덱스값으로 chrdevs 변수에 등록된 file_operations 구조체 주소를 얻는다
해당 구조체에는 문자 디바이스 드라이버가 문자 디바이스 드라이버를 등록하는 함수를 사용하여 저수준 파일 입출력에 대응하는 함수를 설정한 내용을 담고 있다
struct file_operations
open() : xxx_open()
fd = open(const char *pathname, int flags);
int xxx_open(struct inode *inode, struct file *filp)
{
···
return ret;
}
xxx_open() 함수에 오류가 발생하면 반환값이 fd에 전달되고 정상적으로 수행된 경우에는 0을 반환
close() : xxx_release()
ret = close(int fd);
int xxx_release(struct inode *inode, struct file *filp)
{
···
return ret;
}
read() : xxx_read()
ret = read(int fd, void *buf, size_t count);
ssize_t xxx_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
···
return ret;
}
read() 함수의 buf는 디바이스 드라이버에서 데이터를 읽어오기 위한 버퍼의 주소 → xxx_read()의 buf에 전달됨
전달된 버퍼의 주소값은 프로세스의 메모리 공간을 지정하는 주소값이기 때문에 디바이스 드라이버에서 직접 사용 불가능
count는 read()에서 읽어오려는 데이터의 개수
write() : xxx_write()
ret = write(int fd, const void *buf, size_t count);
ssize_t xxx_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
···
return ret;
}
write() 함수 buf는 디바이스 드라이버에 데이터를 쓰기 위한 버퍼 주소 → xxx_write()의 buf에 전달됨
count는 하드웨어에 쓸 데이터 수
lseek() : xxx_llseek()
ret = lseek(int fd, off_t offset, int whence);
loff_t xxx_llseek(struct file *filp, loff_t off, int whence)
{
···
return ret;
}
offset과 whence 지정된 값으로 파일 포인터를 옮기고 해당 값은 off와 whence에 전달
ioctl() : xxx_ioctl()
ret = ioctl(int fd, int request, ···)
int xxx_ioctl(struct inode *incode, struct file *filp, unsigned int cmd, unsigned long arg)
{
···
return ret;
}
가변 매개변수형 함수로 매개변수를 2, 3개 지정할 수 있음
request 값은 cmd로 전달됨
매개변수가 두개라면 arg 값은 어떤 값이 될지 모르지만 세개일 경우에는 arg 값은 세번째 값이 됨
file_operations 등록과 제거
file_operations 구조체를 커널에 등록하거나 제거하기 위해 사용하는 함수 2가지
// 디바이스 드라이버 등록
#include <linux/fs.h>
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops)
주 번호, 디바이스 드라이버명, file_operations 구조체 변수가 선언되어 있다면 해당 함수를 사용해서 디바이스 드라이버를 등록할 수 있다
// 디바이스 드라이버 제거
#include <linux/fs.h>
int unregister_chrdev(unsigned int major, const char *name)

위 사진은 문자 디바이스 드라이버가 커널에 등록되고 응용 프로그램에서 디바이스 파일을 통해 디바이스 드라이버를 제어한 뒤 다시 커널에서 제거되는 과정을 도식화 한 것이다
insmod로 디바이스 드라이버 모듈을 커널에 적재하면 디바이스 드라이버는 module_init을 통해 register_chrdev 함수를 호출한다
이때 chrdev에는 주 번호와 모듈명, file_operations이 매개변수로 할당되어 모듈 적재를 돕는다
이후 응용 프로그램에서 open 함수가 실행되면 디바이스 파일에서 주 번호와 부 번호를 얻고 그렇게 얻은 주 번호를 이용해 fops에 등록된 함수들을 사용한다
모듈을 커널에서 제거하기 위해서는 rmmod 함수를 사용해서 unregister_chrdev를 호출하면 된다
이제 간단한 예시를 보자
// call_dev.c
#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);
try_module_get(THIS_MODULE);
return 0;
}
static loff_t call_llseek(struct file *filep, 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_pos)
{
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 0x55;
}
static int call_release(struct inode *inode, struct file *filp)
{
printk("call release\n");
module_put(THIS_MODULE);
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_app.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;
}
// Makefile
APP := call_app
MOD := call_dev
SRC := $(APP).c
obj-m := $(MOD).o
CROSS = ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
CC := arm-linux-gnueabihf-gcc
KDIR := /home/ubuntu/pi_bsp/kernel/linux
PWD := $(shell pwd)
default:$(APP)
$(MAKE) -C $(KDIR) M=$(PWD) modules $(CROSS)
cp $(MOD).ko /srv/nfs
$(APP):
$(CC) $(APP).c -o $(APP)
cp $(APP) /srv/nfs
clean:
rm -rf *.ko
rm -rf *.mod.*
rm -rf .*.cmd
rm -rf *.o
rm -rf modules.order
rm -rf Module.symvers
rm -rf $(MOD).mod
rm -rf .tmp_versions
rm -rf $(APP)
$ sudo insmod call_dev.ko
를 실행하면 call_dev.c의 module_init() 안의 call_init()이 호출되며 register_chrdev() 함수를 통해 문자 디바이스 드라이버를 커널에 주 번호 230, calldev라는 디바이스 드라이버명으로 등록하게 된다
이제 call_app.c의 open()함수에서 등록된 /dev/calldev 디바이스 파일을 열고 read, write 등의 함수를 실행하게 된다
다 실행됐으면
$ sudo rmmod call_dev
를 통해 적재한 모듈을 제거하면 된다

실행한 모습은 위와 같다
'임베디드 > Linux BSP' 카테고리의 다른 글
| 모듈 프로그램 (0) | 2024.03.11 |
|---|---|
| 리눅스에서 한글 글자가 깨질 때 (0) | 2024.03.11 |
| 디바이스 파일 (0) | 2024.03.06 |
| 디바이스 드라이버 (1) | 2024.03.05 |
| NFS 파일 공유 시스템 (1) | 2024.02.06 |