liunx中字符驱动编写的简单模板

下面是关于字符驱动两个程序,主要是说明驱动编写的思想,理解驱动是怎么一步一步被实现的。

驱动的第一个实现程序,是相对于裸机编程的,主要是体会一下驱动编程思想:

cdev.h: 所包含的头文件

#ifndef CDEV_H_
#define CDEV_H_

#define MAX_CDEVS 1024

struct cdev {
struct file_operations *ops;
char *name;
int id;
};

fs.h : 包含的另一个头文件

#ifndef FS_H_
#define FS_H_

struct file_operations {
void (*open)(void);
void (*read)(void);
void (*write)(void);
void (*close)(void);
};

void open(int id);
void read(int id);
void write(int id);
void close(int id);
#endif /* FS_H_ */

extern struct cdev *cdev_map[MAX_CDEVS];

#endif /* CDEV_H_ */

cdev.c:

#include "cdev.h"

struct cdev *cdev_map[MAX_CDEVS];

void (struct cdev *dev, struct file_operations *fops)
{
dev->ops = fops;
}

void cdev_add(struct cdev *dev, int id)
{
cdev_map[id] = dev;
}

fs.c: 这里面是具体函数的实现,通过调用结构体执行不同的功能

/*
* fs.c
*
* Created on: 2015-4-3
* Author: Administrator
*/
#include "cdev.h"
#include "fs.h"

void open(int id)
{
struct cdev *tmp_dev = cdev_map[id];
tmp_dev->ops->open();
}

void write(int id)
{
struct cdev *tmp_dev = cdev_map[id];
tmp_dev->ops->write();
}

void read(id)
{
struct cdev *tmp_dev = cdev_map[id];
tmp_dev->ops->read();
}

void close(int id)
{
struct cdev *tmp_dev = cdev_map[id];
tmp_dev->ops->close();
}

led.c: 驱动功能的实现,将特定的功能传给结构体,并给一个标号,方便后面查找函数和调用

/*
* led.c
*
* Created on: 2015-4-3
* Author: Administrator
*/
#include "fs.h"
#include "cdev.h"

#define GPG3CON (*(volatile unsigned int *)0xe03001c0)
#define GPG3DAT (*(volatile unsigned int *)0xe03001c4)

void led_init(void)
{
GPG3CON = 0x1111;
}

void led_on(void)
{
GPG3DAT = 0xf;
}

void led_off(void)
{
GPG3DAT = 0x0;
}

struct file_operations led_ops = {
.open = led_init,
.write = led_on,
.close = led_off,
};

struct cdev led_cdev;
int led_dev_num = 0; //led设备的设备号

void led_driver_init(void)
{
//初始化led 字符设备驱动
cdev_init(&led_cdev, &led_ops);

//注册进内核
cdev_add(&led_cdev, led_dev_num);

}

kernel_main.c 主函数调用写好的驱动

/*
* kernel_main.c
*
* Created on: 2015-4-3
* Author: Administrator
*/

void start_kernel(void)
{
//内核各种机制的初始化以及模块入口初始化
led_driver_init();

//运行用户空间进程(应用)
main();
}

main.c 主函数:

#include "cdev.h"
#include "fs.h"
/*
* 延时函数
*/
void soft_delay(unsigned int loops)
{
while (loops > 0)
loops--;
}

/*
* led 测试c入口
*/
void main()
{
//获取led的设备号 0
open(0);
write(0);
}

对应于上面程序的makefile:

##############################################################
CROSS_COMPILE = arm-none-eabi-

CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump

INCLUDE_PATH = ./include

CFLAGS = -I$(INCLUDE_PATH) -mabi=apcs-gnu -c -g -O0 -o
##############################################################

objs = init/start.o kernel/kernel_main.o cdev/fs.o cdev/cdev.o drivers/led.o main.o

pro_name = kernel
##############################################################

all:$(objs)
$(LD) $(objs) -Tmap.lds -o $(pro_name).elf #链接
$(OBJCOPY) -O binary $(pro_name).elf $(pro_name).bin #二进制提取
$(OBJDUMP) -D $(pro_name).elf > $(pro_name).dis #反汇编

%.o : %.s
$(CC) $(CFLAGS) [email protected] $< #-c 只编译不链接 -g 调试 -O0优化等级 -O1 -O2 -o 输出
%.o : %.c
$(CC) $(CFLAGS) [email protected] $< #[email protected]:目标 $<:第一个依赖 $^: 多个依赖

clean:
rm -rf *.bin *.elf *.dis $(objs)

驱动的第二个实现,这个是在内核里面编写的程序:

hello_char.c:这个C程序里面包含了很多内核里面的语句,这个要查一下他们的用法

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>

struct cdev hello_cdev; 先创建一个对象
int hello_major = 250; 主设备号 (前12位)
int hello_minor = 0; 次设备号 (后20位)
int num_of_hellos = 1; 注册这个设备的个数,这里表示就一个
dev_t hello_devno; 最终构成的设备号记做hello_devno

MODULE_LICENSE("GPL");

static int hello_open(struct inode *nodp, struct file *filp)
{
printk(KERN_INFO "hello open! \n");
return 0;
}
static int hello_release(struct inode *nodp, struct file *filp)
{
printk(KERN_INFO "hello release! \n");
return 0;
}

struct file_operations hello_ops = {
.open = hello_open,
.release = hello_release,
};

static int __init hello_init(void)
{
/*construct cdev number*/
hello_devno = MKDEV(hello_major, hello_minor); //hello_major << 20 | hello_minor; 构成设备号
register_chrdev_region(hello_devno, num_of_hellos, "hello_char");注册设备号,后面三个参数分别表示(设备号,有几个,设备名字)

/*cdev initialize*/
cdev_init(&hello_cdev, &hello_ops); 初始化设备

/*add to kernel*/
cdev_add(&hello_cdev, hello_devno, num_of_hellos); 将设备号和设备联系起来

printk(KERN_INFO "hello_init ! \n");
return 0;
}

static void __exit hello_exit(void) 卸载的函数
{
cdev_del(&hello_cdev); 卸载的时候删除对象
unregister_chrdev_region(hello_devno, num_of_hellos);
printk("hello_exit ! \n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("just for test!");

test.c内核调用驱动的程序:

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc, const char *argv[])
{
int fd;

fd = open("/dev/hello", O_RDWR); 打开设备,在这之前linux里面创建了一个节点->sudo mknod /dev/hello c 250 0 对应前面的主次设备号
if (fd < 0) {
perror("open");
return -1;
}

close(fd);
return 0;
}

对应于上面程序的makefile :

MOD_NAME = hello_char

obj-m = $(MOD_NAME).o

#KERN_DIR = /home/linux/linux-2.6.35-farsight
KERN_DIR = /lib/modules/$(shell uname -r)/build

all:
make -C $(KERN_DIR) M=$(shell pwd) modules

clean:
rm -rf *.o *.ko *.mod.c *.order *.symvers .*.cmd .*versions

backup:
tar cvf ../$(MOD_NAME).tar.gz ../$(MOD_NAME)
cp ../$(MOD_NAME).tar.gz /mnt/hgfs/ubuntu_share

时间: 2024-07-29 10:37:12

liunx中字符驱动编写的简单模板的相关文章

Golang中使用heap编写一个简单高效的定时器模块

定时器模块在服务端开发中非常重要,一个高性能的定时器模块能够大幅度提升引擎的运行效率.使用Golang和heap实现一个通用的定时器模块,代码来自:https://github.com/xiaonanln/goTimer 也可以查看文档:http://godoc.org/github.com/xiaonanln/goTimer,下面是完整的代码,并进行适当的注释和分析. 从性能测试结果来看,基于heap的定时器模块在效率上并不会比时间轮(TimeWheel)的实现慢多少,但是逻辑上要简单很多.

arm-linux字符设备驱动开发之---简单字符设备驱动

一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流的设备,常见的字符设备有鼠标.键盘.串口.控制台和LED设备等.2.块设备:是指可以从设备的任意位置读取一定长度数据的设备.块设备包括硬盘.磁盘.U盘和SD卡等. 每一个字符设备或块设备都在/dev目录下对应一个设备文件.linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和

1、编写一个简单Makefile模板

一.Makefile简介 一个工程中的源文件不计其数,其按类型.功能.模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令.linux内核的编译同样也遵循这些规则,具体说明可见kernel/Documentation/kbuild/makefiles.txt 二.简单编写一个Makefile模板 当编译少量的源文件

linux最简单的驱动编写及测试流程

本文采用nfs挂载网络文件系统的方式,手动创建设备节点, 动态加载驱动模块,对理解驱动编写流程有很大好处! 一.初级驱动执行流程: 1.写好Makefile文件,里面包含将应用程序编译成app文件,将驱动程序编译成mydrv.ko文件的脚本 2.在ubuntu里执行sudo make 3.确保mydrv.ko和app被拷贝到nfs挂载的根文件系统的/modules目录 4.在模块中创建设备节点(在串口软件显示的开发板文件系统里执行) mknod /dev/dcx-drv c 250 0 4.1.

程序一 用记事本建立文件src.dat,其中存放若干字符。编写程序,从文件src.dat中读取数据,统计其中的大写字母、小写字母、数字、其它字符的个数,并将这些数据写入到文件test.dat中。

用记事本建立文件src.dat,其中存放若干字符.编写程序,从文件src.dat中读取数据,统计其中的大写字母.小写字母.数字.其它字符的个数,并将这些数据写入到文件test.dat中. #include<stdio.h>#include<stdlib.h>#include<string.h>int main(){ FILE*fp1,*fp2; char ch; int da=0,xiao=0,shuzi=0,qita=0; if((fp1=fopen("sr

Tiny4412之串口(Uart)驱动编写

一:tiny4412串口驱动编写 1.串口通信简介 串口通信指串口按位(bit)发送和接收字节,串口通信的概念非常简单,串口按位(bit)发送和接收字节.尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线 接收数据.它很简单并且能够实现远距离通信.比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2 米:而对于串口而言,长度可达1200米. 串口通信所采用的通信协议为RS-232,RS-232通信方式允许简单连

MPU6050带字符驱动的i2c从设备驱动1

开干: 1.闲言碎语 这个驱动,越写觉的越简单,入门难,入门之后感觉还好.Linux开发还是比较友好的. 2.编写MPU6050带字符驱动的i2c从设备驱动 要实现的功能就是,将MPU6050作为字符驱动,在应用层,对其进行读数据.实现简单的功能.在前面的分析和实践中,可以看到实现字符驱动主要是实现file_operation中的方法,注册初始化cdev,让cdev和file_opration产生联系,字符驱动的初始化通过module_init来声明.实现i2c从设备驱动,主要是i2c_clie

驱动篇-字符驱动入门(完美解决cat echo 字符设备乱码的问题)(一)

闲来无事,整理一下驱动入门知识! 大部分与网上整理的差不多,我主要想说的有两个特别的地方,刚入门的人看别人整理的肯定都不知道怎么测试.或者测试结果不像他们所写的那样! 第一点就是用mknod创建的设备名,设备号不能随便写,必须你所写的源文件命名的一致. 比如你在c文件中定义 #define DEV_NAME "chardev" 那么设备名就是chardev 设备号可以通过 cat /proc/devices |grep chardev 得到主设备号. [email protected]

第三十三天:Tiny4412驱动开发之LED驱动和按键驱动编写

从今天开始进入驱动开发的课程的学习,共完成四件事情.一:u-boot的简单移植,二:uboot中编写helloword程序 三:开发板中led灯的驱动编写,包括led点亮,闪烁,跑马,流水.四:开发板中按键的驱动编写,按下按键后在屏幕中显示字符. 一:u-boot的简单移植 1.进入开发板提供的源码文件包,解压uboot源码包. cd /home/bunfly/source_code/ tar xf uboot_tiny4412-20130729.tgz 2.进入uboot文件夹,更改uboot