ioctl
ioctl() 함수란 하드웨어의 제어와 상태 정보를 얻기 위해 제공되는 함수이다. read(), write()를 이용해서 데이터를 읽고 쓰는 등의 기능은 가능하지만 하드웨어를 제어하거나 상태 정보를 확인하려면 ioctl()를 이용해야 한다.
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
첫 번째 인자는 open 한 디바이스 드라이버의 fd 값이다
두 번째 인자는 디바이스에게 전달할 명령이다. 이 명령에 따라서 디바이스를 컨트롤할 수 있다.
에러시 -1을 반환, 다른 시스템 호출과 같이 성공하면 0보다 크거나 같은 값 리턴
read(), write()
https://rorsi.tistory.com/65 open(), close() https://rorsi.tistory.com/61 위 포스트에 이어서 디바이스 드라이버를 만들었다면 이제 그 드라이버를 이용해 open(), close()를 구현해 보고자 한다. driver.c #include #include
rorsi.tistory.com
이전 read, write에 이어서 ioctl() 함수를 구현해 보고자 한다.
driver.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/unistd.h>
#include <linux/cdev.h>
#define DEVICE_NAME "driver"
#define CALL_PRINT 1
static dev_t device_dev;
static struct class *device_class;
static struct cdev device_cdev;
static char *buffer = NULL;
int device_open(struct inode *inode, struct file *filp);
int device_release(struct inode *inode, struct file *filp);
ssize_t device_read(struct file *filp, char *buf, size_t count, loff_t *f_ops);
ssize_t device_write(struct file *filp, const char *buf, size_t count, loff_t *f_ops);
long device_ioctl(struct file *filp, unsigned int cmd, unsigned long data);
static struct file_operations fops = {
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write,
.unlocked_ioctl = device_ioctl
};
int device_open(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "device open\n");
return 0;
}
int device_release(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "device close\n");
return 0;
}
ssize_t device_read(struct file *filp, char *buf, size_t count, loff_t *f_ops)
{
int msg;
if(buffer == NULL)
return -1;
msg = copy_to_user(buf, buffer, count);
printk("[%s] count = %ld, msg = %u\n", __func__, count, msg);
return count - msg;
}
ssize_t device_write(struct file *filp, const char *buf, size_t count, loff_t *f_ops)
{
int msg;
if(buffer != NULL)
kfree(buffer);
if((buffer = kmalloc(count + 1, GFP_KERNEL)) == NULL)
return -ENOMEM;
msg = copy_from_user(buffer, buf, count);
printk("[%s] count = %ld, msg = %u\n", __func__, count, msg);
return count - msg;
}
long device_ioctl(struct file *filp, unsigned int cmd, unsigned long data)
{
switch (cmd) {
case CALL_PRINT:
printk(KERN_INFO "[%s] CALL PRINT!", __func__);
break;
default:
break;
}
return 0;
}
int __init device_init(void)
{
if(alloc_chrdev_region(&device_dev, 0, 1, DEVICE_NAME))
{
printk(KERN_ALERT "[&s] alloc_chrdev_region failed\n", __func__);
return -1;
}
cdev_init(&device_cdev, &fops);
if(cdev_add(&device_cdev, device_dev, 1))
{
printk(KERN_ALERT "[%s] cdev_add failed\n", __func__);
unregister_chrdev_region(device_dev, 1);
}
if((device_class = class_create(THIS_MODULE, DEVICE_NAME)) == NULL)
{
printk(KERN_ALERT "[%s] class_add failed\n", __func__);
unregister_chrdev_region(device_dev, 1);
}
if(device_create(device_class, NULL, device_dev, NULL, DEVICE_NAME) == NULL)
{
printk(KERN_ALERT "[%s] device_create failed\n", __func__);
class_destroy(device_class);
}
printk(KERN_INFO "[%s] successfully created device: Major = %d, Minor = %d\n",
__func__, MAJOR(device_dev), MINOR(device_dev));
return 0;
}
void __exit device_exit(void)
{
device_destroy(device_class, device_dev);
class_destroy(device_class);
cdev_del(&device_cdev);
unregister_chrdev_region(device_dev, 1);
printk("KERN_INFO [%s] successfully unregistered.\n", __func__);
}
module_init(device_init);
module_exit(device_exit);
MODULE_AUTHOR("Test");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("character device driver");
위에 설명한 거와 같이 ioctl() 함수와 연결할 수 있게 file operation에 해당 코드를 적어준다.
static struct file_operations fops = {
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write,
.unlocked_ioctl = device_ioctl
};
그런데 ioctl이라는 함수는 어디로 가고 왜 unlocked_ioctl, compat_ioctl일까? 우선 unlocked_ioctl부터 보자. 왜 unlocked라는 말이 붙었을까? 옛날 (2.6 이전)에는 ioctl을 호출할 때, BKL을 사용했다. BKL은, 커널 전체에 걸리는 락으로, 동시에 여러 개의 프로세스가 커널 모드에 진입하지 못하도록 막는 락이다. 듣기만 해도 엄청나게 비효율적이란 걸 알 수 있다. 그래서 2.6.39를 기준으로 완전히 제거되었다. 이제는 ioctl을 호출할 때 BKL이 아니라, 디바이스 드라이버가 스스로 락을 관리해야 한다. 따라서 이름이 unlocked_ioctl로 변경되었다. compat_ioctl은 32비트와의 호환성을 위한 함수이다.
ioctl.c
#include <stdio.h> // printf()
#include <unistd.h> // close(), read(), write()
#include <sys/fcntl.h> // open()
#include <sys/ioctl.h> // ioctl()
#define CALL_PRINT 1
int main(void) {
int fd;
char buf[1000];
int read_ret, write_ret;
fd = open("/dev/driver", O_RDWR);
if (fd < 0)
{
printf("failed opening device\n");
return 0;
}
else
{
printf("succeess opening device\n");
}
write_ret = write(fd, "hello", 5);
read_ret = read(fd, buf, 5);
printf("fd = %d, ret write = %d, ret read = %d\n", fd, write_ret, read_ret);
printf("content = %s\n", buf);
if(ioctl(fd, CALL_PRINT, NULL))
{
printf("failed ioctl\n");
}
else
{
printf("success ioctl\n");
}
close(fd);
}
디바이스 드라이버를 등록 한 후 해당 코드를 작성한 뒤 실행시켜준다.
정상적으로 ioctl()이 작동한 것을 확인할 수 있다.
이제 dmesg 명령어를 통해 커널 메시지를 확인해보면 정상적으로 ioctl을 통해 출력된 것을 볼 수 있다.
'Linux > Device Driver' 카테고리의 다른 글
read(), write() (0) | 2022.11.25 |
---|---|
open(), close() (0) | 2022.11.25 |
디바이스 드라이버 (0) | 2022.11.20 |