linux下的字符设备驱动

本文介绍了Linux字符设备驱动的基本概念及其实现过程。通过一个简单的LED设备驱动例子,详细展示了如何创建和使用字符设备文件,以及如何实现open、ioctl和close等关键函数。

字符设备驱动总结

作者:JCY

此文章是学习了一段时间的linux字符设备驱动所写,在文中如有错误之处请指正,将不胜感激。

 

写了裸机那么长时间了,终于接触到了linux 驱动程序的设计,当然最简单的驱动程序没有涉及到与设备相关的知识,知识了解一下模块的加载。说到模块的加载,就想写C语言和C++接触到的一个程序hello一样,我们第一个接触到的最简单的模块的加载也是hello

我写了一个helloworld的程序,以此来说说模块。程序如下

#include <linux/init.h>

#include <linux/module.h>

static void  __init  helloword_init(void)

{

printk("load module : hello word   11111 !");

//return 0;

}

static void  __exit helloword_exit(void)

{

printk("unload module : hello word 11111!");

//return 0;

}

module_init(helloword_init);

module_exit(helloword_exit);

MODULE_LICENSE("GPL");

前面两行是编写模块驱动程序最重要的头文件,每一个驱动程序都会包括这两个头文件。

模块加载函数(module_init(函数名))和释放函数(module_exit(函数名))的宏定义就包含在init.h当中。helloword_inithelloword_exit函数是我自定义的函数,函数定义和声明的格式基本固定,可加一些对函数的一下修饰,如程序当中的static,在函数只实现了在终端打印处信息,仅此而已。由于这是在内核当中的程序所以不能用我们在写应用程序时的printf函数。

至于最后一行是对本模块许可权限的声明,GPL指示本模块为通用公共许可证,如果一个模块不包含任何的许可权限,那么当你加载内核时,内核会提示非标内核的警告。

如果你要编译该模块还有一个Makefile文件需要编写,如果没有此文件,我们管理和编译会很麻烦,当然你不用这个Makefile文件也是可的。我把我写的Makefile文件当中的内容显示如下:

#makefile for module

obj-m := hellomodule.o

KDIR = /home/jcy/SoftEmbed/linux-2.6.30.9

all:

make -C $(KDIR) SUBDIRS=$(shell pwd) modules

第一行为注释,第二行意思是将hellomodule.o和编译过程中生成hellomodule_mod.c编译成hellomodule.ko模块,如果你要编译其他的模块只需要更改hellomodule.o的为要编译C文件的目标文件名就行了。KDIR 是变量名被赋值为内核源文件的路径(此文件要和所正在使用的内核是一样的,否则在加载内核时会出现一些问题)all:为一个目标。最后一行为真正所执行的命令。

看我们将模块下载到板子上后所要执行的命令,

insmod  :模块的加载命令。insmod hellomodule.ko

当执行此命令时,内核就会调用helloword_init函数,便会去实行此函数中的语句了,在此模块中只打印了一行“load module : hello word   11111 !”。

lsmod   :显示所有加载的模块的信息

rmmod  :卸载模块

字符型设备函数调用的流程

Open的函数中有一个文件名参数,当在应用程序中执行了open函数时,因为每一个字符设备文件都对应这一个节点,同过该节点的主设备号,找到在内核当中的一个存放已注册设备的struct dev *的数组。一个字符设备结构体当中有struct file_operations *成员,该指针成员中存放着所有的对此字符设备的操作函数,就是通过这些函数就可以对硬件设备进行操作。例如open函数指针,ioctl函数指针等等!只要对struct file_operations的相应的成员复制就可以在应用程序中调用相关函数来访问内核模块中的函数了,从而实现了控制外设和采集数据的目的。

现在将LED设备驱动拷贝如下

my_led.h

#include <linux/module.h>

#include <linux/init.h>

#include <linux/fs.h>

#include <linux/cdev.h>

#include <mach/regs-gpio.h>

#include <asm/io.h>

#define MAIN_DEV_NUM 108

#define MINOR_DEV_NUM  0

struct cdev my_led_cdev;

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

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

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

struct file_operations  my_led_fops =

{

.open = my_led_open,

.ioctl = my_led_ioctl,

.release = my_led_close,

};

int my_led_open(struct inode *inode, struct file *file)

{

printk("dev_t = %ld", inode.i_rdev);

unsigned int value = 0 ;

value  |= (1<<10)|(1<<12)|(1<<16)|(1<<20);

writel(value,S3C2410_GPBCON);

value = 0;

writel(value,S3C2410_GPBUP);

return 0;

}

#define MY_LED_ON  1

#define MY_LED_OFF 0

#define LED1       1

#define LED2       2

#define LED3       3

#define LED4       4

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

{

//writel(0,S3C2410_GPBDAT);

printk("enter into my_led_inctl!\n\r");

unsigned int value = 0;

value = readl(S3C2410_GPBDAT);

switch(arg)

{

case LED1 :

if(MY_LED_ON == cmd)

{

writel(value&(~(1<<5)) ,S3C2410_GPBDAT);

}

else if(MY_LED_OFF == cmd)

{

writel((value|(1<<5)),S3C2410_GPBDAT);

}

break;

case LED2 :

if(MY_LED_ON == cmd)

{

writel(value&(~(1<<6)),S3C2410_GPBDAT);

}

else if(MY_LED_OFF == cmd)

{

writel((value|(1<<6)),S3C2410_GPBDAT);

}

break;

case LED3 :

if(MY_LED_ON == cmd)

{

writel(value&(~(1<<8)),S3C2410_GPBDAT);

}

else if(MY_LED_OFF == cmd)

{

writel((value|(1<<8)),S3C2410_GPBDAT);

}

break;

case LED4 :

if(MY_LED_ON == cmd)

{

writel(value&(~(1<<10)),S3C2410_GPBDAT);

}

else if(MY_LED_OFF == cmd)

{

writel((value|(1<<10)),S3C2410_GPBDAT);

}

break;

default :

writel(value&(~((1<<5)|(1<<6)|(1<<8)|(1<<10))),S3C2410_GPBDAT);

break;

}

return 0;

}

int my_led_close(struct inode *inode, struct file *file)

{

printk("close the led dev!\n\r");

return 0;

}

my_led.c

#include <inc/my_led.h>

unsigned long int my_led_no = 0;

static int __init my_led_init(void)

{

my_led_no = MKDEV(MAIN_DEV_NUM, MINOR_DEV_NUM);//内核源码的定义为#define MKDEV(ma,mi) ((ma)<<8 | (mi))

//register_chrdev_region(my_led_no,1,"/dev/led");

cdev_init(&my_led_cdev,&my_led_fops);//也可以对my_led_cdev中的ops变量直接赋值,此函数就是对cdev中变量进行初始化

my_led_cdev.owner = THIS_MODULE;//((struct module *)0)

cdev_add(&my_led_cdev, my_led_no, 1);//第二个的类型是dev_t类型(即unsigned long型)存储主设备和次设备号

#if DEBUG

printk("load my my_led module !\n\r");

#endif //end DEBUG

return 0;

}

static void __exit my_led_exit(void)

{

cdev_del(&my_led_cdev);

//unregister_chrdev_region(my_led_no, 1);

printk("unload my my_led module !\n\r");

}

module_init(my_led_init);

module_exit(my_led_exit);

MODULE_LICENSE("GPL");

设备号包括主备号和次设备号,主设备号最大值为255,次设备也为255.MKDEV宏就是来生成设备号,它的参数有主设备号和次设备号,其宏定义为#define MKDEV(ma,mi) ((ma)<<8 | (mi))。从宏定义我们能知道次设备号的最大值为255

编写此字符设备的流程为

首先建立一个struct file_operations的变量,然后对变量中的所需成员复制,用到那个函数就在前面声明一下,然后再变量复制之后就对所定义的函数进行实现。然后再建立dev的结构体,然后再对结构体中必要的成员复制,可以直接复制,也可以通过内核提供的函数复制,那种都不会错,最后通过调用cdev_add函数将指定设备号的设备加入到内核。

将对应的应用程序列处如下:

/*

将对应的驱动加载以后,创造一个设备结点(与驱动当中设置的结点是一样,命令:mknod /dev/my_led c 108 0

然后执行应用程序(如./led ON 2),那么第二个二极管就会点亮,数字参数标示对第几个二极管进行操作 。OFF 标示关闭二极管

*/

#define MY_LED_ON  1

#define MY_LED_OFF 0

#define LED1       1

#define LED2       2

#define LED3       3

#define LED4       4

#include <stdio.h>

#include <string.h>

int main(int argc, char *argv[])

{

int fd = 0;

int cmd = 0;

int arg = 0;

unsigned int led_num = 0;

if(argc <= 2)

{

printf("warning(main) :MY_LED_ON/MY_LED_OFF, 1/2/3/4\n\r");

printf("warning(main) :the program will exit !\n\r");

return -1;

}

printf("argc = %d\n\r" , argc);

printf("argv[0] = %s\n\r argv[1] = %s\n\r  argv[2] = %s\n\r",argv[0],argv[1],argv[2]);

if((argv[2][0]<='4')&&(argv[2][0]>='1'))

{

arg = argv[2][0] - 0x30;

printf("the variable arg = %d\n\r",arg);

}

else

{

printf("warning(main) : the argument is error !\n\r");

}

if(strcmp(argv[1],"ON") == 0)

{

printf("the second argument is ON\n\r");

cmd = MY_LED_ON;

}else if(strcmp(argv[1],"OFF") == 0)

{

printf("the second argument is OFF\n\r");

cmd = MY_LED_OFF;

}else 

{

printf("warning(main) :the argument is error argv[1]\n\r");

}

printf("cmd and arg is two argument send to driver \n\r");

printf("cmd= %d\n\r",cmd);

printf("arg= %d\n\r",arg);

fd = open("/dev/my_led",0);

ioctl(fd,cmd,arg);

close(fd);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值