`

Linux下的C编程实战之(五)

阅读更多

Linux下的C编程实战之(五) 

1.引言


设备驱动程序是操作系统内核和机器硬件之间的接口,它为应用程序屏蔽硬件的细节,一般来说,Linux的设备驱动程序需要完成如下功能:


(1)初始化设备;


(2)提供各类设备服务;


(3)负责内核和设备之间的数据交换;


(4)检测和处理设备工作过程中出现的错误。


妙不可言的是,Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得Windows的设备操作犹如文件一般。在应用程 序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。本系列文章的第2章文件系统编程中已经看到了这些函数的真面目, 它们就是open ()、close ()、read ()、write () 等。


Linux主要将设备分为二类:字符设备和块设备(当然网络设备及USB等其它设备的驱动编写方法又稍有不同)。这两类设备的不同点在于:在对字符设 备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,而块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回 请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备主要针对磁盘等慢速设备。以字符设备的驱动较为简单,因此本章主要阐述字符设备的驱 动编写。


2.驱动模块函数


init 函数用来完成对所控设备的初始化工作,并调用register_chrdev() 函数注册字符设备。假设有一字符设备“exampledev”,则其init 函数为:

void exampledev_init(void)
{
 if (register_chrdev(MAJOR_NUM, " exampledev ", &exampledev_fops))
TRACE_TXT("Device exampledev driver registered error");
 else
TRACE_TXT("Device exampledev driver registered successfully");
…//设备初始化
}
 


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


file_operations结构体定义为:

struct file_operations
{
 int (*lseek)();
 int (*read)();
 int (*write)();
 int (*readdir)();
 int (*select)();
 int (*ioctl)();
 int (*mmap)();
 int (*open)();
 void(*release)();
 int (*fsync)();
 int (*fasync)();
 int (*check_media_change)();
 void(*revalidate)();
};
 


大多数的驱动程序只是利用了其中的一部分,对于驱动程序中无需提供的功能,只需要把相应位置的值设为NULL。对于字符设备来说,要提供的主要入口有:open ()、release ()、read ()、write ()、ioctl ()。


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

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


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


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

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


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

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

void read(struct inode * inode ,struct file * file ,char * buf ,int count) ;


参数buf是指向用户空间缓冲区的指针,由用户进程给出,count 为用户进程要求读取的字节数,也由用户给出。


read() 函数的功能就是从硬设备或内核内存中读取或复制count个字节到buf 指定的缓冲区中。在复制数据时要注意,驱动程序运行在内核中,而buf指定的缓冲区在用户内存区中,是不能直接在内核中访问使用的,因此,必须使用特殊的 复制函数来完成复制工作,这些函数在<asm/ segment.h>中定义:

void put_user_byte (char data_byte ,char * u_addr) ;
void put_user_word (short data_word ,short * u_addr) ;
void put_user_long(long data_long ,long * u_addr) ;
void memcpy_tofs (void * u_addr ,void * k_addr ,unsigned long cnt) ;
 


参数u_addr为用户空间地址,k_addr 为内核空间地址,cnt为字节数。


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

void write (struct inode * inode ,struct file * file ,char * buf ,int count) ;


write ()的功能是将参数buf 指定的缓冲区中的count 个字节内容复制到硬件或内核内存中,和read() 一样,复制工作也需要由特殊函数来完成:

unsigned char_get_user_byte (char * u_addr) ;
unsigned char_get_user_word (short * u_addr) ;
unsigned char_get_user_long(long * u_addr) ;
unsigned memcpy_fromfs(void * k_addr ,void * u_addr ,unsigned long cnt) ;
 


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

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


参数cmd为设备驱动程序要执行的命令的代码,由用户自定义,参数arg 为相应的命令提供参数,类型可以是整型、指针等。


同样,在驱动程序中,这些函数的定义也必须符合命名规则,按照本文约定,设备“exampledev”的驱动程序的这些函数应分别命名为 exampledev_open、exampledev_ release、exampledev_read、exampledev_write、exampledev_ioctl,因此设备 “exampledev”的基本入口点结构变量exampledev_fops 赋值如下:

struct file_operations exampledev_fops {
 NULL ,
 exampledev_read ,
 exampledev_write ,
 NULL ,
 NULL ,
 exampledev_ioctl ,
 NULL ,
 exampledev_open ,
 exampledev_release ,
 NULL ,
 NULL ,
 NULL ,
 NULL
} ;
 

3.内存分配


由于Linux驱动程序在内核中运行,因此在设备驱动程序需要申请/释放内存时,不能使用用户级的malloc/free函数,而需由内核级的函数kmalloc/kfree () 来实现,kmalloc()函数的原型为:

void kmalloc (size_t size ,int priority);


   参数size为申请分配内存的字节数;参数priority说明若kmalloc()不能马上分配内存时用户进程要采用的动作:GFP_KERNEL 表示等待,即等kmalloc()函数将一些内存安排到交换区来满足你的内存需要,GFP_ATOMIC 表示不等待,如不能立即分配到内存则返回0 值;函数的返回值指向已分配内存的起始地址,出错时,返回0。


kmalloc ()分配的内存需用kfree()函数来释放,kfree ()被定义为:

# define kfree (n) kfree_s( (n) ,0)


其中kfree_s () 函数原型为:

void kfree_s (void * ptr ,int size);


参数ptr为kmalloc()返回的已分配内存的指针,size是要释放内存的字节数,若为0 时,由内核自动确定内存的大小。


4.中断


许多设备涉及到中断操作,因此,在这样的设备的驱动程序中需要对硬件产生的中断请求提供中断服务程序。与注册基本入口点一样,驱动程序也要请求内核将特定的中断请求和中断服务程序联系在一起。在Linux中,用request_irq()函数来实现请求:

int request_irq (unsigned int irq ,void( * handler) int ,unsigned long type ,char * name);


   参数irq为要中断请求号,参数handler为指向中断服务程序的指针,参数type 用来确定是正常中断还是快速中断(正常中断指中断服务子程序返回后,内核可以执行调度程序来确定将运行哪一个进程;而快速中断是指中断服务子程序返回后, 立即执行被中断程序,正常中断type 取值为0 ,快速中断type 取值为SA_INTERRUPT),参数name是设备驱动程序的名称。


5.实例


笔者最近设计了一块采用三星S3C2410 ARM处理器的电路板(ARM处理器广泛应用于手机、PDA等嵌入式系统),板上包含四个用户可编程的发光二极管(LED),这些LED连接在ARM处理 器的可编程I/O口(GPIO)上。下图给出了ARM中央处理器与LED的连接原理:

 

我们在ARM处理器上移植Linux操作系统,现在来编写这些LED的驱动:

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <asm/hardware.h>
#define DEVICE_NAME "leds" /*定义led 设备的名字*/
#define LED_MAJOR 231 /*定义led 设备的主设备号*/
static unsigned long led_table[] =
{
 /*I/O 方式led 设备对应的硬件资源*/
 GPIO_B10, GPIO_B8, GPIO_B5, GPIO_B6,
};
/*使用ioctl 控制led*/
static int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
 switch (cmd)
 {
case 0:
case 1:
 if (arg > 4)
 {
return -EINVAL;
 }
 write_gpio_bit(led_table[arg], !cmd);
default:
 return -EINVAL;
 }
} 
static struct file_operations leds_fops =
{
 owner: THIS_MODULE, ioctl: leds_ioctl,
};
static devfs_handle_t devfs_handle;
static int __init leds_init(void)
{
 int ret;
 int i;
 /*在内核中注册设备*/
 ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &leds_fops);
 if (ret < 0)
 {
printk(DEVICE_NAME " can't register major number\n");
return ret;
 }
 devfs_handle = devfs_register(NULL, DEVICE_NAME, DEVFS_FL_DEFAULT, LED_MAJOR,
0, S_IFCHR | S_IRUSR | S_IWUSR, &leds_fops, NULL);
 /*使用宏进行端口初始化,set_gpio_ctrl 和write_gpio_bit 均为宏定义*/
 for (i = 0; i < 8; i++)
 {
set_gpio_ctrl(led_table[i] | GPIO_PULLUP_EN | GPIO_MODE_OUT);
write_gpio_bit(led_table[i], 1);
 }
 printk(DEVICE_NAME " initialized\n");
 return 0;
}

static void __exit leds_exit(void)
{
 devfs_unregister(devfs_handle);
 unregister_chrdev(LED_MAJOR, DEVICE_NAME);
}

module_init(leds_init);
module_exit(leds_exit);
 


使用命令方式编译led 驱动模块:

#arm-linux-gcc -D__KERNEL__ -I/arm/kernel/include

-DKBUILD_BASENAME=leds -DMODULE -c -o leds.o leds.c


以上命令将生成leds.o 文件,把该文件复制到板子的/lib目录下,使用以下命令就可以安装leds驱动模块:

#insmod /lib/ leds.o


删除该模块的命令是:

#rmmod leds


6.小结


本章讲述了Linux设备驱动程序的入口函数及驱动程序中的内存申请、中断等,并给出了一个通过ARM处理器的GPIO口控制LED的驱动实例。


分享到:
评论

相关推荐

    LinuxC编程实战电子书

     《LinuxC编程实战》系统地介绍了在Linux平台下用C语言进行程序开发的过程,集趣味性、实战性 于一体的160多段代码实例,帮助读者快速掌握在Linux平台下进行C语言程序开发的方法和技巧,并通 过一个原创的BT软件...

    linux C编程实战

    第一篇 Linux和C编程基础  第1章 Linux系统概述  1.1 Linux操作系统介绍   1.1.1 Linux的发展历程   1.1.2 Linux的特性   1.1.3 Linux的内核版本和发行版本   1.2 C语言简介   1.2.1 C语言的...

    Linux.C编程实战.pdf

    从给定的文件信息来看,核心关注点在于“Linux.C编程实战.pdf”,这是一份关于在Linux环境下进行C语言编程的实战指南。虽然文件描述部分包含了大量的广告信息和社区介绍,但我们的重点将放在与标题和描述相关的知识...

    Linux下的C编程实战.pdf

    本书详细介绍了在Linux环境下进行C语言编程的全过程,包括开发环境搭建、程序编译、调试等关键环节,为读者提供了一条清晰的Linux C编程学习路径。 首先,作者在书中指出,Linux在当前的软件开发领域中扮演着关键...

    linux+C编程实战

    "Linux+C编程实战"这个主题深入探讨了如何在Linux操作系统下进行C语言的编程实践,这对于系统级编程、嵌入式开发以及软件工程等领域至关重要。 首先,Linux是一个开源的类UNIX操作系统,它的内核由Linus Torvalds...

    Linux C语言编程实战 书附源代码

    《Linux C语言编程实战》这本书旨在帮助读者深入理解这两种技术,并通过实践来提升技能。源代码是学习过程中不可或缺的一部分,因为它使读者能够直接查看和运行程序,从而加深对概念的理解。 首先,我们要了解Linux...

    Linux C编程实战

    Linux C编程实战,Linux C学习,董永清 著,人民邮电出版社

    linux c编程实战附带光盘

    《Linux C编程实战》是董永清先生撰写的一本深入探讨Linux环境下C语言编程的书籍。这本书籍旨在帮助读者掌握在Linux系统上进行C语言编程的基础知识和高级技巧,为软件开发人员提供实用的指导。光盘附带的资料包含了...

    Linux C编程实战 光盘

    "Linux C编程实战"是由董永清编著的一本书,其目的是帮助读者掌握在Linux环境下使用C语言进行程序开发的技巧和实践。光盘内容包含了书中的源代码示例,以及额外的文档,如Linux安装指南和BT下载软件的开发过程。 ...

    linux C编程实战光盘

    Linux C编程实战光盘的内容涵盖了Linux操作系统环境下的C语言编程技术,这是一本结合理论与实践的书籍。在Linux系统中,C语言是基础且强大的编程工具,它提供了低级别的系统访问权限,使得开发者能够创建高效、可靠...

    linuxC编程实战

    ### Linux C编程实战:开发平台搭建与编程技巧详解 #### 开发平台搭建 在Linux操作系统中,C语言编程因其高效性、灵活性以及广泛的系统级控制能力而受到开发者的青睐。对于初学者而言,搭建一个合适的开发环境至关...

    Linux C编程实战(1CD)

    《Linux C编程实战》是一本针对Linux平台下C语言编程的实战指南,由童永清编著,由人民邮电出版社于2008年3月1日出版。这本书旨在帮助读者掌握Linux环境下的C语言编程技能,包括基础概念、系统编程、编程工具的使用...

    Linux+C编程实战(光盘)

    本资源为Linux+C编程实战书的附带光盘,含书中的全部源码

    linux C编程实战.pdf

    linux C编程实战.pdf

    linux C编程实战_linux编程_

    在深入探讨“Linux C编程实战”这一主题时,我们首先要理解Linux操作系统的基础和C语言的编程原理。Linux是一个开源的、类Unix的操作系统,而C语言则是一种强大的、低级的编程语言,常用于系统级编程,如操作系统、...

    Linux C编程实战 源代码

    Linux C编程实战 源代码 学习Linux平台C编程的优秀的实例代码。

    linux c编程实战

    linux c编程实战,完整版。介绍详细,适合学linux c的新手

Global site tag (gtag.js) - Google Analytics