Excuse the ads! We need some help to keep our site up.

List

Character Device Drivers

struct file_operations

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;
static int chardev_open(struct inode *inode, struct file *file)
{
	printk("chardev_open");
	return 0;
}
struct file_operations chardev_fops = {
	.open    = chardev_open,
};

Example [struct file_operations - .open]

OS Information

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:~$ 

Source code

#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

Build & Run

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$
Command
insmod생성된 모듈을 Kernel의 symbol table을 통해 Kernel에 링크하는 명령어
rmmodKernel에 등록된 모듈을 제거하는 명령어
lsmodKernel에 등록된 모듈 목록을 출력하는 명령어
modinfo모듈에 상세한 정보를 출력하는 명령어
인자mknod <디바이스 파일명> <디바이스 파일 형식> <Major number> <Minor number>
디바이스 파일 형식
  • p : FIFO(:12)
  • b : 블럭 장치 파일 (block device file)
  • c, u : 문자 파일 (character special file), unbuffered special file
Major Number & Minor Number
  • MAJOR는 블럭장치 혹은 문자장치의 그룹에 할당되는 번호입니다.
  • MINOR 번호는 MAJOR로 묶여진 문자장치의 그룹중 하나에 할당되는 번호다.
  • 이 두개의 번호를 이용해서 장치를 식별할 수 있습니다.

Example [struct file_operations .open, .release, .read, .write]

Source code

chardev_init()

chardev_exit()

chardev_open()

chardev_release()

chardev_write()

chardev_read()

#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

Test program

Source code

#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;
}

Build & Run

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$ 

References