Category: Linux Device Driver

Sysfs文件系统(一):从serio virtual bus开始说起

seriobus

serio是一种虚拟总线,先上一副图 大概就是这样的,驱动程序注册一个serio port; 当接受硬件传上来的数据的时候通过serio port将数据传递给在总线另外一端的serio driver。当然由于这种结构的出现也就使得一个serio driver可以对应多个serio port(serio driver只是一个数据处理的程序罢了)。 bus的种类很多了,除了serio bus, 还有很多。 具体可以看下/sys/bus下面的文件夹,每一个文件夹的名字就是一种总线。随便打开一个文件夹里面都有两个子文件夹:devices和drivers。(在serio总线中serio port就是device,而serio driver就是driver)它们的架构都大同小异。  具体示例程序可以看Linux源代码中的/driver/input中的程序,都是以这种模式编写的。 同样的,这种模式还有一个至关重要的好处:就是对于硬件厂商来说,他们只需要提供某种类型总线的driver就好了,而这样的程序是非常好写的,只需要经过简单的培训,一个对Linux驱动程序没有一点了解的程序员都能够写出。 来看下device和driver是如何匹配的,我们用serio总线的代码来做一个示例看下。 static int serio_match_port(const struct serio_device_id *ids, struct serio *serio) {     while (ids->type || ids->proto) {         if ((ids->type == SERIO_ANY ||

read more

  • 13th Feb, 2010

Linux内核中的两个宏: likely & unlikely

可以在驱动中经常看到这样的代码: if(likely( x > 1)){       /* … */ } else {      /* … */ } 一开始不知道是做什么用的,后来问了海哥,他说是一个编译优化的宏,这东西表示likely()中的那个表达式成立的概率比较高,就会让cache中优先存放if之后的语句。 今天去查了下这两个宏的定义: #define likely(x)       __builtin_expect((x),1) #define unlikely(x)     __builtin_expect((x),0) __builtin_expect((x),1) 表示的是我们对x表达式成立的期望值是1. 这样编译器编译出汇编代码的时候就会把if下面的语句紧接着之前的代码. 而unlikely(x)则会把 else之后的代码紧接着之前的代码。这样cpu就会优先把那部分代码放到cache中去。

  • 5th Feb, 2010

Linux内核编程中的计时工具

内核中经常需要把握时间过了多长。这篇文章就是大概介绍一下内核中的计时方法。 jiffies, HZ Hz物理上的意义是频率的单位,在Linux内核中,HZ是系统计时器频率值的常量,即一秒钟系统计时器增加几次。 X86体系架构下,在2.4内核中HZ被默认的设置为100,2.6中是1000,但是2.6.13中却是250. ARM体系架构下,2.6内核中HZ被设置为100. jiffies是自开机以来系统计时器增加的总次数。所以jiffies的值是与HZ的值直接相关的。 HZ是多少,则jiffies的值每一秒就增加多少。比如说当HZ是100的时候,jiffies的值每一秒就增加100。 有一点注意:jiffies是一个32位无符号整型数。当HZ值是1000的时候,一秒钟增加1000则jiffies会在49.7天这样的时候就溢出了。而一台服务器开启的时间往往会远超于这个时间。所以内核提供了这样一个变量jiffies_64, 这是一个64位无符号整型数。这样就会处理很长很长的时间了。 注意:在32位机上,jiffies_64其实是由两个32位无符号整型数组合起来的,它的低32位部分就是jiffies,这样就造成了读取jiffies_64不是一个原子操作。所以在读取jiffies_64时建议不要直接使用jiffies_64, 而是使用get_jiffies_64()这样一个内核辅助函数。 现在来看在代码中如何使用这个计时工具。 下面的代码判断某一些工作是否在1秒钟之内完成: unsigned long timeout = jiffies + HZ; /* ..do something.. */ if(time_after(jiffies, timeout)) /*超过1秒钟*/ 下面的代码忙等1秒钟时间: unsigned long timeout = jiffies + HZ; while(time_before(jiffies, timeout)) continue; 这样的忙等法当然不好,看下面的代码会让出CPU1秒不执行自己的代码之后再回来继续自己的执行:

read more

  • 4th Feb, 2010

中断处理之三:阻塞型I/O

应用程序向硬件请求数据的时候,硬件那不一定有数据上来。怎么办呢? 简单的想法就是忙等。这样效率低,所以这里就介绍一下Linux中的阻塞型输入输出。 wait_queue_heat_t结构体 这个结构体是用来管理睡眠和唤醒的条件的。不同的等待事件我们要为其创建不同的等待队列。 定义等待队列方法如下: wait_queue_head_t sample_wait_queue;init_waitqueue_head(sample_wait_queue); 或者 DECLARE_WAIT_QUEUE_HEAD(sample_wait_queue); 如何使用? 使用方法比较简单,在先定义了等待队列之后。 当进程需要睡眠的时候(比如等待数据的到来)就调用 interruptible_sleep_on(&sample_wait_queue); 来使得当前进程睡眠; 当数据来的时候就调用 wake_up_interruptible(&sample_wait_queue); 唤醒进程。 这里有一个问题:要在哪里唤醒进程呢? 答案是:在中断处理函数中,在有数据来的时候,会发生中断,这时候中断处理函数就会被调用(当然你要申请过IRQ并且注册了该中断处理函数)。 所以就会经常看到这样结构的代码: /* 中断处理函数 */void XXX_interrupt(int irq, void *dev_id, struct pt_regs *regs){    /* … */    wake_up_interrupt(&waitqueue);     /* … */} /* 阻塞式读入

read more

  • 3rd Feb, 2010

中断处理之二: Top half & Bottom half

中断处理函数(interrupt handler)有一个要求就是要短小精悍,运行时间越短越好。因为在中断处理函数之前,会先关中断,那么在中断处理函数执行的这段时间内,所有发生的中断将都不能被处理。但是很多时候我们在中断处理中必须要做很多事情,这时候怎么办呢? 内核里面提供了bottom half这样的机制使得我们可以达到这样的目的。 Top half & Bottom half中断处理过程被分成了两个部分,top half会将所需要作的工作全部交给bottom half去做然后自己立刻返回。在内核中提供了下面三种机制可以让我们实现bottom half: softirqs, tasklets 和 work queues. Softirq是基础的bottom half机制, tasklet是建立在softirq之上的。这二者的主要区别在于Softirq是可重入的,而tasklet是不可重入的。 以下是示例代码 Softirq示例代码: void __init softirq_sample_init(){    /* … */        /*调用open_softirq打开自定义的SAMPLE_SOFT_IRQ软中断*/     open_softirq(SAMPLE_SOFT_IRQ, softirq_sample_bottom, NULL);     /*SAMPLE_SOFT_IRQ这个东西要先自己手动加到        include/linux/interrupt.h中的那个enum中去*/     /*这里参数的第二项就是bottom

read more

  • 2nd Feb, 2010

关于中断处理

当中断发生的时候,操作系统会先去查找中断向量表中该中断发生的时候的中断处理程序是什么。然后再执行这个中断处理例程。 现在来看一下在内核态下(我们主要说的是驱动程序)是如何申请中断的。 经常可以在open函数的实现中看见对request_irq这个函数的调用,其实这个就是在申请中断。 假设我们已经写好了中断处理函数如下: void interrupt_handler(int irq, void *dev_id, struct pt_regs *regs){    //…    return IRQ_HANDLED;} 我们可以在回调函数temp_open方法中调用中断处理注册如下: int temp_open(struct inode* inode, struct file *filp){    if(!request_irq(TEMP_IRQ, interrupt_handler,                     SA_INTERRUPT, “temp_chr”, NULL))    {        //…申请irq成功    }    return 0;} 这样我们的中断处理函数就注册成功了,当相应的中断发生的时候,中断处理函数就会被调用。

  • 1st Feb, 2010

Linux驱动程序中的I/O

在第一篇文章中只是介绍了下Linux驱动的整体结构,并没有对具体实现进行说明。 这篇文将简单介绍一下在实现Linux驱动程序中I/O操作的实现。 I/O操作最基本需要实现的函数有三个: read(), write(), ioctl(). read(), write()一个是从硬件读数据,一个是向硬件写数据。 来看怎么与硬件交互数据: 1. I/O port读写函数:inb, inw, inl, outb, outw, outl; 2. I/O memory读写函数:readb, readw, readl;writeb, writew, writel; 还是在I/O memory中的函数,这三个是一点小福利:memset_io, memcpy_fromio, memcpy_toio;这三个东西和memset,memcpy的用法基本完全一样。 ioctl()略,将会有一篇小文章专门说它~ User Space 与 Kernel Space的交互 请注意一下,我们写的这些函数的参数其实都是由用户程序给传过来的。 而我们现在是在内核态,内核态不能使用用户态的内存。 所以我们在写驱动程序的时候不能直接读写用户态的资源。 怎么办呢? copy_from_user() 与

read more

  • 29th Jan, 2010

Linux驱动程序架构(一)

linuxdd1

(转载请注明出处) 上一篇文章有说到用户程序调用C标准库函数的时候,会对应到驱动程序file_operations中函数指针所指向的一个回调函数上去。 现在来分析一下这个调用的过程(图片在墙外): 对于这图,我想要说的有: 1.struct file数据结构:        它与file_operations一样,也是在/include/linux/fs.h中定义的。这是一个内核数据结构,不会出现在用户态。         file结构其实代表的是一个被打开的文件,每一个被打开的文件在内核空间都有一个对应的file结构。在open一个文件的时候,这个结构会被创建。         struct file中有一个元素:struct file_operations *f_op;        在open设备文件的时候,这个f_op会被初始化为驱动程序中的file_operations。         这样,对设备文件的操作就被映射到驱动程序中的实现上去了。 2. VFS(Virtual File System) Layer        因为设备文件它并不是真正意义上的文件,只不过是起到了将system calls映射到驱动程序中的操作上罢了。所以我们把它叫做Virtual File.        (明晰一个概念:设备文件就是VFS)=====话说在linux下面有没有好用的画图工具啊?类似Visio那样的。上面那个图画的我辛苦啊。

  • 26th Jan, 2010

Linux驱动程序设计之入门篇

(转载请注明出处) 一. 明晰基础概念和工作方式 在说驱动之前要先明确一个概念,大家应该都知道,在linux中把一切东西都当作文件。 同样的,一个硬件也被当成一个文件,这些代表硬件的文件我们把它们叫做设备文件。 设备文件被统一的放在/dev路径下面。(用过mount命令的应该很清楚,mount就是将/dev下面的设备文件给挂载到我们的文件系统上) 写过程序的一般都很清楚,当我们打开普通文件之后可以读写,可以对文件作很多事情。 而现在我们面对的是一个设备文件,它代表的是一个硬件,那么读写之类各种操作是由什么东西来完成的呢? 答案是:驱动程序。 其实驱动程序在做什么事情呢?答案很简单,就是实现c语言中那些对文件的操作函数。当用户程序调用这些操作函数如open(),read(),write()等等普通的glibc中的函数时,会自动将操作映射到驱动程序中对这些操作的实现。以此来实现对硬件的读写操作。 用户态程序对设备文件的操作的例子就像这样: int fd;char buff[10];fd = open(“/dev/port”, O_RDWR);write(fd, buff, 1);close(fd); 大概代码就像这样。 二. 核心数据结构 现在要介绍一个在理解驱动程序设计架构时最重要的数据结构 – file_opeartions(是我自己以为的,不同意见可以说…嘿嘿) 我不直接说这个数据结构,我用一段最简单的驱动程序的代码来说明。 int xxx_open(struct inode *inode, struct file *filp){    //…} int xxx_release(struct inode *inode, struct file

read more

  • 26th Jan, 2010