首页 | 资讯动态 | linux基础 | 系统管理 | 网络管理 | 编程开发 | linux数据库 | 服务器技术 | linux相关 | linux认证 | 嵌入式 | 下载中心 | 专题 | linux招聘 | 镜像站
OKLinux中文技术站
·设为首页
·加入收藏
·联系我们
系统管理: 中文环境 系统管理 桌面应用 内核技术 | Linux基础: 基础入门 安装配置 常用命令 经验技巧 软件应用 | Linux数据库: Mysql Postgre Oracle DB2 Sybase other
网络管理: 网络安全 网络应用 Linux服务器 环境配置 黑客安全 | 编程开发: PHP CC++ Python Perl Shell 嵌入式开发 java jsp | PHP技术: PHP基础 PHP技巧 PHP应用 PHP文摘
Linux资讯 Linux招聘 Linux专题 Apache | Linux相关: 硬件相关 Linux解决方案 Linux认证 企业应用 其它Unix | 相关下载: 资料下载 参考手册 开发工具 服务器类 软路由 其它
 技术搜索:
会员中心 注册会员 高级搜索  
  → 当前位置:首页>linux基础>安装配置>正文

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

http://www.oklinux.cn  2007-04-28  来源:    会员收藏  游客收藏  【 】 

Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得linux的设备操作犹如文件一般。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open()、close()、read()、write() 等。

 

Linux主要将设备分为二类:字符设备和块设备。字符设备是指设备发送和接收数据以字符的形式进行;而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。

 

下面我们来假设一个非常简单的虚拟字符设备:这个设备中只有一个4个字节的全局变量int global_var,而这个设备的名字叫做"globalvar"。对"globalvar"设备的读写等操作即是对其中全局变量global_var的操作。

 

驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对所控设备的初始化工作,并调用register_chrdev() 函数注册字符设备:

 

 

static int __init globalvar_init(void)
{
  if (register_chrdev(MAJOR_NUM, " globalvar ", &gobalvar_fops))

 {

  //…注册失败

 }

 else

 {

  //…注册成功

 }
}

 

其中,register_chrdev函数中的参数MAJOR_NUM为主设备号, "globalvar"为设备名,globalvar_fops为包含基本函数入口点的结构体,类型为file_operations。当globalvar模块被加载时,globalvar_init被执行,它将调用内核函数register_chrdev,把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执行系统调用时提供入口地址。

 

与模块初始化函数对应的就是模块卸载函数,需要调用register_chrdev()的"反函数"

 

 

unregister_chrdev():

static void __exit globalvar_exit(void)

{

 if (unregister_chrdev(MAJOR_NUM, " globalvar "))

 {

  //…卸载失败

 }

 else

 {

  //…卸载成功

 }

}

 

随着内核不断增加新的功能,file_operations结构体已逐渐变得越来越大,但是大多数的驱动程序只是利用了其中的一部分。对于字符设备来说,要提供的主要入口有:open()、release()、read()、write()、ioctl()、llseek()、poll()等。

 

open()函数 对设备特殊文件进行open()系统调用时,将调用驱动程序的open() 函数:

 

int (*open)(struct inode * ,struct file *);

 

其中参数inode为设备特殊文件的inode (索引结点) 结构的指针,参数file是指向这一设备的文件结构的指针。open()的主要任务是确定硬件处在就绪状态、验证次设备号的合法性(次设备号可以用 MINOR(inode-> i - rdev) 取得)、控制使用设备的进程数、根据执行情况返回状态码(0表示成功,负数表示存在错误)等;

 

release()函数 当最后一个打开设备的用户进程执行close ()系统调用时,内核将调用驱动程序的release() 函数:

 

void (*release) (struct inode * ,struct file *) ;

 

release 函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。

 

read()函数 当对设备特殊文件进行read() 系统调用时,将调用驱动程序read()函数:

 

ssize_t (*read) (struct file *, char *, size_t, loff_t *);

 

用来从设备中读取数据。当该函数指针被赋为NULL 值时,将导致read 系统调用出错并返回-EINVAL("Invalid argument,非法参数")。函数返回非负值表示成功读取的字节数(返回值为"signed size"数据类型,通常就是目标平台上的固有整数类型)。

 

globalvar_read函数中内核空间与用户空间的内存交互需要借助第2节所介绍的函数:

 

 

static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)

{

 …

 copy_to_user(buf, &global_var, sizeof(int));

 …

}

 

write( ) 函数 当设备特殊文件进行write () 系统调用时,将调用驱动程序的write () 函数:

 

ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

 

向设备发送数据。如果没有这个函数,write 系统调用会向调用程序返回一个-EINVAL。如果返回值非负,则表示成功写入的字节数。

 

globalvar_write函数中内核空间与用户空间的内存交互需要借助第2节所介绍的函数:

 

 

static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)

{

…

copy_from_user(&global_var, buf, sizeof(int));

…

}

 

ioctl() 函数 该函数是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息,函数原型为:

 

int (*ioctl) (struct inode * ,struct file * ,unsigned int ,unsigned long);

 

unsigned int参数为设备驱动程序要执行的命令的代码,由用户自定义,unsigned long参数为相应的命令提供参数,类型可以是整型、指针等。如果设备不提供ioctl 入口点,则对于任何内核未预先定义的请求,ioctl 系统调用将返回错误(-ENOTTY,"No such ioctl fordevice,该设备无此ioctl 命令")。如果该设备方法返回一个非负值,那么该值会被返回给调用程序以表示调用成功。

 

llseek()函数 该函数用来修改文件的当前读写位置,并将新位置作为(正的)返回值返回,原型为:

 

loff_t (*llseek) (struct file *, loff_t, int);

 

poll()函数 poll 方法是poll 和select 这两个系统调用的后端实现,用来查询设备是否可读或可写,或是否处于某种特殊状态,原型为:

 

unsigned int (*poll) (struct file *, struct poll_table_struct *);

 

我们将在"设备的阻塞与非阻塞操作"一节对该函数进行更深入的介绍。

 

 设备"gobalvar"的驱动程序的这些函数应分别命名为gobalvar_open、 gobalvar_ release、gobalvar_read、gobalvar_write、gobalvar_ioctl,因此设备"gobalvar"的基本入口点结构变量gobalvar_fops 赋值如下:

 

 

struct file_operations gobalvar_fops = {

 read: gobalvar_read,

 write: gobalvar_write,

};

 

  上述代码中对gobalvar_fops的初始化方法并不是标准C所支持的,属于GNU扩展语法。

 

  完整的globalvar.c文件源代码如下:

 

 

#include <linux/module.h>

#include <linux/init.h>

#include <linux/fs.h>

#include <asm/uaccess.h> 

MODULE_LICENSE("GPL");

#define MAJOR_NUM 254 //主设备号

static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);

static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);

//初始化字符设备驱动的file_operations结构体

struct file_operations globalvar_fops =

{

 read: globalvar_read, write: globalvar_write,

};

static int global_var = 0; //"globalvar"设备的全局变量

static int __init globalvar_init(void)

{

 int ret;

 //注册设备驱动

 ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);

 if (ret)

 {

  printk("globalvar register failure");

 }

 else

 {

  printk("globalvar register success");

 }

 return ret;

}

static void __exit globalvar_exit(void)

{

 int ret;

 //注销设备驱动

 ret = unregister_chrdev(MAJOR_NUM, "globalvar");

 if (ret)

 {

  printk("globalvar unregister failure");

 }

 else

 {

  printk("globalvar unregister success");

 }

}

static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)

{

 //将global_var从内核空间复制到用户空间

 if (copy_to_user(buf, &global_var, sizeof(int)))

 {

  return - EFAULT;

 } 

 return sizeof(int);

}

static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)

{

 //将用户空间的数据复制到内核空间的global_var

 if (copy_from_user(&global_var, buf, sizeof(int)))

 {

  return - EFAULT;

 } 

 return sizeof(int);

}

module_init(globalvar_init);

module_exit(globalvar_exit); 

  运行:

gcc -D__KERNEL__ -DMODULE -DLINUX -I /usr/local/src/linux2.4/include -c -o globalvar.o globalvar.c 

  编译代码,运行:

inmod globalvar.o 

  加载globalvar模块,再运行:

cat /proc/devices 

  发现其中多出了"254 globalvar"一行,如下图:

 

  接着我们可以运行:

mknod /dev/globalvar c 254 0 

  创建设备节点,用户进程通过/dev/globalvar这个路径就可以访问到这个全局变量虚拟设备了。

我们写一个用户态的程序globalvartest.c来验证上述设备:

#include <sys/types.h>

#include <sys/stat.h>

#include <stdio.h>

#include <fcntl.h>

main()

{

 int fd, num;

 //打开"/dev/globalvar"

 fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);

 if (fd != -1 )

 {

  //初次读globalvar

  read(fd, &num, sizeof(int));

  printf("The globalvar is %d\n", num);

  //写globalvar

  printf("Please input the num written to globalvar\n");

  scanf("%d", &num);

  write(fd, &num, sizeof(int));

  //再次读globalvar

  read(fd, &num, sizeof(int));

  printf("The globalvar is %d\n", num);

  //关闭"/dev/globalvar"

  close(fd);

 }

 else

 {

  printf("Device open failure\n");

 }

} 

  编译上述文件:

gcc -o globalvartest.o globalvartest.c 

  运行

./globalvartest.o 

  可以发现"globalvar"设备可以正确的读写。

上一篇:轻松实现 Linux系统下互联网过滤功能   下一篇:菜鸟乐园 Linux系统各项开机服务介绍


收藏于收藏夹】 【评论】 【推荐】 【打印】 【关闭
相关文档
·菜鸟乐园 Linux系统各项开机服务介绍
·新手看招 Linux下RPM软件的安装技巧
·通过PXE远程安装Linux系统全程解析
·精彩全记录 推荐100个最佳的Linux站点
·让三种系统在服务器上共“舞”
·安装Linux应用软件的基本方法
·Samba服务典型的三种共享配置
·安全攻略 SSH工具SCP使用示例详细解析
·配置文件/etc/syslog.conf的实例解析
·在ThinkPad上安装Ubuntu的过程详解
·linux的运行模式:Runlevel详细解析
·Linux系统启动引导程序配置文件解析
·Linux操作系统文件系统的比较和选择
·深入浅出 Linux设备驱动阻塞与非阻塞
·深入浅出 Linux设备驱动异步通知介绍
·深入浅出 Linux设备驱动中断处理介绍
发表评论
密码: 匿名评论
评论内容:

(不超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规)
 
  最新文档
·系统管理:如何禁止RHEL AS4的图形启动
·Windows和Linux Fedora8双系统安装过程
·Linux通过ndiswrapper安装无线网卡驱动
·Linux系统利用Crontab命令实现定时重启
·Ubuntu Linux系统设定服务自行开机启动
·在Linux系统下查看端口的使用情况及启
·Linux系统下安装Perl及Perl模块方法
·Qtopia应用程序与Linux内核数据通信介
·Ubuntu下NVIDIA驱动安装及3D桌面的设置
·修改VMware下Ubuntu界面分辨率过高问题
·在Linux系统下如何调整文件打开方式
·如何在Linux系统下更改文件使用权限
  阅读排行
·Redhat5 Server下载、序列号以及相关介
·使用源代码将 Glibc 升级到 2.6
· 在U盘上安装Damn Small Linux
·Linux下推荐的常用应用程序列表
·Linux的GRUB引导程序配置方法大全
·新手学堂 详细讲解Ubuntu Server安装过
·从硬盘安装RHEL 5(Red Hat Enterprise
·新手入门 Fedora Linux 7系统的安装指
·使用 ActiveScaffold 增强 Ruby on Rai
·RedHat AS 4.0 硬盘安装图解一
·什么是Linux?Linux与UNIX之间的关系
·Linux不是Windows
·Intel 845集成显示驱动
·简介 PLAYSTATION 3 上的 Linux
·安装大型Linux 集群: 配置服务和安装
网摘收藏: