自己写字符驱动示例linux2.4

1.最近研究了下字符驱动,现在将过程记录下来。

2.首先应该搞明白你要再那个内核下编写驱动,2.4x or 2.6x ???我现在是2.4编写驱动,然后了解结构file_operations,这个结构在linux/fs.h中定义。

struct file_operations {
       struct module *owner;
       loff_t (*llseek) (struct file *, loff_t, int);
       ssize_t (*read) (struct file *, char *, size_t, loff_t *);
       ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
       int (*readdir) (struct file *, void *, filldir_t);
       unsigned int (*poll) (struct file *, struct poll_table_struct *);
       int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
       int (*mmap) (struct file *, struct vm_area_struct *);
       int (*open) (struct inode *, struct file *);
       int (*flush) (struct file *);
       int (*release) (struct inode *, struct file *);
       int (*fsync) (struct file *, struct dentry *, int datasync);
       int (*fasync) (int, struct file *, int);
       int (*lock) (struct file *, int, struct file_lock *);
    	 ssize_t (*readv) (struct file *, const struct iovec *, unsigned long,
          loff_t *);
    	 ssize_t (*writev) (struct file *, const struct iovec *, unsigned long,
          loff_t *);
    };
上面的结构来自于内核2.4.2,这个结构的作用是把你的设备执行相应操作的函数指针对应起来,比如你要执行read操作,你就应该有一个read函数;

后面会看到例子里面;

然后是给file_operations结构赋值,这里有两种不同的写法分别在2.4与2.6内核里;

2.4里面比较老的方法如下:

struct file_operations fops = {
       read: device_read,
       write: device_write,
       open: device_open,
       release: device_release
    };
2.6里面的赋值如下:

struct file_operations fops = {
       .read = device_read,
       .write = device_write,
       .open = device_open,
       .release = device_release
    };
如果你没有初始化结构成员的值,那么它会被gcc设置为NULL。


3.接下来我们需要注册一个设备;

字符设备是被当作为一个设备文件来操作的,这个设备文件在/dev目录下;主设备号告诉驱动应该和那个设备文件关联,副设备号是为了告诉驱动应该去操作

哪个具体的设备文件,当这个驱动与多个设备文件关联的时候;

 int register_chrdev(unsigned int major, const char *name,
       struct file_operations *fops);
使用上面的函数注册一个设备,注册一个设备到系统,我们不需要传递副设备号给系统;

major主设备号是你请求的设备号,name 是你的设备名称,这个设备名称会出现在/proc/devices里面, fops为相应的file_operations结构指针;

如果major为0,系统会动态分配一个主设备号给我们,如果动态生成,我们还需要将设备文件与主设备号关联;

这里提供三种方法进行配对:

a.动态分配后,由驱动打印出主设备号,然后我们通过mknod手动创建设备文件;

b.动态分配后,通过自动脚本读取/proc/devices里面的设备号建立设备文件;

c.动态分配后,直接在驱动里面调用mknod命令创建设备文件;


4.卸载设备;

当你已经完成了字符设备的所有操作函数与注册设备后,你需要知道如何卸载一个设备;

在卸载设备前我们来认识下几个宏:这些宏定义在linux/modules.h里面

  • MOD_INC_USE_COUNT: 递增使用计数器

  • MOD_DEC_USE_COUNT: 递减使用计数器

  • MOD_IN_USE: 显示使用计数器

以上的计数器是用来记录有多少人在使用这个驱动,如果计数器的值不为0,你将无法卸载这个驱动;

unregister_chrdev(Major, DEVICE_NAME);
此函数用来卸载一个设备;


5.下面我们来看下例子代码

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
//#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>




#define BUFFERSIZE 200
#define DEVICE_MAJOR 250
#define DEVICE_NAME "mycdev"
#define SUCCESS 0


static int device_major = DEVICE_MAJOR;
static char msg[BUFFERSIZE];
static char *msg_Ptr;
static int Device_Open = 0;




static int init_module(void);
static void cleanup_module(void);
static int my_cdev_open(struct inode *, struct file *);
static int my_cdev_release(struct inode *, struct file *);
static size_t my_cdev_read(struct file *, char *, size_t, loff_t *);
static size_t my_cdev_write(struct file *, const char *, size_t, loff_t *);
static loff_t my_cdev_llseek(struct file *filp, loff_t offset, int orig);


static const struct file_operations my_cdev_fops=
{
    .owner = THIS_MODULE,
    .open = my_cdev_open,
    .release = my_cdev_release,
    .read = my_cdev_read,
    .write = my_cdev_write,
    .llseek = my_cdev_llseek,
};


static int my_cdev_open(struct inode *node, struct file *filp)
{
    static int counter = 0;
    if(Device_Open) return -EBUSY;
    Device_Open++;
    sprintf(msg, "I already told you %d times Hello world!\n", counter++);
    msg_Ptr = msg;
    MOD_INC_USE_COUNT;
    
    return 0;
}


static int my_cdev_release(struct inode* node, struct file* filp)
{
    Device_Open--;
    MOD_DEC_USE_COUNT;
    return 0;
}


static size_t my_cdev_read(struct file *filp, char *buf, size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    if(*msg_Ptr == 0)
        return 0;
    if(p >= BUFFERSIZE)
        return count ? -ENXIO : 0;
    if(copy_to_user(buf, (void*)(msg_Ptr+p), count))
    {
        ret = -EFAULT;
    }else{
        *ppos += count;
        ret = count;
    }
    return ret;
}


static size_t my_cdev_write(struct file *filp, const char *buf, size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    
    if(p >= BUFFERSIZE)
        return count ? -ENXIO : 0;
    if(count > BUFFERSIZE - p)
        count = BUFFERSIZE -p;
    if(copy_from_user(msg_Ptr, buf, count))
    {
        ret = -EFAULT;
    }else{
        *ppos += count;
        ret = count;
    }
    return ret;
}


static loff_t my_cdev_llseek(struct file *filp, loff_t offset, int orig)
{
    loff_t ret = 0;
    switch(orig)
    {
        case 0:
        if(offset < 0)
        {
            ret = -EINVAL;
            break;
        }
        if(offset > BUFFERSIZE)
        {
            ret = -EINVAL;
            break;
        }
        filp->f_pos = (unsigned int)offset;
        ret = filp->f_pos;
        break;
        default:
        ret = -EINVAL;
        break;
    }
    return ret;
}


/*
static void my_cdev_setup(struct my_cdev *dev, int index)
{
    int err;
    dev_t devno = MKDEV(DEVICE_MAJOR, index);
    cdev_init(&dev->cdev, &my_cdev_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &my_cdev_fops;
    err = cdev_add(&dev->cdev, devno, 1);
    if(err)
        printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
*/


static int init_module(void)
{
    device_major = register_chrdev(0, DEVICE_NAME, &my_cdev_fops);
    if(device_major < 0)
    {
        printk("Register the character device failed with %d\n", device_major);
        return device_major;
    }


    printk("<1>I was assigned major number %d.  To talk to\n", device_major);
    printk("<1>the driver, create a dev file with\n");
    printk("'mknod /dev/mycdev c %d 0'.\n", device_major);
    printk("<1>Try various minor numbers.  Try to cat and echo to\n");
    printk("the device file.\n");
    printk("<1>Remove the device file and module when done.\n");
    return 0;
}


static void cleanup_module(void)
{
    int ret = unregister_chrdev(device_major, DEVICE_NAME);
    if(ret < 0)
        printk("Error in unregister_chrdev: %d\n", ret);
}


MODULE_AUTHOR("Robert Luo");
MODULE_LICENSE("Dual BSD/GPL");


请在你自己的系统上找到相应的编译器进行编译后,编译为模块加载形式,加载;


然后根据提示建立一个设备文件mknod /dev/mycdev c 254 0


最后我们写一段测试代码来看看驱动是否可以用了;

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>

#define BUFFERSIZE 200

int main(void)
{
	int fp =0;
	char str[BUFFERSIZE];
	fp = open("/dev/mycdev", O_RDWR);
	if(!fp)
	{
		printf("Open device failed.\n");
		return -1;
	}
	write(fp, "Hello, my devices", strlen("Hello, my devices"));
	lseek(fp, 0, 0 );
	read(fp, str, BUFFERSIZE);
	printf("Read content: %s\n", str);
	close(fp);
}

编译后运行,你应该会看到类似的显示,没允许一次提示的数字都会变,你发现这段显示是不是不对? 呵呵,自己看看原因吧。我懒得改了。

Read content: Hello, my devicesu 0 times Hello world!

Read content: Hello, my devicesu 1 times Hello world!

Read content: Hello, my devicesu 2 times Hello world!




你可能感兴趣的:(自己写字符驱动示例linux2.4)