Linux网管日记(20)学习Linux设备开发的趣闻轶事

众所周知,Linux已经成功应用于服务器和桌面系统,也许,大家手头用的一些小型设备就有Linux的相关应用。随着嵌入式系统应用的持续升温,Linux也开始广泛应用于嵌入式领域,逐步成为通信、工业控制、消费电子等领域的主流操作系统。Linux正以其独特的优势极大地吸引电子设计工程师,Linux在嵌入式系统中的占有率与日俱增。全世界有无数的嵌入式产品正使用Linux作为其操作系统,在这些采用Linux作为操作系统的设备中,无一例外都包含着多个Linux设备驱动,没有这些设备驱动,用户便无法享受Linux上诸多精彩纷呈的应用。

在Linux/UNIX系统中,通常会将设备当作文件来处理。本能的,我们会想到如何创建设备文件,以及如何对设备文件进行读写。

1.设备文件的来龙去脉

Linux设备驱动的开发需要牢固的硬件基础,并需要对驱动中所涉及的Linux内核知识有良好的掌握,具体表现在:
    (1)驱动直接与硬件打交道,在编写某类硬件设备的驱动时,我们必须对该驱动涉及到的硬件的工作原理和接口有清楚的掌握,因为许多时候,我们需要直接操作寄存器、控制中断和DMA。
    (2)编写Linux设备驱动涉及到许多Linux内核的API,会大量使用自旋锁、信号量、等待队列、tasklet、内存与I/O访问,如果对内核中的相关API了解不够充分,很难写出高质量的驱动。

Linux系统中有一个抽象化的设备目录,文件名为“/dev”。该目录下存有指向系统中硬件的特殊文件。这些指向硬件设备的文件,极大地简化了程序员对硬件的操作。这样,就像日常报号的对号入座一样,程序员就可以像访问普通文件一样来访问硬件,而无需使用特殊的接口函数。

1

Linux系统将设备分成了3种类型:字符设备、块设备和网络接口
 
(1)字符设备:字符设备就是能够像字节流一样访问的设备,字符终端和串口就属于字符设备。
 
(2)块设备:块设备上可以容纳文件系统。与字符设备不同,在读写操作时,块设备每次只能传输一个或多个完整的块。在Linux操作系统中,应用程序可以像访问字符设备一样读写块设备(一次读取或写入任意的字节数据)。因此,块设备和字符设备的区别仅仅是在内核中对于数据的管理不同。

如果该设备是字符设备的话,将以字符“c”开头。如果是块设备的话,将以字符“b”开头。
 
(3)网络接口:网络接口存在一定的特殊性,其与内核的通信完全不同于内核与字符设备或块设备的通信。网络接口可以是一个硬件设备,也可以是一个软件设备。

在Linux系统启动时,设备驱动将被加载。设备驱动成功加载后,将向系统反馈一个主设备号,驱动程序将根据该主设备号在/dev目录下创建对应的设备文件。

 

2.设备在linux中如何描述

在/dev目录下存放有系统支持的所有设备。设备又可以成为设备节点,如果往系统中添加新设备,必须在/dev下创建相应的节点。一个设备驱动程序往往可以驱动若干设备,设备通过主设备号和次设备号区分。同一主设备号的所有设备使用一个驱动程序。次设备号为驱动程序提供了如何区分不同设备的入口。有些情况下还可以根据次设备号的高低4位区分设备的工作类型。举个例子以hda0-hda4和hdb0-hdb4的关系,hda代表第一块硬盘,hdb代表第二块硬盘,由于大家都是硬盘因此此用同一驱动程序,所以had和hdb的主设备号一致。Hda后面的序号分别代表了该硬盘上的分区信息,为了有效的区分这些分区,次设备的设定做了分类,该字节的高四位代表不同的硬盘的,而低4位代表分区索引。

 

3.我们所熟悉的终端

传统终端指的是计算机外部的设备,而现在终端为显示器、控制器及键盘等设备的总称。终端与平常使用的计算机的根本区别在于:没有自己的CPU单元和内存单元。

终端的主要功能是发送用户输入的请求给主机,并显示主机完成运算后的结果。随着计算机技术的发展,目前终端有了新的含义。一切接入网络的计算机设备称为终端,如电脑、PDA、智能电话、数字电视等。

通过stty命令可以直接修改和查询终端驱动程序的设置。

有时候运行一些程序,需要修改终端的设置。对于用户而言,这些修改往往是不可见的,因为程序在退出后,会将终端设置为原有的情况。但是,如果出现程序崩溃或被kill命令杀死的情况,有可能来不及将终端设置为原有的参数。这时,终端就有可能出现异常的情况,有可能不能正常处理换行字符,或不能恰当地显示输入的字符,甚至可能不能正确执行命令。

Linux系统中提供了ttyname函数,用于判断某个打开的文件描述符是否是某个终端设备。如果是,则返回终端设备的名称。

在下面的程序中,通过使用ttyname函数获得了标准输入、标准输出和标准错误输出的终端名称。具体代码如下:

------------------------------------------------------------    

// 获得标准输入、输出和错误输出的终端名称
#include <stdio.h>
#include <unistd.h>

int main(int argc,char* argv[]){
//下面3个字符指针分别用来保存标准输入、标准输出和
//标准错误输出的终端名
char* tty_out_name;
char* tty_in_name;
char* tty_err_name;

//调用ttyname函数来获得终端名称,如果返回结果为NULL,表示
//调用失败,输出错误信息
if((tty_out_name=ttyname(STDOUT_FILENO))==NULL){
perror("Cannot get tty name");
return 1;
}

//打印输出获得的终端名称
printf("STDOUT_FILENO tty name is :%s/n",tty_out_name);

if((tty_in_name=ttyname(STDIN_FILENO))==NULL){
perror("Cannot get tty name");
return 1;
}

printf("STDIN_FILENO tty name is :%s/n",tty_in_name);

if((tty_err_name=ttyname(STDERR_FILENO))==NULL){
perror("Cannot get tty name");
return 1;
}

printf("STDERR_FILENO tty name is :%s/n",tty_err_name);
return 0;
}

------------------------------------------------------------

使用gcc编译该程序,获得可执行程序。执行该程序,对比who命令的输出结果,发现标准输入、标准输出和标准错误输出的终端都被设置为了pts/6。具体过程如下:

------------------------------------------------------------
[program@localhost charter6]$ gcc -o p6.1 p6.1.c 
[program@localhost charter6]$ ./p6.1
STDOUT_FILENO tty name is :/dev/pts/6
STDIN_FILENO tty name is :/dev/pts/6
STDERR_FILENO tty name is :/dev/pts/6
[program@localhost charter6]$ who
root     pts/3        2009-10-07 21:25 (:0.0)
program  pts/6        2009-10-07 22:57 (192.168.106.1)
------------------------------------------------------------

4.一点学习体会

近年来内核在驱动方面更偏向于提供设备驱动的架构(Framework)而非单个设备驱动,考虑到框架更强的兼容性,字符设备、块设备、网络设备、MTD设备、TTY设备、I2C设备、LCD设备、音频设备、摄像头、USB设备、PCI设备等驱动的体系结构都变得愈发复杂。下面是一点小体会:

(1)开发驱动程序需要对内核有一定的了解,但并不是非要解读的多么透彻。

(2)一个等式。还是用这个比较流行的等式来总结一下学习体会:

Linux设备驱动开发=硬件控制+Linux内核API+驱动框架

其中,Linux内核API用于:并发/同步控制、阻塞/唤醒、中断底半部调度、内存和I/O访问等。

(3)设备驱动程序通常分为字符设备和块设备,这是泛指分类。区别就是有缓冲区的就是块设备,无缓冲区的就是字符设备。但是在linux源码的drivers目录下,不仅仅有block和CHAR目录,还有其他的各种设备,那是因为有些开发人员为了方便并没有根据这种分类而是采用了更为直观的功能分类。比如sound目录,大家一看就知道里面存放的是各种音频驱动了。

(4)开发linux下驱动程序与使用的发行版本无关,不管你用redhat或mandrake等,都无所谓。真正影响你的是你当前所用的内核的版本。如果你打算在目标板上用2.4.x的内核,而你的主机上的linux用的却是2.6.x的系统,你的开发将是件麻烦的事。

(5)内核引用。开发驱动过程中,由于采用的是内核引用,在程序编译时是不需要链接到库文件的。因此lib路径对我们就没什么用了。但是由于需要引用内核提供的各种数据结构和接口,必须设置好相应版本的include路径,通常在/usr/include。在/usr/include下有好多头文件,真正我们需要的只有/usr/include/linux目录和/usr/include/asm目录。驱动程序设计中有两个函数和三个数据结构最重要。Init_module和cleanup_module这两个函数。File_operations,inode,file这三个数据结构,在linux/fs.h中定义。至于其他的内存操作,如i/o操作、定时器、中断、DMA等待等,就是提升部分了。

等我再继续研究,再和大家分享吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值