现在的位置: 首页 > 综合 > 正文

ubuntu下2.6.21内核的驱动开发实例

2013年08月27日 ⁄ 综合 ⁄ 共 4597字 ⁄ 字号 评论关闭
 globalvar.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <asm/semaphore.h>
MODULE_LICENSE("GPL");

#define MAJOR_NUM 253

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*);

static struct file_operations globalvar_fops =
{
read: globalvar_read, write: globalvar_write,
};

static int global_var = 0;
static struct semaphore sem;
static wait_queue_head_t outq;
static int flag = 0;

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");
init_MUTEX(&sem);
init_waitqueue_head(&outq);
}
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)
{
//等待数据可获得
if(wait_event_interruptible(outq, flag != 0))
{
return - ERESTARTSYS;
}

if (down_interruptible(&sem))
{
return - ERESTARTSYS;
}

flag = 0;
if (copy_to_user(buf, &global_var, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
up(&sem);
return sizeof(int);
}

static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len,loff_t *off)
{
if (down_interruptible(&sem))
{
return - ERESTARTSYS;
}
if (copy_from_user(&global_var, buf, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
up(&sem);
flag = 1;
//通知数据可获得
wake_up_interruptible(&outq);
return sizeof(int);
}
module_init(globalvar_init);
module_exit(globalvar_exit);

Makefile:

obj-m += globalvar.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

make后生成.ko文件,insmod加载该模块,然后在/dev/目录下建立设备节点:mknod globalvar c 253 0

测试应用程序
read.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
main()
{
int fd, num;
fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
if (fd != - 1)
{
while (1)
{
read(fd, &num, sizeof(int)); //程序将阻塞在此语句,除非有针对globalvar的输入
printf("The globalvar is %d/n", num);
//如果输入是0,则退出
if (num == 0)
{
close(fd);
break;
}
}
}
else
{
printf("device open failure/n");
}
close(fd);
}

write.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
main()
{
int fd, num;
fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
if (fd != - 1)
{
while (1)
{
printf("Please input the globalvar:/n");
scanf("%d", &num);
write(fd, &num, sizeof(int));
//如果输入0,退出
if (num == 0)
{
close(fd);
break;
}
}
}
else
{
printf("device open failure/n");
}
close(fd);
}

关于主设备号和次设备号
linux下的设备在/dev/目录下一般会有相对应的节点,关于设备号有如下的言论:
主设备号被系统用来确定驱动程式,次设备号被驱动程式用来确定具体的设备。

像如下的两个字符设备节点:
crw-r--r--  1 root root 254, 0  Jan  9 13:14 /dev/nx_ids
crw-r--r--  1 root root 254, 99 Jan  9 13:14 /dev/nx_ips

他们的主设备号都是254,当用户代码打开这两个设备的时候,系统会定位到同一个驱动程式,并调用其open函数。那么驱动程式怎么知道用户打开的是哪一个设备呢?就是根据次设备号来判断的。

int my_drv_open(struct inode *inode, struct file *filp)
{
    if (0 == MINOR(inode->i_rdev))
    {
        //nx_ids
    }
    else if (99 == MINOR(inode->i_rdev))
    {
        //nx_ips
    }
    return 0;
}
在include/linux/kdev_t.h文档中能够看到操作设备号的方法
***关于中断
* 申请中断应该在设备打开的时候进行,而不要在模块初始化的时候进行,因为中断信号线非常有限,如果在模块初始化的时候申请了中断,那么驱动程序可能只是占用而从未使用,也会阻止起其他驱动使用该中断,而在设备打开的时候申请中断,则可以共享中断。
request_irq()、free_irq()
  
  这是驱动程序申请中断和释放中断的调用。在include/linux/sched.h里声明。request_irq()调用的定义:
  int request_irq(unsigned int irq,
  void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
  unsigned long irqflags,
  const char * devname,
  void *dev_id);
  
 
 irq是要申请的硬件中断号。在Intel平台,范围0--15。handler是向系统登记的中断处理函数。这是一个回调函数,中断发生时,系统调用
这个函数,传入的参数包括硬件中断号,device
id,寄存器值。dev_id就是下面的request_irq时传递给系统的参数dev_id。irqflags是中断处理的一些属性。比较重要的有
SA_INTERRUPT,标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。快速
处理程序被调用时屏蔽所有中断。慢速处理程序不屏蔽。还有一个SA_SHIRQ属性,设置了以后运行多个设备共享中断。dev_id在中断共享时会用到。
一般设置为这个设备的device结构本身或者NULL。中断处理程序可以用dev_id找到相应的控制这个中断的设备,或者用rq2dev_map找到
中断对应的设备。
    void free_irq(unsigned int irq,void *dev_id);

*驱动中,如果要休眠,必须释放该设备的信号量或自旋锁。

***时钟
  
  时钟的处理类似中断,也是登记一个时间处理函数,在预定的时间过后,系统时钟的处理类似中断,也是登记一个时间处理函数,在预定的时间过后,系统会调用这个函数。在include/linux/timer.h里声明。
  
  struct timer_list {
  struct timer_list *next;
  struct timer_list *prev;
  unsigned long expires;
  unsigned long data;
  void (*function)(unsigned long);
  };
  void add_timer(struct timer_list * timer);
  int del_timer(struct timer_list * timer);
  void init_timer(struct timer_list * timer);
  
 
 使用时钟,先声明一个timer_list结构,调用init_timer对它进行初始化。time_list结构里expires是标明这个时钟的周
期,单位采用jiffies的单位。jiffies是Linux一个全局变量,代表时间。它的单位随硬件平台的不同而不同。系统里定义了一个常数HZ,代
表每秒种最小时间间隔的数目。这样jiffies的单位就是1/HZ。Intel平台jiffies的单位是1/100秒,这就是系统所能分辨的最小时间
间隔了。所以expires/HZ就是以秒为单位的这个时钟的周期。 中国网管联盟bitsCN.com
  
  function就是时间到了以后的回调函数,它的参数就是timer_list中的data。data这个参数在初始化时钟的时候赋值,一般赋给它设备的device结构指针。
  
  在预置时间到系统调用function,同时系统把这个time_list从定时队列里清除。所以如果需要一直使用定时函数,要在function里再次调用add_timer()把这个函数。

抱歉!评论已关闭.