KDD字符设备基本概念

数据结构及关系如下:

include/linux/cdev.h
12 struct cdev {
13         struct kobject kobj;
14         struct module *owner;
15         const struct file_operations *ops;
16         struct list_head list;
17         dev_t dev;
18         unsigned int count;
19 };

include/linux/fs.h
518 /*
519  * Keep mostly read-only and often accessed (especially for
520  * the RCU path lookup and 'stat' data) fields at the beginning
521  * of the 'struct inode'
522  */
523 struct inode {
595         union {
596                 struct pipe_inode_info  *i_pipe;
597                 struct block_device     *i_bdev;
598                 struct cdev             *i_cdev;
599         };
612 };

include/linux/fs.h
765 struct file {
777         struct inode            *f_inode;       /* cached value */
800         /* needed for tty driver, and maybe others */
801         void                    *private_data;
812 };

struct cdev表示字符设备,ops为文件操作函数,dev为设备号:

// include/linux/cdev.h
struct cdev {
    struct module *owner;
    const struct file_operations *ops;
    dev_t dev;
    // ......
};

关联cdev和ops的函数是cdev_init:

void cdev_init(struct cdev *, const struct file_operations *);

关联cdev和dev的函数是cdev_add,即添加字符设备:

int cdev_add(struct cdev *, dev_t, unsigned);

添加字符设备后,就可以用mknod创建文件,可以读写。

创建设备号时,需要指定设备名,即函数alloc_chrdev_region。

mknod需要知道设备名和设备号,安装驱动后,可以在/proc/devices中依据设备名查到设备号:

insmod hello.ko \
&& major_device_number=`awk '$2=="winlin" {print $1}' /proc/devices` \
&& mknod /dev/winlin c $major_device_number 0

创建文件后,就可以读写这个文件/dev/winlin了:

cat /dev/winlin \
&& echo "linux is a great operating system for server." > /dev/winlin \
&& cat /dev/winlin

最后,删除设备和驱动:

rm -f /dev/winlin \
&& rmmod hello

完整源码如下:

// hello.c
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");

dev_t devno;
int count = 1;
char* name = "winlin";

/**
make

insmod hello.ko \
&& major_device_number=`awk '$2=="winlin" {print $1}' /proc/devices` \
&& mknod /dev/winlin c $major_device_number 0

cat /dev/winlin \
&& echo "linux is a great operating system for server." > /dev/winlin \
&& cat /dev/winlin

rm -f /dev/winlin \
&& rmmod hello
*/

/*module_param(count, int, S_IRUGO|S_IWUSR);*/
module_param(name, charp, S_IRUGO|S_IWUSR);

#include <linux/fs.h>
#include <linux/uaccess.h>

char _data[1024] = "KDD char device buffer.\n";

ssize_t hello_read(struct file* file, 
    char __user* data, size_t size, loff_t* offset
){
    int len = strlen(_data);
    
    if(*offset >= len){
        return 0;
    }
    
    printk(KERN_ALERT
        "hello_read, len=%d, size=%d, offset=%d\n", 
        len, (int)size, (int)*offset);
        
    if(copy_to_user(data, _data, (len < size)? len:size) != 0){
        return -1;
    }
    
    *offset += len;
    return len;
}

ssize_t hello_write(struct file* file, 
    const char __user* data, size_t size, loff_t* offset
){
    int size_to_write = (sizeof(_data) - 1 < size)? sizeof(_data) - 1:size;
    
    printk(KERN_ALERT
        "hello_write, len=%d, size=%d, offset=%d\n", 
        size_to_write, (int)size, (int)*offset);
        
    if(copy_from_user(_data, data, size_to_write) != 0){
        return -1;
    }
    
    _data[size_to_write] = 0;
    
    return size_to_write;
}

int hello_open(struct inode* node, struct file* file){
    return 0;
}

int hello_release(struct inode* node, struct file* file){
    return 0;
}

struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = hello_read,
    .write = hello_write,
    .open = hello_open,
    .release = hello_release,
};

#include <linux/cdev.h>
struct cdev dev;

int hello_init(void){
    printk(KERN_INFO "hello, %s!\n", name);
    
    if(alloc_chrdev_region(&devno, 0, count, name) != 0){
        return -1;
    }
    
    cdev_init(&dev, &fops);
    dev.owner = THIS_MODULE;
    //dev.ops = &fops;
    
    if(cdev_add(&dev, devno, 1) != 0){
        return -1;
    }
    
    return 0;
}

void hello_exit(void){
    printk(KERN_ALERT "goodbye, %s\n", name);
    
    unregister_chrdev_region(devno, count);
}

module_init(hello_init);
module_exit(hello_exit);

Makefile如下:

ifneq ($(KERNELRELEASE),)
    obj-m := hello.o
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)

default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
	rm -f *.mod.* *.ko *.order *.symvers *.o

endif

一个驱动可以创建多个设备,即minor不一样,major一样。代码如下:

// hello.c
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");

int major = 0;
int count = 1;
char* name = "winlin";

/**
make

insmod hello.ko count=2\
&& major_device_number=`awk '$2=="winlin" {print $1}' /proc/devices` \
&& mknod /dev/winlin0 c $major_device_number 0 \
&& mknod /dev/winlin1 c $major_device_number 1

cat /dev/winlin0 \
&& echo "[dev0] linux is a great operating system for server." > /dev/winlin0 \
&& cat /dev/winlin0

cat /dev/winlin1 \
&& echo "[dev1] linux is a great operating system for server." > /dev/winlin1 \
&& cat /dev/winlin1

rm -f /dev/winlin* \
&& rmmod hello
*/

module_param(count, int, S_IRUGO|S_IWUSR);
module_param(name, charp, S_IRUGO|S_IWUSR);

#include <linux/fs.h>
#include <linux/uaccess.h>

char _data[1024] = "KDD char device buffer.\n";

ssize_t hello_read(struct file* file, 
    char __user* data, size_t size, loff_t* offset
){
    int len = strlen(_data);
    
    if(*offset >= len){
        return 0;
    }
    
    printk(KERN_ALERT
        "hello_read, len=%d, size=%d, offset=%d\n", 
        len, (int)size, (int)*offset);
        
    if(copy_to_user(data, _data, (len < size)? len:size) != 0){
        return -1;
    }
    
    *offset += len;
    return len;
}

ssize_t hello_write(struct file* file, 
    const char __user* data, size_t size, loff_t* offset
){
    int size_to_write = (sizeof(_data) - 1 < size)? sizeof(_data) - 1:size;
    
    printk(KERN_ALERT
        "hello_write, len=%d, size=%d, offset=%d\n", 
        size_to_write, (int)size, (int)*offset);
        
    if(copy_from_user(_data, data, size_to_write) != 0){
        return -1;
    }
    
    _data[size_to_write] = 0;
    
    return size_to_write;
}

int hello_open(struct inode* node, struct file* file){
    printk(KERN_ALERT "hello_open file\n");
    return 0;
}

int hello_release(struct inode* node, struct file* file){
    printk(KERN_ALERT "hello_release file\n");
    return 0;
}

struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = hello_read,
    .write = hello_write,
    .open = hello_open,
    .release = hello_release,
};

#include <linux/cdev.h>
struct cdev dev;

int hello_init(void){
    int i;
    dev_t devno;
    printk(KERN_INFO "hello, %s!\n", name);
    
    if(alloc_chrdev_region(&devno, 0, count, name) != 0){
        return -1;
    }
    major = MAJOR(devno);
    
    cdev_init(&dev, &fops);
    dev.owner = THIS_MODULE;
    //dev.ops = &fops;
    
    for(i = 0; i < count; i++){
        if(cdev_add(&dev, MKDEV(major, i), 1) != 0){
            return -1;
        }
    }
    
    return 0;
}

void hello_exit(void){
    printk(KERN_ALERT "goodbye, %s\n", name);
    
    cdev_del(&dev);
    unregister_chrdev_region(MKDEV(major, 0), count);
}

module_init(hello_init);
module_exit(hello_exit);

这两个设备都是共享一个cdev和data,可以让每个设备有不同的表示,即根据count来开辟一个结构体,这个结构体包含了cdev和data。

代码如下:

// hello.c
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");

int major = 0;
int count = 1;
char* name = "winlin";

/**
make

insmod hello.ko count=2\
&& major_device_number=`awk '$2=="winlin" {print $1}' /proc/devices` \
&& mknod /dev/winlin0 c $major_device_number 0 \
&& mknod /dev/winlin1 c $major_device_number 1

cat /dev/winlin0 \
&& echo "[dev0] linux is a great operating system for server." > /dev/winlin0 \
&& cat /dev/winlin0

cat /dev/winlin1 \
&& echo "[dev1] linux is a great operating system for server." > /dev/winlin1 \
&& cat /dev/winlin1

rm -f /dev/winlin* \
&& rmmod hello
*/

module_param(count, int, S_IRUGO|S_IWUSR);
module_param(name, charp, S_IRUGO|S_IWUSR);

#include <linux/fs.h>
#include <linux/uaccess.h>

#include <linux/cdev.h>

struct winlin_dev{
    dev_t devno;
    char data[1024];
    struct cdev cdev;
};
const char* msg = "KDD char device buffer.\n";
struct winlin_dev* devs;

ssize_t hello_read(struct file* file, 
    char __user* data, size_t size, loff_t* offset
){
    struct winlin_dev* dev = file->private_data;
    int len = strlen(dev->data);
    
    if(*offset >= len){
        return 0;
    }
    
    printk(KERN_ALERT
        "hello_read, len=%d, size=%d, offset=%d\n", 
        len, (int)size, (int)*offset);
        
    if(copy_to_user(data, dev->data, (len < size)? len:size) != 0){
        return -1;
    }
    
    *offset += len;
    return len;
}

ssize_t hello_write(struct file* file, 
    const char __user* data, size_t size, loff_t* offset
){
    struct winlin_dev* dev = file->private_data;
    int size_to_write = (sizeof(dev->data) - 1 < size)? sizeof(dev->data) - 1:size;
    
    printk(KERN_ALERT
        "hello_write, len=%d, size=%d, offset=%d\n", 
        size_to_write, (int)size, (int)*offset);
        
    if(copy_from_user(dev->data, data, size_to_write) != 0){
        return -1;
    }
    
    dev->data[size_to_write] = 0;
    
    return size_to_write;
}

int hello_open(struct inode* node, struct file* file){
    struct winlin_dev* dev = container_of(node->i_cdev, struct winlin_dev, cdev);
    file->private_data = dev;
    return 0;
}

int hello_release(struct inode* node, struct file* file){
    return 0;
}

struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = hello_read,
    .write = hello_write,
    .open = hello_open,
    .release = hello_release,
};

int hello_init(void){
    int i;
    dev_t devno;
    printk(KERN_INFO "hello, %s!\n", name);
    
    if(alloc_chrdev_region(&devno, 0, count, name) != 0){
        return -1;
    }
    major = MAJOR(devno);
    
    devs = kmalloc(count * sizeof(struct winlin_dev), GFP_KERNEL);
    if(!devs){
        return -1;
    }
    memset(devs, 0, count * sizeof(struct winlin_dev));
    
    for(i = 0; i < count; i++){
        cdev_init(&devs[i].cdev, &fops);
        devs[i].cdev.owner = THIS_MODULE;
        //devs[i].cdev.ops = &fops;
        
        devs[i].devno = MKDEV(major, i);
        if(cdev_add(&devs[i].cdev, devs[i].devno, 1) != 0){
            return -1;
        }
        memcpy(devs[i].data, msg, strlen(msg));
    }
    
    return 0;
}

void hello_exit(void){
    int i;
    printk(KERN_ALERT "goodbye, %s\n", name);
    
    for(i = 0; i < count; i++){
        cdev_del(&devs[i].cdev);
    }
    kfree(devs);
    
    unregister_chrdev_region(MKDEV(major, 0), count);
}

module_init(hello_init);
module_exit(hello_exit);

Makefile修改为:

ifneq ($(KERNELRELEASE),)
    obj-m := hello.o
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)

default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
	rm -f *.mod.* *.ko *.order *.symvers *.o

install:
	bash install.sh

uninstall:
	rm -f /dev/winlin*
	rmmod hello

endif

安装脚本:

#!/bin/bash
#install.sh
insmod hello.ko count=2
major_device_number=`cat /proc/devices|grep winlin|awk '{print $1}'`
mknod /dev/winlin0 c ${major_device_number} 0
mknod /dev/winlin1 c ${major_device_number} 1


你可能感兴趣的:(KDD字符设备基本概念)