Linux设备驱动程序学习笔记07:字符设备驱动程序V

时间:2022-06-25 23:36:47

我们的字符设备的驱动程序源码如下:

/*  
memdev.c : ldd

Author : moon.cheng.2014@gmail.com
Date : 2014-08-05
Version: 1.0

This program is a demo program for linux device drivers created by moon.
It is a driver for a segment of memory.You can use the interface it supply
to operate the memory. Such as read,write or clear it.

If you find some bugs or you have some advices. Send me a email pls!

This program is free software; you can redistribute it and/or modify
<span style="white-space:pre"></span>it under the terms of the GNU General Public License as published by
<span style="white-space:pre"></span>the Free Software Foundation; either version 2 of the License, or
<span style="white-space:pre"></span>(at your option) any later version.

*/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>/*copy_to_user & copy_from_user*/

#define M_DEBUG 1 /*debug output infomation switch*/
#define MEM_SIZE 512 /*memory size*/
#define MAJOR_DEVNO 0 /*use 0 to use malloc_chrdev_region*/

static int major_devno = MAJOR_DEVNO;
static struct memdev *devp;
/*device struct*/
struct memdev {
struct cdev cdev;
unsigned char mem[MEM_SIZE];
};


loff_t memdev_llseek(struct file *fp, loff_t off, int ori)
{
int ret;

#if M_DEBUG
printk(KERN_INFO"%s:%d off = %d ori = %d\n", __func__, __LINE__,off, ori);
#endif
switch (ori) {
case SEEK_SET:
if (off < 0 || off > MEM_SIZE) {
return -EINVAL;
}
fp -> f_pos = off;
ret = fp -> f_pos;
break;
case SEEK_CUR:
if ((fp->f_pos + off < 0) || (fp->f_pos + off > MEM_SIZE)) {
return -EINVAL;
}
fp -> f_pos += off;
ret = fp -> f_pos;
break;
case SEEK_END:
if (off < 0 || off > MEM_SIZE) {
return -EINVAL;
}
fp -> f_pos = MEM_SIZE - off;
ret = fp -> f_pos;
break;
default :
return -EINVAL;
}
return ret;
}

ssize_t memdev_read(struct file *fp, char __user *buff, size_t count, loff_t *f_pos)
{
struct memdev *devp = fp->private_data;
unsigned int size = count;
unsigned long p = *f_pos;
int ret = 0;
#if M_DEBUG
printk(KERN_INFO"%s:%d *f_pos = %d size = %d\n", __func__, __LINE__, p, size);
#endif
if ( p > MEM_SIZE) {
return -EINVAL;
}
if (p + size > MEM_SIZE) {
size = MEM_SIZE - p;
}

if (copy_to_user(buff, devp->mem + p, size)) {
ret = -EFAULT;
} else {
ret = size;
*f_pos += size;
#if M_DEBUG
printk(KERN_INFO"%s:%d read %d bytes from mem!\n", __func__, __LINE__, size);
#endif
}

return ret;
}

ssize_t memdev_write(struct file *fp, const char __user *buff, size_t count, loff_t *f_pos)
{
struct memdev *devp = fp->private_data;
unsigned int size = count;
unsigned long p = *f_pos;
int ret = 0;
#if M_DEBUG
printk(KERN_INFO"%s:%d *f_pos = %d size = %d\n", __func__, __LINE__, p, size);
#endif
if ( p > MEM_SIZE) {
return -EINVAL;
}
if (p + size > MEM_SIZE) {
size = MEM_SIZE - p;
}

if (copy_from_user(devp->mem + p, buff, size)){
ret = -EFAULT;
} else {
ret = size;
*f_pos += size;
#if M_DEBUG
printk(KERN_INFO"%s:%d write %d bytes to mem!\n", __func__, __LINE__, size);
#endif
}

return ret;
}

int memdev_ioctl(struct inode *nodep, struct file *fp, unsigned int cmd, unsigned long arg)
{
int ret;
struct memdev *devp;

#if M_DEBUG
printk(KERN_INFO"%s:%d cmd = %d \n", __func__, __LINE__, cmd);
#endif
switch (cmd) {
case 0 :
devp = fp -> private_data;
memset(devp->mem, 0, MEM_SIZE);
break;
default : ret = -EINVAL;
}
return 0;
}

int memdev_open(struct inode *nodep, struct file *fp)
{
struct memdev *devp;

devp = container_of(nodep->i_cdev, struct memdev, cdev);
fp->private_data = devp;
#if M_DEBUG
printk(KERN_INFO"%s:%d you have open the device\n", __func__, __LINE__);
#endif
return 0;
}


int memdev_release(struct inode *nodep, struct file *fp)
{
#if M_DEBUG
printk(KERN_INFO"%s:%d you have release the device\n", __func__, __LINE__);
#endif
return 0;
}


/*file_operations */
static struct file_operations memdev_fops = {
.owner = THIS_MODULE,
.llseek = memdev_llseek,
.read = memdev_read,
.write = memdev_write,
.ioctl = memdev_ioctl,
.open = memdev_open,
.release = memdev_release,
};

static int __init memdev_init(void)
{
int ret;
dev_t devno;

/*apply for device no*/
if (major_devno) {
devno=MKDEV(major_devno, 0);
ret = register_chrdev_region(devno, 0, "memdev");
} else {
ret = alloc_chrdev_region(&devno, 0, 1, "memdev");
major_devno = MAJOR(devno);
}
if (!!ret) {
printk(KERN_ERR"arrage device no erro!\n");
return ret;
}

/*apply for device struct*/
devp = kmalloc(sizeof(struct memdev), GFP_KERNEL);
if (!devp) {
printk(KERN_ERR"malloc memory to devp erro!\n");
unregister_chrdev_region(devno, 1);
return -ENOMEM;
}
memset(devp, 0, sizeof(struct memdev));

/*Init our device struct*/
cdev_init(&devp->cdev, &memdev_fops);
devp->cdev.owner = THIS_MODULE;
ret = cdev_add(&devp->cdev, devno, 1);
if (!!ret) {
printk(KERN_ERR"register the device to system erro!\n");
kfree(devp);
unregister_chrdev_region(devno, 1);
return ret;
}

return 0;

}

static void __exit memdev_exit(void)
{
cdev_del(&devp->cdev);
kfree(devp);
unregister_chrdev_region(MKDEV(major_devno, 0), 1);
}

module_init(memdev_init);
module_exit(memdev_exit);

MODULE_AUTHOR("moon.cheng.2014@gmail.com");
MODULE_VERSION("v1.0");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is a charactor device demo driver");

同时我们为其写了一个小的测试程序,源代码如下:

/*  
memts.c : ldd

Author : moon.cheng.2014@gmail.com
Date : 2014-08-05
Version: 1.0

This program is a testing program for charactor drivers.You can use
it to open/close/read/write/llseek/ioctl a charactor devices. The
origin purpose for this program is to test the functions in memdev.c


If you find some bugs or you have some advices. Send me a email pls!

This program is free software; you can redistribute it and/or modify
<span style="white-space:pre"></span>it under the terms of the GNU General Public License as published by
<span style="white-space:pre"></span>the Free Software Foundation; either version 2 of the License, or
<span style="white-space:pre"></span>(at your option) any later version.

*/
#include <stdio.h>
#include <fcntl.h>
#include <string.h>

void m_read(int fd)
{
int r_number;
char buff[33];

memset(buff, 0, 32);

printf("input the number of bytes you want to read pls[1-32]:");
scanf("%d", &r_number);
getchar();

if (r_number <= 0 || r_number > 32) {
r_number = 4;
}
r_number = read(fd, buff, r_number);

buff[r_number+1] = '\0';
printf("%s\n", buff);
}

void m_write(int fd)
{
char buff[32];
printf("input the string you want to write pls:");
memset(buff, 0, 32);
fgets(buff, 32, stdin);
write(fd, buff, strlen(buff)-1);
}

void m_seek(int fd)
{
char seek_base;
int offset;
printf("syntax:_N N is the offset number\n");
printf(" sN---seek from the begin\n");
printf(" cN---seek from the current\n");
printf(" eN---seek from the end\n");
printf("input where to seek pls:");

seek_base = getchar();
scanf("%d", &offset);
getchar();

switch (seek_base){
case 's':
lseek(fd, offset, SEEK_SET);
break;
case 'c':
lseek(fd, offset, SEEK_CUR);
break;
case 'e':
lseek(fd, offset, SEEK_END);
break;
default:
printf("Check you input!\n");
}
}

int main(void)
{
int fd = -1;
char cmd;
do {
printf("[o-open|w-write|r-read|c-clear|s-seek|q-quit]:");
cmd = getchar();
getchar();
switch (cmd) {
case 'o':
if (-1 == fd)
fd = open("./memdev", O_RDWR);
break;
case 'w':
if (-1 == fd) {
printf("you should run command o first!\n");
break;
}
m_write(fd);
break;
case 'r':
if (-1 == fd) {
printf("you should run command o first!\n");
break;
}
m_read(fd);
break;
case 'q':
if (-1 != fd)
close(fd);
break;
case 'c':
if (-1 == fd) {
printf("you should run command o first!\n");
break;
}
ioctl(fd, 0);
break;
case 's':
if (-1 == fd) {
printf("you should run command o first!\n");
break;
}
m_seek(fd);
break;
default:
;
}
}while('q' != cmd);
return 0;
}

编译好ko文件并将其insmod到系统之后,我们需要创建设备节点文件。通过查看/proc/devices来看系统给我们分配的设备节点:

Linux设备驱动程序学习笔记07:字符设备驱动程序V

可以看到我们的主设备号是250,通过mknod命令在当前目录下创建设备节点:

Linux设备驱动程序学习笔记07:字符设备驱动程序V

编译好memts程序后,我们就可以运行它来测试我们的字符设备了。打开字符设备的M_DEBUG宏,我们可以再打开一个终端通过dmesg来查看我们设置的打印消息。

下面的两张图是我们的测试输出以及通过dmesg看到的打印消息对照图:

Linux设备驱动程序学习笔记07:字符设备驱动程序V

Linux设备驱动程序学习笔记07:字符设备驱动程序V

还可以通过cat以及echo等命令,同我们的测试程序一起来操作设备文件。大家可以去尝试一下。到此,字符设备驱动程序的基本内容已经学完了。