Excuse the ads! We need some help to keep our site up.
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); int (*iterate_shared) (struct file *, struct dir_context *); __poll_t (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); unsigned long mmap_supported_flags; int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*setfl)(struct file *, unsigned long); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int); int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t, u64); ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *, u64); } __randomize_layout; |
작성한 함수의 명은 "struct file_operations" 형태로 선언된 구조체의 ".open" 필드에 함수의 주소(chardev_open)를 저장합니다.
static int chardev_open(struct inode *inode, struct file *file) { printk("chardev_open"); return 0; } struct file_operations chardev_fops = { .open = chardev_open, }; |
lazenca0x0@ubuntu:~$ uname -a Linux ubuntu 4.18.0-11-generic #12-Ubuntu SMP Tue Oct 23 19:22:37 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux lazenca0x0@ubuntu:~$ gcc --version gcc (Ubuntu 8.2.0-7ubuntu1) 8.2.0 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. lazenca0x0@ubuntu:~$ |
User space에서 디바이스가 open할때 chardev_open()함수가 호출됩니다.
#include <linux/init.h> #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/sched.h> #include <linux/device.h> #include <linux/slab.h> #include <asm/current.h> #include <linux/uaccess.h> #define DEVICE_NAME "chardev" #define DEVICE_FILE_NAME "chardev" #define MAJOR_NUM 100 static int chardev_open(struct inode *inode, struct file *file) { printk("chardev_open"); return 0; } struct file_operations chardev_fops = { .open = chardev_open, }; static int chardev_init(void) { int ret_val; ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &chardev_fops); if (ret_val < 0) { printk(KERN_ALERT "%s failed with %d\n", "Sorry, registering the character device ", ret_val); return ret_val; } printk(KERN_INFO "%s The major device number is %d.\n", "Registeration is a success", MAJOR_NUM); printk(KERN_INFO "If you want to talk to the device driver,\n"); printk(KERN_INFO "you'll have to create a device file. \n"); printk(KERN_INFO "We suggest you use:\n"); printk(KERN_INFO "mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM); printk(KERN_INFO "The device file name is important, because\n"); printk(KERN_INFO "the ioctl program assumes that's the\n"); printk(KERN_INFO "file you'll use.\n"); return 0; } static void chardev_exit(void) { unregister_chrdev(MAJOR_NUM, DEVICE_NAME); } module_init(chardev_init); module_exit(chardev_exit); |
obj-m := chardev.o all: make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean |
lazenca0x0@ubuntu:~/Kernel/Module/chardev$ make make -C /lib/modules/4.18.0-11-generic/build M=/home/lazenca0x0/Kernel/Module/chardev modules make[1]: Entering directory '/usr/src/linux-headers-4.18.0-11-generic' Makefile:982: "Cannot use CONFIG_STACK_VALIDATION=y, please install libelf-dev, libelf-devel or elfutils-libelf-devel" CC [M] /home/lazenca0x0/Kernel/Module/chardev/chardev.o Building modules, stage 2. MODPOST 1 modules WARNING: modpost: missing MODULE_LICENSE() in /home/lazenca0x0/Kernel/Module/chardev/chardev.o see include/linux/module.h for more information CC /home/lazenca0x0/Kernel/Module/chardev/chardev.mod.o LD [M] /home/lazenca0x0/Kernel/Module/chardev/chardev.ko make[1]: Leaving directory '/usr/src/linux-headers-4.18.0-11-generic' |
lazenca0x0@ubuntu:~/Kernel/Module/chardev$ sudo insmod chardev.ko lazenca0x0@ubuntu:~/Kernel/Module/chardev$ dmesg |tail [14735.412269] chardev_open [14886.237136] chardev_open [15136.839556] Registeration is a success The major device number is 100. [15136.839557] If you want to talk to the device driver, [15136.839558] you'll have to create a device file. [15136.839582] We suggest you use: [15136.839583] mknod chardev c 100 0 [15136.839583] The device file name is important, because [15136.839584] the ioctl program assumes that's the [15136.839584] file you'll use. lazenca0x0@ubuntu:~/Kernel/Module/chardev$ sudo mknod chardev c 100 0 lazenca0x0@ubuntu:~/Kernel/Module/chardev$ sudo chmod 666 chardev lazenca0x0@ubuntu:~/Kernel/Module/chardev$ echo 'A' > chardev -bash: echo: write error: Invalid argument lazenca0x0@ubuntu:~/Kernel/Module/chardev$ dmesg |tail [14886.237136] chardev_open [15136.839556] Registeration is a success The major device number is 100. [15136.839557] If you want to talk to the device driver, [15136.839558] you'll have to create a device file. [15136.839582] We suggest you use: [15136.839583] mknod chardev c 100 0 [15136.839583] The device file name is important, because [15136.839584] the ioctl program assumes that's the [15136.839584] file you'll use. [15231.493826] chardev_open lazenca0x0@ubuntu:~/Kernel/Module/chardev$ |
|
|
해당 모듈이 커널에 등록될 때 chardev_init() 함수에서 다음 기능을 처리합니다.
class_destroy() 함수를 이용하여 class_create() 함수에 의해 생성된 디바이스 클래스를 소멸시킵니다.
unregister_chrdev_region() 함수를 이용하여 alloc_chrdev_region() 함수에 의해 등록된 장치 번호를 반환합니다.
copy_from_user() 함수를 이용하여 사용자 공간으로 부터 전달 받은 데이터(buf)를 "p→buffer" 변수에 복사합니다.
#include <linux/init.h> #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/sched.h> #include <linux/device.h> #include <linux/slab.h> #include <asm/current.h> #include <linux/uaccess.h> MODULE_LICENSE("Dual BSD/GPL"); #define DRIVER_NAME "chardev" #define BUFFER_SIZE 256 static const unsigned int MINOR_BASE = 0; static const unsigned int MINOR_NUM = 2; static unsigned int chardev_major; static struct cdev chardev_cdev; static struct class *chardev_class = NULL; static int chardev_open(struct inode *, struct file *); static int chardev_release(struct inode *, struct file *); static ssize_t chardev_read(struct file *, char *, size_t, loff_t *); static ssize_t chardev_write(struct file *, const char *, size_t, loff_t *); struct file_operations chardev_fops = { .open = chardev_open, .release = chardev_release, .read = chardev_read, .write = chardev_write, }; struct data { unsigned char buffer[BUFFER_SIZE]; }; static int chardev_init(void) { int alloc_ret = 0; int cdev_err = 0; int minor; dev_t dev; printk("The chardev_init() function has been called."); alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME); if (alloc_ret != 0) { printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret); return -1; } //Get the major number value in dev. chardev_major = MAJOR(dev); dev = MKDEV(chardev_major, MINOR_BASE); //initialize a cdev structure cdev_init(&chardev_cdev, &chardev_fops); chardev_cdev.owner = THIS_MODULE; //add a char device to the system cdev_err = cdev_add(&chardev_cdev, dev, MINOR_NUM); if (cdev_err != 0) { printk(KERN_ERR "cdev_add = %d\n", alloc_ret); unregister_chrdev_region(dev, MINOR_NUM); return -1; } chardev_class = class_create(THIS_MODULE, "chardev"); if (IS_ERR(chardev_class)) { printk(KERN_ERR "class_create\n"); cdev_del(&chardev_cdev); unregister_chrdev_region(dev, MINOR_NUM); return -1; } for (minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) { device_create(chardev_class, NULL, MKDEV(chardev_major, minor), NULL, "chardev%d", minor); } return 0; } static void chardev_exit(void) { int minor; dev_t dev = MKDEV(chardev_major, MINOR_BASE); printk("The chardev_exit() function has been called."); for (minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) { device_destroy(chardev_class, MKDEV(chardev_major, minor)); } class_destroy(chardev_class); cdev_del(&chardev_cdev); unregister_chrdev_region(dev, MINOR_NUM); } static int chardev_open(struct inode *inode, struct file *file) { char *str = "helloworld"; int ret; struct data *p = kmalloc(sizeof(struct data), GFP_KERNEL); printk("The chardev_open() function has been called."); if (p == NULL) { printk(KERN_ERR "kmalloc - Null"); return -ENOMEM; } ret = strlcpy(p->buffer, str, sizeof(p->buffer)); if(ret > strlen(str)){ printk(KERN_ERR "strlcpy - too long (%d)",ret); } file->private_data = p; return 0; } static int chardev_release(struct inode *inode, struct file *file) { printk("The chardev_release() function has been called."); if (file->private_data) { kfree(file->private_data); file->private_data = NULL; } return 0; } static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct data *p = filp->private_data; printk("The chardev_write() function has been called."); printk("Before calling the copy_from_user() function : %p, %s",p->buffer,p->buffer); if (copy_from_user(p->buffer, buf, count) != 0) { return -EFAULT; } printk("After calling the copy_from_user() function : %p, %s",p->buffer,p->buffer); return count; } static ssize_t chardev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct data *p = filp->private_data; printk("The chardev_read() function has been called."); if(count > BUFFER_SIZE){ count = BUFFER_SIZE; } if (copy_to_user(buf, p->buffer, count) != 0) { return -EFAULT; } return count; } module_init(chardev_init); module_exit(chardev_exit); |
obj-m := chardev.o all: make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean |
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #define TEXT_LEN 12 int main() { static char buff[256]; int fd; if ((fd = open("/dev/chardev0", O_RDWR)) < 0){ printf("Cannot open /dev/chardev0. Try again later.\n"); } if (write(fd, "lazenca0x0", TEXT_LEN) < 0){ printf("Cannot write there.\n"); } if (read(fd, buff, TEXT_LEN) < 0){ printf("An error occurred in the read.\n"); }else{ printf("%s\n", buff); } if (close(fd) != 0){ printf("Cannot close.\n"); } return 0; } |
lazenca0x0@ubuntu:~/Kernel/Module/WR$ make make -C /lib/modules/4.18.0-11-generic/build M=/home/lazenca0x0/Kernel/Module/WR modules make[1]: Entering directory '/usr/src/linux-headers-4.18.0-11-generic' Makefile:982: "Cannot use CONFIG_STACK_VALIDATION=y, please install libelf-dev, libelf-devel or elfutils-libelf-devel" CC [M] /home/lazenca0x0/Kernel/Module/WR/chardev.o Building modules, stage 2. MODPOST 1 modules CC /home/lazenca0x0/Kernel/Module/WR/chardev.mod.o LD [M] /home/lazenca0x0/Kernel/Module/WR/chardev.ko make[1]: Leaving directory '/usr/src/linux-headers-4.18.0-11-generic' lazenca0x0@ubuntu:~/Kernel/Module/WR$ |
lazenca0x0@ubuntu:~/Kernel/Module/WR$ echo 'KERNEL == "chardev[0-9]*",GROUP="root",MODE="0666"' >> /etc/udev/rules.d/80-chardev.rules lazenca0x0@ubuntu:~/Kernel/Module/WR$ sudo insmod chardev.ko [sudo] password for lazenca0x0: lazenca0x0@ubuntu:~/Kernel/Module/WR$ ls -al /dev/chardev* crw-rw-rw- 1 root root 240, 0 Nov 27 22:39 /dev/chardev0 crw-rw-rw- 1 root root 240, 1 Nov 27 22:39 /dev/chardev1 lazenca0x0@ubuntu:~/Kernel/Module/WR$ |
lazenca0x0@ubuntu:~/Kernel/Module/WR$ gcc -o test test.c lazenca0x0@ubuntu:~/Kernel/Module/WR$ ./test lazenca0x0 lazenca0x0@ubuntu:~/Kernel/Module/WR$ dmesg |tail [ 10.899284] random: 7 urandom warning(s) missed due to ratelimiting [ 11.942688] [drm:vmw_stdu_crtc_page_flip [vmwgfx]] *ERROR* Page flip error -16. [ 130.546522] chardev: loading out-of-tree module taints kernel. [ 130.546570] chardev: module verification failed: signature and/or required key missing - tainting kernel [ 130.547051] The chardev_init() function has been called. [ 133.948317] The chardev_open() function has been called. [ 133.948320] The chardev_write() function has been called. [ 133.948322] Before calling the copy_from_user() function : 0000000012f53a81, helloworld [ 133.948323] After calling the copy_from_user() function : 0000000012f53a81, lazenca0x0 [ 133.948324] The chardev_read() function has been called. lazenca0x0@ubuntu:~/Kernel/Module/WR$ |
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> int main() { static char buff[256]; int fd0_A, fd0_B, fd1_A; if ((fd0_A = open("/dev/mydevice0", O_RDWR)) < 0) perror("open"); if ((fd0_B = open("/dev/mydevice0", O_RDWR)) < 0) perror("open"); if ((fd1_A = open("/dev/mydevice1", O_RDWR)) < 0) perror("open"); if (write(fd0_A, "0_A", 4) < 0) perror("write"); if (write(fd0_B, "0_B", 4) < 0) perror("write"); if (write(fd1_A, "1_A", 4) < 0) perror("write"); if (read(fd0_A, buff, 4) < 0) perror("read"); printf("%s\n", buff); if (read(fd0_B, buff, 4) < 0) perror("read"); printf("%s\n", buff); if (read(fd1_A, buff, 4) < 0) perror("read"); printf("%s\n", buff); if (close(fd0_A) != 0) perror("close"); if (close(fd0_B) != 0) perror("close"); if (close(fd1_A) != 0) perror("close"); return 0; } |
lazenca0x0@ubuntu:~/Kernel/Module/WR$ ./test1 0_A 0_B 1_A lazenca0x0@ubuntu:~/Kernel/Module/WR$ [ 1837.876449] The chardev_open() function has been called. [ 1837.876452] The chardev_open() function has been called. [ 1837.876484] The chardev_open() function has been called. [ 1837.876486] The chardev_write() function has been called. [ 1837.876488] Before calling the copy_from_user() function : 000000000affca99, helloworld [ 1837.876489] After calling the copy_from_user() function : 000000000affca99, 0_A [ 1837.876489] The chardev_write() function has been called. [ 1837.876490] Before calling the copy_from_user() function : 00000000d881b9fa, helloworld [ 1837.876491] After calling the copy_from_user() function : 00000000d881b9fa, 0_B [ 1837.876491] The chardev_write() function has been called. [ 1837.876492] Before calling the copy_from_user() function : 00000000ecd9f924, helloworld [ 1837.876492] After calling the copy_from_user() function : 00000000ecd9f924, 1_A [ 1837.876493] The chardev_read() function has been called. [ 1837.876559] The chardev_read() function has been called. [ 1837.876561] The chardev_read() function has been called. [ 1837.876563] The chardev_release() function has been called. [ 1837.876564] The chardev_release() function has been called. lazenca0x0@ubuntu:~/Kernel/Module/WR$ |