`
sony-soft
  • 浏览: 1008509 次
文章分类
社区版块
存档分类
最新评论

Linux字符设备驱动(三)

 
阅读更多

字符设备驱动之体验篇

.字符设备之编程

通过程序来体验字符设备驱动编程的过程

Linux系统中,字符设备驱动由如下几个部分组成:

(1)字符设备驱动模块加载与卸载函数

(2)字符设备驱动的file_operations结构体中成员函数

file_operations结构体中成员函数是字符设备驱动与内核的接口,是用户空间对Linux进行系统调用最终的实现着。

(3)在字符设备驱动中,需要定义一个file_operations的实例,并将具体设备驱动的函数赋值给file_operations的成员。

它完成以下的功能:
1.
对设备初始化和释放。
2.
把数据从内核传送到硬件和从硬件读取数据。
3.
读取应用程序传送给设备文件的数据和回送应用程序请求的数据。
4.
检测和处理设备出现的错误。

1.设备驱动的头文件,宏即设备结构体

(1)头文件

#include<linux/kernel.h>

#include<linux/init.h>

#include<linux/module.h> //内核模块编程所必需的

#include<linux/cdev.h>

#include<linux/fs.h>//设备编程所必须的

#include<asm/io.h>

#include<asm/system.h>

#include<linux/types.h>//

#include<asm/uaccess.h>//设备编程所必须的

#include<linux/mm.h>

#include<linux/sched.h>//调度和内存管理所必需的

#include<linux/errno.h>

(2)宏和全局变量

#define GLOBALMEM_MAJOR 254 //预设的globalmem的主设备号

#define GLOBALMEM_SIZE 0x1000 //全局内存最大为4kb

#define MEM_CLEAR 0x1 //清空全局内存

static globalmem_major = GLOBALMEM_MAJOR //主设备号

//globalmem设备结构体

struct globalmem_dev

{

struct cdev cdev; //cdev结构体,设备编程,必须的

unsigned char mem[GLOBALMEM_SIZE];//全局内存

}

struct globalmem_dev *dev;//设备结构体实例

1>定义globalmem_dev设备结构体包含了对应于globalmem字符设备的cdev,使用的内存mem[GLOBALMEM_SIZE].这样做体现了面向对象程序设计中“封装“的思想。

2.加载和卸载设备驱动

(1)在字符设备驱动模块加载函数中应该实现设备号的申请和cdev的注册,申请设备结构体的内存

static int __init global_init(void)

{

int result;

dev_t devno = MKDEV(globalmem_major,0)//保存设备号

//申请设备号

if(globalmem_major)

result = register_chrdev_region(devno,1,"globalmem");

else{//动态获得主设备号

result = alloc_chrdev_region(&devno,0,1,"globalmem")

globalmem_major = MAJOR(devno);

}

if(result < 0)

return restult;

dev = kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL);

if(!dev)//申请失败

{

result = -ENOMEM;

goto fail_malloc;

}

memset(dev,0,sizeof(struct globalmem_dev));

globalmem_setup_cdev(devno, 0);//字符设备的注册

return 0;

fail_malloc :

unregister_chrdev_region(devno,1);

return result;

}

(2)在卸载函数中应实现设备号的释放和cdev的注销

static void __exit global_exit(void)

{

cdev_del(&globalmem_devp->cdev); /*注销cdev,即从系统中删除此设备*/

kfree(globalmem_devp); /*释放设备结构体内存*/

unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/

}

要先注销字符设备之后,才能释放设备号

(3)globalmem_setup_cdev()函数完成cdev的初始化和添加

static void globalmem_setup_cdev(struct globalmem_dev *dev,int index)

{

int err;

dev_t devno = MKDEV(globalmem_major,index);

cdev_init(&dev->cdev,&globalmem_fops);

dev->cdev.owner = THIS_MODULE;

dev->cdev.ops = &globalmem_fops;

err = cdev_add(&dev->cdev, devno, 1);

if (err)

printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);

}

1>在获取了设备号范围之后,需要将设备添加到字符设备结构体中,以激活设备。这需要用cdev_init初始化一个sturct cdev的实例,接下来调用cdev_add。在cdev_add成功返回后,设备进入活动状态。

2>cdev_init()函数中,与globalmemdev关联的file_operations结构体代码为

static const struct file_operations globalmem_fops =

{

.owner = THIS_MODULE,

.llseek = globalmem_llseek,

.open = globalmem_open,

.read = globalmem_read,

.write = globalmem_write,

.ioctl = globalmem_ioctl,

.release = globalmem_release,

};

3>申请设备号后在这部分中,比较重要的是在用函数获取设备编号后,其中的参数name是和该编号范围关联的设备名称,它将出现在/proc/devicessysfs中。

3.file_operations中具体函数的实现

打开的设备在内核内部由file结构标识,内核使用file_operations结构访问驱动程序的函数。下面主要介绍常用的几个成员:

(1)文件打开函数

int globalmem_open(struct inode *inode,struct file *filp)

{

//将设备结构体指针赋值给文件私有数据指针

filp->private_data = globalmem_devp;

}

(2)读写函数

globalmem设备驱动的读写函数主要是让设备结构体的mem[]数组与用户空间交互数据,并随着访问的字节数变更返回用户的文件读写偏移位置。

1>读函数

static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,loff_t *ppos)

{

unsigned long p = *ppos;

unsigned int count = size;

int ret = 0;

/*获得设备结构体指针*/

struct globalmem_dev *dev = filp->private_data;

/*分析获取有效的写长度*/

if (p >= GLOBALMEM_SIZE)

return count ? - ENXIO: 0;

if (count > GLOBALMEM_SIZE - p)

count = GLOBALMEM_SIZE - p;

/*从内核空间向用户空间写数据*/

if (copy_to_user(buf, (void*)(dev->mem + p), count))

{

ret = - EFAULT;

}

else

{

*ppos += count;

ret = count;

printk(KERN_INFO "read %d bytes(s) from %d/n", count, p);

}

return ret;

}

2>写函数

static ssize_t globalmem_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)

{

unsigned long p = *ppos;

unsigned int count = size;

int ret = 0;

/*获得设备结构体指针*/

struct globalmem_dev *dev = filp->private_data;

/*分析和获取有效的写长度*/

if (p >= GLOBALMEM_SIZE)

return count ? - ENXIO: 0;

if (count > GLOBALMEM_SIZE - p)

count = GLOBALMEM_SIZE - p;

/*从用户空间向内核空间写数据*/

if (copy_from_user(dev->mem + p, buf, count))

ret = - EFAULT;

else

{

*ppos += count;

ret = count;

printk(KERN_INFO "written %d bytes(s) from %d/n", count, p);

}

return ret;

}

3)文件定位函数

/* seek文件定位函数*/

static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)

{

loff_t ret = 0;

switch (orig)

{

case 0: /*相对文件开始位置便宜*/

if (offset < 0)

{

ret = - EINVAL;

break;

}

if ((unsigned int)offset > GLOBALMEM_SIZE)

{

ret = - EINVAL;

break;

}

//f_pos文件当前的读写位置

//SEEK_SET

filp->f_pos = (unsigned int)offset;

ret = filp->f_pos;

break;

case 1: /*相对文件当前位置偏移*/

if ((filp->f_pos + offset) > GLOBALMEM_SIZE)

{

ret = - EINVAL;

break;

}

if ((filp->f_pos + offset) < 0)

{

ret = - EINVAL;

break;

}

/*SEEK_CUR*/

filp->f_pos += offset;

ret = filp->f_pos;

break;

default:

ret = - EINVAL;

break;

}

return ret;

}

4ioctl设备控制函数

/*ioctl设备控制函数*/

static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg)

{

/*获得设备结构体指针*/

struct globalmem_dev *dev = filp->private_data;

switch (cmd)

{

case MEM_CLEAR:

memset(dev->mem, 0, GLOBALMEM_SIZE);

printk(KERN_INFO "globalmem is set to zero/n");

break;

default:

return - EINVAL;

}

return 0;

}

(5)文件释放函数

int globalmem_release(struct inode *inode,struct file *filp)

{

return 0;

}

说明:

1>一般都是将文件的私有数据private_data 指向设备结构体,read(),write(),ioctl(),llseek()等函数通过private_data访问设备结构体。

2>除了在globalmem_open()函数中通过filp->private_data = globalmem_devp将设备结构体指针赋值给文件私有数据指

针并在

globalmem_write(),globalmem_read(),globalmem_llseek()

globalmem_ioctl()函数中通过struct globalmem_dev *dev = filp->private_data语句获得设备结构体指针并使用该指针操作设备结构体。

3>如果globalmem不只包括一个设备,而是同时包括两个或两个以上的设备,采用private_data的优势就会显示出来。

4>在对globalmem_read(),globalmem_write(),globalmem_ioctl()等重要函数及globalmem_fops结构体等数据结构体进行任何修改的前提下,只是简单地修改globalmem_init(),globalmem_exit()globalmem_open()就可以轻松地让globalmem驱动中包含两个同样的设备(次设备好分别为01)

4.支持两个globalmem设备的globalmem驱动

(1)文件打开函数

int globalmem_open(struct inode *inode,struct file *file)

{

//将设备结构体指针赋值给文件私有数据指针

struct globalmem_dev *dev;

dev = container_of(inode->i_cdev,struct globalmem_dev,cdev);

filp->private_data = dev;

return 0;

}

1>container_of的作用是通过结构体成员的指针找到对应结构体的指针,这个技巧在Linux内核编程中十分常用。在container_of(inode->i_cdev,struct globalmem_dev,cdev)语句中,传给container_of()的第一个参数是结构体成员的指针,第二个参数为整个结构体的类型,第三个参数为传入的第一个参数即结构体成员的类型,container_of()返回值为整个结构体的指针。

(2)设备驱动模块加载函数

int globalmem_init(void)

{

int result;

dev_t devno = MKDEV(globalmem_major,0);

//申请设备号

if(globalmem_major)

result = register_chrdev_region(devno,2,"globalmem");

else//动态申请设备号

{

result = alloc_chrdev_region(&devno,0,2,"globalmem");

globalmem_major = MAJOR(devno);

}

if(result < 0)

return result;

//动态申请两个设备结构体的内存

globalmem_devp=kmalloc(2*sizeof(struct globalmem_dev),GFP,KERNEL);

if(!globalmem_devp) //申请失败

{

result = - ENOMEM;

goto fail_malloc;

}

memset(globalmem_devp,0,2*sizeof(struct globalmem_dev));

globalmem_setup_cdev(&globalmem_devp[0],0);

globalmem_setup_cdev(&globalmem_devp[1],1);

return 0;

fail_malloc: unregister_chrdev_region(devno, 2);

return result;

}

(3)模块卸载函数

void globalmem_exit(void)

{

cdev_del(&(globalmem_devp[0].cdev));

cdev_del(&(globalmem_devp[1].cdev));

kfree(globalmem_devp);//释放设备结构体内存

unregister_chrdev_region(MKDEV(globalmem_major,0),2);//释放设备号

}

1>在支持两个globalmem设备的驱动,在加载模块后需创建两个设备节点:

/dev/globalmem0对应主设备号globalmem_major,次设备号0/dev/globalmem1对应主设备号globalmem_major,次设备号1。分别读写/dev/globalmem0/dev/globalmem1,发现都可以读写到正确的对应设备。

.globalmem驱动在用户空间的验证

方法一

1.编译globalmem驱动后,得到globalmem.ko文件。运行"insmod globalmem.ko" 命令加载模块,通过"lsmod"命令,发现globalmem模块被加载。在通过“cat/proc/devices”命令查看。

2.接下来,通过"sudo mknod /dev/globalmem c 245 0"命令创建“/dev/gbobalmem”设备节点,并通过“echo hello world>/dev/globalmem”命令和“cat /dev/globalmem”命令分别验证设备的写和读。

3.cd /dev 里可以查看建立的设备节点

如果权限不够用sudo chmod 777 globalmem改权限

方法二

编写应用层程序进行验证

1.头文件

#include<stdio.h>

#include<fcntl.h>

2.

int main()

{

int myfile;

char buffer[100];

int retval;

myfile = open("/dev/global",O_RDWR);

if(myfile < 0){

printf("Open failed/n");

}

write(myfile,"hello,tiger",sizeof("hello,tiger"));

close(myfile);

myfile = open("/dev/global",O_RDWR);

if(myfile < 0){

printf("Open failed/n");

}

retval = read(myfile,buffer,100);

buffer[retval] = 0;

printf("Response:%s/n",buffer);

close(myfile);

}

分享到:
评论

相关推荐

    linux字符设备驱动实例

    本例子是一个linux字符设备驱动的最简单的例子,有详细的说明,适合初次接触者。

    Linux字符设备驱动总结

    linux 字符设备驱动 字符设备是指在I/O传输过程中以字符为单位进行传输的设备,例如键盘,打印机等。请注意,以字符为单位并不一定意味着是以字节为单位,因为有的编码规则规定,1个字符占16比特,合2个字节。  在...

    嵌入式Linux字符设备驱动的设计与应用

    嵌入式Linux字符设备驱动的设计与应用 嵌入式Linux字符设备驱动的设计与应用

    Linux下支持阻塞操作的字符设备驱动

    Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动...

    深入浅出 Linux字符设备驱动程序解析

    深入浅出 Linux字符设备驱动程序解析

    Linux字符设备驱动实现

    编写一个字符设备驱动,并利用对字符设备的同步操作,设计实现一个聊天程序。可以有一个读,一个写进程共享该字符设备,进行聊天;也可以由多个读和多个写进程共享该字符设备,进行聊天

    Linux字符设备驱动实验代码

    简单的字符设备的驱动程序,并对所编写的设备驱动程序进行测试,了解Linux操作系统如何管理字符设备。由于网上许多资源不完整,本资源整合了许多内容。包括驱动程序memdev.c,memdev.h,app-mem.c,MakeFile文件。...

    linux字符设备驱动程序学习笔记

    详细介绍了linux字符设备驱动程序,对各个名词做了自己的理解,在学习中的笔记,有错误还请海涵

    linux字符设备驱动模型

    linux字符设备驱动模型

    基于Linux字符设备驱动程序的设计与实现

    Linux 设备驱动程序是为特定的硬件提供给用户程序的 一组标准化接口,它隐藏了设备工作的细节。Linux 系统下 驱动程序是运行在内核态的,是和...Linux 系统的设备分为3 种类型,分别是字符设备、 块设备和网络设备。

    最简单的linux字符设备驱动

    一个最简单的字符设备驱动程序,包括LDD第三版前三章的内容。 关键是书中并未讲的太细,关于mknod以及如何自己写一个程序使用自己的驱动,我的代码中有详细的过程,也在blog中写明了驱动模块的思路以及常见问题的...

    Linux 字符设备驱动程序的设计

    介绍了Linux 字符设备驱动程序中建立设备,初始化设备、设备的资源分配和如 何访问设备的方法及相关函数的实现.

    嵌入式Linux下字符型设备驱动程序的开发

    嵌入式Linux下字符型设备驱动程序的开发,驱动开发入门首选~

    Linux字符设备驱动(转载)

    概括的说,字符设备驱动主要要做三件事:1、定义一个结构体static struct file_operations变量,其内定义一些设备的打开、关闭、读、写、控制函数;2、在结构体外分别实现结构体中定义的这些函数;3、向内核中注册或...

    linux字符设备驱动

    1)编写一个简单的字符设备驱动程序,该字符设备包括打开、读、写、I/O控制与释放五个基本操作。 2)编写一个测试程序,测试字符设备驱动程序的正确性。 3)要求在实验报告中列出Linux内核的版本与内核加载的过程

    Linux增加字符设备驱动实验

    Linux增加字符设备驱动实验,可以打印出一个hello world

    Linux字符设备驱动架构分析

    Linux字符设备驱动架构分析,Linux字符设备驱动架构分析,Linux字符设备驱动架构分析

    操作系统课程设计—linux字符设备驱动程序

    这是linux下的字符设备驱动程序,对于初学驱动程序的人有很不错的参考价值。

    linux 字符设备驱动程序 示例代码

    linux字符设备驱动程序,示例代码。 共8个文件。包括内核态的驱动程序和用户态的测试例程。

Global site tag (gtag.js) - Google Analytics