第三章 字符设备驱动模型cdev
1 设备驱动的分类
1.1 字符设备 c
应用程序以“字符”的方式来访问驱动程序。应用程序和驱动程序之间交互的数据是一个字节一个字节的。这些数据是以“流”的方式进行,实时的传递。数据是没有缓存的。
系统IO编程:open()/read()/write()/ioctl()/mmap()/close()
将字符设备看成了一个文件 --->设备文件/设备文件节点
crw-rw---- 1 root root 10, 131 Jan 1 12:00 /dev/adc
crw-rw---- 1 root root 29, 0 Jan 1 12:00 /dev/fb0
crw-rw---- 1 root root 14, 3 Jan 1 12:00 /dev/dsp
crw-rw---- 1 root root 13, 64 Jan 1 12:00 /dev/event0
1.2 块设备 b
带有缓存的,是以“块”为单位进行数据的传递。块设备都是有文件系统的,块设备可以分区
brw-rw---- 1 root root 31, 0 Jan 1 12:00 /dev/mtdblock0
brw-rw---- 1 root root 31, 1 Jan 1 12:00 /dev/mtdblock1
brw-rw---- 1 root root 31, 2 Jan 1 12:00 /dev/mtdblock2
brw-rw---- 1 root root 31, 3 Jan 1 12:00 /dev/mtdblock3
brw-rw---- 1 root root 31, 4 Jan 1 12:00 /dev/mtdblock4
挂载--->将带有文件系统的块设备挂载到根文件系统的某个目录下,然后通过该目录就可以访问块设备。
#mount -t vfat /dev/sda1 /mnt/data
标准IO编程:fopen()/fread()/fwrite()/fclose()
1.3 网络设备 s
有线网卡 无线网卡。socket链接来访问网络链接。
2 一个字符设备驱动的设计流程(简单)
2.1 定义一个字符设备--->cdev
2.2 申请一个设备号 --->10--主设备号, 131--次设备号,设备号32位,高12位主设备号,低20位次设备号, 10<<20+130 (左移20位加上130)。动态申请or静态注册
2.3 定义文件操作集并初始化 --->file_operations给应用程序提供一个标准的接口
2.4 字符设备的初始化
2.5 将字符设备加入内核
2.6 创建设备文件
3 cdev结构体 (描述一个字符设备)
每个字符设备驱动都有一个cdev,在linux的内核中,使用cdev来描述一个字符设备。
/***********************************
#include <linux/cdev.h>
struct cdev {
struct kobject kobj; --->给kernel管理设备驱动使用的一个结构体,/sys下的内容
struct module *owner; --->cdev属于谁?,一般使用THIS_MODULE
const struct file_operations *ops; --->文件操作集
struct list_head list; --->kernel管理cdev的一个链表
dev_t dev; --->设备号
unsigned int count; --->次设备的数量
};
**************************************/
dev_t dev; --->设备号
typedef __kernel_dev_t dev_t;
typedef __u32 __kernel_dev_t; //32位 无符号整形
typedef unsigned int __u32;
unsigned int count; --->次设备的数量
如:串口设备文件 /dev/s3c2410_serila # -l
crw-rw---- 1 root root 204, 64 Jan 1 12:08 /dev/s3c2410_serial0
crw-rw---- 1 root root 204, 65 Jan 1 12:00 /dev/s3c2410_serial1
crw-rw---- 1 root root 204, 66 Jan 1 12:00 /dev/s3c2410_serial2
crw-rw---- 1 root root 204, 67 Jan 1 12:00 /dev/s3c2410_serial3
例: 在开发板里 # echo 123456 > /dev//s3c2410_serila0 //把数字写到串口0 (即控制台,屏幕显示)
uart有四个,这4个Uart是同一个类型的设备,驱动模型是相同的。则这四个设备共用一个主设备号,用次设备号来区分每个不同的子设备。
例:定义一个cdev
struct cdev led_drv;
4 设备号与设备号的申请
dev_t dev; 是一个32bits的整性值,其中高12bits是主设备号,低20bits是次设备号。每个设备有唯一的设备号。
#include <linux/cdev.h>
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
4.1 设备号的小函数:
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //第一个参数:主设备号, 第二个参数:次设备号, 将主设备号左移20位 或上(加上) 次设备号
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //得到设备号里的 主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //得到设备号里的 次设备号
4.2 静态注册一个设备号
/**
* register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure. //返回值
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name) // (设备号, 1个子设备, 设备名称)
参数说明:
dev_t from --->静态注册的设备号。如果我们一次注册多个子设备,from就是第一个子设备的设备号
unsigned count --->子设备的数量
const char *name --->设备名称 #cat /proc/devices
返回值:
成功---返回0
失败---返回负数的错误码
例:申请一个led指示灯的驱动
unsigned int led_major=200; //主设备号
unsigned int led_minor = 0; //次设备号
dev_t dev_num; //设备号的变量
int ret; //返回值
dev_num = MKDEV(led_major,led_minor); //通过主设备号,次设备号, 得到设备号
ret = register_chrdev_region(dev_num, 1, "gec210_led"); // (设备号, 1个子设备, 设备名称) ,把这个设备号注册到内核
if(ret < 0)
{
return ret; //设备号注册不成功,退出到返回值,不往后运行,
}
例:申请4个uart驱动的设备号, 串口
crw-rw---- 1 root root 204, 64 Jan 1 12:08 /dev/s3c2410_serial0
crw-rw---- 1 root root 204, 65 Jan 1 12:00 /dev/s3c2410_serial1
crw-rw---- 1 root root 204, 66 Jan 1 12:00 /dev/s3c2410_serial2
crw-rw---- 1 root root 204, 67 Jan 1 12:00 /dev/s3c2410_serial3
dev_t dev_num; //设备号的变量
int ret; //返回值
dev_num = MKDEV(204,64); //通过主设备号,次设备号, 得到设备号
ret = register_chrdev_region(dev_num, 4, "s3c2410_serial"); // (设备号, 1个子设备, 设备名称--后面的序号系统添加) ,把这个设备号注册到内核
if(ret < 0)
{
return ret;
}
4.3 动态分配一个设备号
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
参数说明:
dev_t *dev --->分配后的设备号,指针
unsigned baseminor --->指定的次设备号,如果注册多个次设备,则是次设备号的开始值
unsigned count --->子设备的数量
const char *name --->设备名称 #cat /proc/devices
返回值:
成功---返回0
失败---返回负数的错误码
4.4 注销一个设备号
一般在驱动安装过程中是申请设备号,在驱动的卸载过程中,来注销这个设备号。
void unregister_chrdev_region(dev_t from, unsigned count)
5 文件操作集 (提供函数原型)
在驱动程序中定义的文件操作集是给应用程序提供系统调用的接口。
应用程序(用户空间)open() ----->系统调用(内核空间) sys_open()---->vfs_open() (虚拟文件系统)--->file_operations.open() 驱动程序 (文件操作集结构体里函数)
每个字符设备驱动都有一个自己的file_operations,file_operations是属于cdev的(结构体struct cdev 里的成员 )。我们要定义自己的file_operations,然后对其进行初始化。
5.1文件操作集原型:
#inlcude <linux/fs.h>
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
5.2 实际我们设计驱动的时候,常用的成员:
struct file_operations {
struct module *owner; //结构体指针
int (*open) (struct inode *, struct file *); //(文件节点, 文件指针); 打开一个驱动, 给用户程序的open调用的接口,与用户程序的open的参数不一样.
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //
int (*mmap) (struct file *, struct vm_area_struct *);
int (*release) (struct inode *, struct file *); //用户程序 close 系统调用的接口
};
例:led灯的驱动
open("/dev/led", O_RDWR) -->系统调用-->file_operations.open =gec210_led_open() -->GPJ2_0~3配置成输出,led初始是灭
write(fd,buf,sizeof(buf)) -->系统调用-->file_operations.write =gec210_led_write() -->根据buf的值,控制LED的状态
close(fd) -->系统调用-->file_operations.release =gec210_led_release() -->led灭
//系统调用(是对内核保护,用户程序不能随便访问内核), 用户程序函数是自己定的,要初始化到文件操作集的函数上,来控制硬件
static int gec210_led_open (struct inode *inode, struct file *filp) //函数定义
//(在内核里管理一个文件的节点, 指向这个文件的指针),用户自定义函数gec210_led_open 用初始化为文件操作集里的成员open函数
{
printk("openning the driver of led\n");
return 0; //一般内核, 成功返回0 ,失败返回负数错误码
}
ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos) //(文件指针, 用户空间程序写下来的数据, 数据大小, 文件指针的偏移值)
{
//接收用户空间write函数写下来的数据,并用这些数据控制led灯的状态。
}
static int gec210_led_release(struct inode *inode, struct file *filp) //同close
{
printk("openning the driver of led\n");
return 0;
}
static struct file_operations gec210_led_fops = { //自定义一个文件操作集结构体,对结构体初始化,
.owner = THIS_MODULE, //此文件操作集结构体 ,属于当前模块, {点 成员},特点1可以缺省初始化, 2 可以打乱成员初始化,定义顺序
.open = gec210_led_open, //用户自定义函数gec210_led_open 初始化为文件操作集里的成员open函数
.write = gec210_led_write, //同上
.release = gec210_led_release, //同上
};
注:本例没用到read ,如要读灯的状态,亮灭(数据寄存器),可用read函数
6 字符设备初始化
#include <linux/cdev.h>
/**
* cdev_init() - initialize a cdev structure //初始化一个结构体
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
原型
void cdev_init(struct cdev *cdev, const struct file_operations *fops) //把文件操作集放到cdev结构体里里,把两者捆绑
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev); //把cdev结构体里的数据清空
INIT_LIST_HEAD(&cdev->list); //初始化cdev链表
kobject_init(&cdev->kobj, &ktype_cdev_default); //初始化cdev里有kobj
cdev->ops = fops; //把文件操作集给了cdev的一个成员
}
例:
static struct cdev led_drv; //定义cdev
static struct file_operations gec210_led_fops = { //定义文件操作集
.owner = THIS_MODULE,
.open = gec210_led_open,
.write = gec210_led_write,
.release = gec210_led_release,
};
cdev_init(&led_drv,&gec210_led_fops); //初始化, 参数是指针,要加取址符
另一种:错误 ,段错误
static struct cdev *led_drv; //指针 ,要先分配空间 ,不然初始化,清空指向的地址没有分配,
static struct file_operations *gec210_led_fops = {
.owner = THIS_MODULE,
.open = gec210_led_open,
.write = gec210_led_write,
.release = gec210_led_release,
};
cdev_init(led_drv,gec210_led_fops);
7 将字符设备加入内核
7.1 添加
#include <linux/cdev.h>
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure. //失败返回负数错误码, 成功没有说明即为,系统默认0
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count) //(定义好的cdev, 设备号, 次设备数量)
7.2 删除
在驱动的卸载函数中,需要将cdev从内核中删除:
/**
* cdev_del() - remove a cdev from the system
* @p: the cdev structure to be removed
*
* cdev_del() removes @p from the system, possibly freeing the structure
* itself.
*/
void cdev_del(struct cdev *p)
8 用户空间和内核空间交换数据的函数
#include <linux/uaccess.h>
8.1 放在驱动程序的write函数--->应用程序向驱动程序写数据
inline long copy_from_user(void *to,const void __user * from, unsigned long n)
get_user(x, ptr)
8.2 放在驱动程序的read函数--->应用程序从驱动程序读数据
原型:
inline long copy_to_user(void __user *to,const void *from, unsigned long n)put_user(x, ptr)
参数说明:
void *to --->用户空间的一个buffer
const void *from --->内核空间的buffer
unsigned long n --->拷贝的大小
返回值:
拷贝出错,返回负数的错误码
拷贝成功,返回0
例:
static ssize_t
s3c_adc_read(struct file *file, char __user *buffer,
size_t size, loff_t *pos)
{
unsigned int adc_value = 0;
adc_value = s3c_adc_convert(); //ADC转换,然后得到数字量
//buffer -->用户空间的buffer,应用程序read函数就是从这个buffer来读取数据
//&adc_value -->内核空间的数据,ADC转换后的数字量
//sizeof(unsigned int) -->数据的大小
if (copy_to_user(buffer, &adc_value, sizeof(unsigned int))) //把内核空间的数据拷贝到用户空间,用户空间应用程序才能从驱动程序得到数据,要read函数里
return -EFAULT; //返回一个负数错误码
return sizeof(unsigned int); //返回正确拷贝的字节数
}
ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
int ret;
char kbuf[20];
if(len > 20)
return -EINVAL;
ret = copy_from_user(kbuf,buf,len); //从用户空间拷贝数据
if(ret!= 0)
return -EFAULT;
return len;
}
9 错误码
errno.h 是C语言C标准函式库里的标头档,定义了通过错误码来回报错误信息的宏。
errno本身是一个整型的全局变量,当使用errno的库函数,在执行出错时,只通过函数返回值返回一个表示出错的标识,如-1或NULL等,具体的出错原因会被赋值到errno中。通过查询errno可以确定具体的出错原因。
在errno.h中定义了一系列常见的宏,其形式为
#define EPERM 1 /* Operation not permitted */
可以划分为
1 定义一个宏名,以E开头;
2 定义其值,为一个正整数;
3 一个注释区域,说明该错误号出现时的具体错误内容。
errno.h中的条目因不同编译器的实现而有所区别,一般在100~128条范围内,具体内容可以在编译器的系统标准头文件夹下查看对应文件。
只有当一个库函数失败时,errno才会被设置。当函数成功运行时,errno的值不会被修改。这意味着我们不能通过测试errno的值来判断是否有错误存在。反之,只有当被调用的函数提示有错误发生时检查errno的值才有意义。
查看错误代码errno是调试程序的一个重要方法。当linux C api函数发生异常时,一般会将errno变量(需include errno.h)赋一个整数值,不同的值表示不同的含义,可以通过查看该值推测出错的原因。在实际编程中用这一招解决了不少原本看来莫名其妙的问题。
9.1.1文件名: errno.h errno-base.h
9.1.2所在文件夹:
A) linux 2.4.20-18内核代码
/usr/include/asm/errno.h
B) linux 2.6.32的内核代码中
LINUX/android/kernel/include/uapi/asm-generic/errno-base.h --->errno 1-34
LINUX/android/kernel/include/uapi/asm-generic/errno.h --->errno 35-133
LINUX/android/kernel/include/linux/errno.h --->errno512-529
C) errno 在 <errno.h> 中定义,错误 Exx 的宏定义在 /usr/include/asm-generic 文件夹下面的 errno-base.h 和 errno.h,分别定义了 1-34 、35-132 的错误定义。
/usr/include/asm-generic/errno-base.h --->errno 1-34
/usr/include/asm-generic/errno.h --->errno 35-133
9.1.3 strerror() 函数依据 errno 值返回错误描述字符串,下面程序打印对照表:
A)
[cpp] view plain copy
#include <errno.h>
#include <string.h>
#include <stdio.h>
int main()
{
int i;
for(i = 0; i < 140; ++i)
{
errno = i;
printf("errno %d :\t\t%s\n",i,strerror(errno));
}
return 0;
}
B)
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
int fd;
fd=open("abc",O_WRONLY);
if(fd<0){
printf("Error:fd=%d\n",fd);
perror("open file abc");//这一行就是根据系统的编号打印出错误的信息
}
return 0;
}
//*********************
9.2 Linux errno 错误对照表宏定义
9.2.1 errno-base.h --->errno 1-34
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
9.2.2 errno.h --->errno 35-133
10 访问驱动的应用程序
#include <stdio.h>
#include <fcntl.h>
int main(void)
{
int fd;
int ret;
char buf[]="hello driver\n";
//"/dev/led_drv" ---linux驱动的设备文件节点(node)
fd = open("/dev/led_drv", O_WRONLY);
if(fd <0)
{
perror("open led_drv:");
return -1;
}
ret = write(fd,buf,sizeof(buf));
if(ret < 0)
{
perror("write led_drv: ");
return -1;
}
sleep(1);
close(fd);
return 0;
}
11 创建设备文件
11..1 安装驱动
# insmod led_drv.ko
[ 576.746579] hello gec210
11.2 主设备号和设备名称
# cat /proc/devices
Character devices:
100 gec210_leds
11.3 创建设备文件(手动)
#mknod /dev/led_drv c 100 0
说明:
mknod 设备文件 设备类型 主设备号 次设备号
11.4 查看设备文件
[root@GEC210 /test]# ls /dev/led_drv -l
crw-r--r-- 1 root root 100, 0 Jan 1 12:19 /dev/led_drv
[root@GEC210 /test]# ./test
[ 1241.597908] openning the driver of led
[ 1241.597968] kbuf=hello driver
[ 1241.597972]
[ 1242.598121] closing the driver of led
A 作业
1.作业1 什么是系统调用?
2.作业2 什么是vfs
3.作业3 结构体的初始化方法
4.作业4 linux内核的编码风格
5.作业5 goto的用法
6.
1) 定义文件操作集中的read函数
2) 去掉文件操作集中的open和release函数
B1 代码一 --字符设备驱动模型cdev
1. Filename: led_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
//1)定义一个字符设备
static struct cdev led_drv;
static unsigned int led_major = 100; //0-->动态分配,>0-->静态注册
static unsigned int led_minor = 0;
static dev_t led_drv_num;
//3)定义文件操作集,并初始化
static int gec210_led_open (struct inode *inode, struct file *filp)
{
printk("openning the driver of led\n");
return 0;
}
ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
int ret;
char kbuf[20];
if(len > 20)
return -EINVAL;
ret = copy_from_user(kbuf,buf,len); //从用户空间拷贝数据
if(ret!= 0)
return -EFAULT;
printk("kbuf=%s\n",kbuf);
return len;
}
static int gec210_led_release(struct inode *inode, struct file *filp)
{
printk("closing the driver of led\n");
return 0;
}
static struct file_operations gec210_led_fops = {
.owner = THIS_MODULE,
.open = gec210_led_open,
.write = gec210_led_write,
.release = gec210_led_release,
};
static int __init gec210_led_init(void) //驱动的初始化及安装函数
{
int ret;
//2)申请/注册设备号
if(led_major == 0){
ret = alloc_chrdev_region(&led_drv_num, led_minor, 1, "gec210_leds");
}
else{
led_drv_num = MKDEV(led_major,led_minor);
ret = register_chrdev_region(led_drv_num, 1, "gec210_leds");
}
if(ret < 0){
printk("led_drv_num is error \n");
return ret;
}
//4)初始化cdev
cdev_init(&led_drv, &gec210_led_fops);
//5)将cdev加入kernel
ret = cdev_add(&led_drv,led_drv_num, 1 );
if(ret < 0){
printk("cdev add error\n");
goto failed_cdev_add;
}
printk("hello gec210\n"); //替代printf()
return 0;
failed_cdev_add:
unregister_chrdev_region(led_drv_num, 1);
return ret;
}
static void __exit gec210_led_exit(void) //驱动卸载函数
{
unregister_chrdev_region(led_drv_num, 1);
cdev_del(&led_drv);
printk("good bye gec210\n");
}
module_init(gec210_led_init); //驱动的入口
module_exit(gec210_led_exit); //驱动的出口
//内核模块的描述
MODULE_AUTHOR("bobeyfeng@163.com");
MODULE_DESCRIPTION("the first demo of module");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
//----------------------------------------
2. Filename: test.c
#include <stdio.h>
#include <fcntl.h>
int main(void)
{
int fd;
int ret;
char buf[]="hello driver\n"; //定义字符串
//"/dev/led_drv" ---linux驱动的设备文件节点(node)
fd = open("/dev/led_drv", O_WRONLY);
if(fd <0)
{
perror("open led_drv:");
return -1;
}
ret = write(fd,buf,sizeof(buf));
if(ret < 0)
{
perror("write led_drv: ");
return -1;
}
sleep(1);
close(fd);
return 0;
}
//--------------------------------------
3. Filename: Makefile
obj-m += led_drv.o
#KERNELDIR := /lib/modules/$(shell uname -r)/build
KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko
B2 代码二 --字符设备驱动模型cdev
1. Filename: led_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
static struct cdev led_drv;
static unsigned int led_major = 200; // 等于零是动态生成 大于零是静态注册
static unsigned int led_minor = 0;
static dev_t led_drv_num;
int ret;
// static int gec210_led_open (struct inode *inode, struct file *filp)
// {
// printk("openning the driver of led---open\n");
// return 0;
// }
ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
//接收用户空间write函数写下来的数据,并用这些数据控制led灯的状态。
int ret;
char kbuf[40];
if(len > 40)
return -EINVAL;
ret = copy_from_user(kbuf,buf,len);
if(ret < 0)
return -EFAULT;
printk("kbuf:%s\n",kbuf);
return len;
}
ssize_t gec210_led_read(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
int ret;
char kbuf[40]="hello user!";
if(len > 40)
return -EINVAL;
ret = copy_to_user(buf,kbuf,len);
if(ret < 0)
return -EFAULT;
return len;
}
// static int gec210_led_release(struct inode *inode, struct file *filp)
// {
// printk("openning the driver of led----release\n");
// return 0;
// }
static struct file_operations gec210_led_fops = {
.owner = THIS_MODULE,
//.open = gec210_led_open,
.write = gec210_led_write,
.read = gec210_led_read,
//.release = gec210_led_release,
};
static int __init gec210_led_init(void) //驱动的初始化及安装函数
{
printk("hello gec210\n"); //替代printf()
if(led_major == 0){
ret = alloc_chrdev_region(&led_drv_num,led_minor,1,"gec210_led");
}
else{
led_drv_num = MKDEV(led_major,led_minor);
ret = register_chrdev_region(led_drv_num,1,"gec210_led");
}
if(ret < 0){
printk("led_drv_num is error!\n");
return ret;
}
cdev_init(&led_drv,&gec210_led_fops);
ret = cdev_add(&led_drv,led_drv_num,1);
if(ret < 0){
printk("fail to add dev!\n");
goto failed_cdev_add;
}
failed_cdev_add:
unregister_chrdev_region(led_drv_num,1);
return ret;
return 0;
}
static void __exit gec210_led_exit(void)
{
unregister_chrdev_region(led_drv_num,1);
cdev_del(&led_drv);
printk("good bye gec210\n");
}
module_init(gec210_led_init); //驱动的入口
module_exit(gec210_led_exit); //驱动的出口
//内核模块的描述
MODULE_AUTHOR("bobeyfeng@163.com");
MODULE_DESCRIPTION("the first demo of module");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
//--------------------------------------
2. Filename: test
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd;
char buf[20]="hello driver!";
char buf1[40];
fd = open("/dev/led_drv",O_RDWR);
if(fd < 0)
{
perror("open led_drv:");
return -1;
}
int ret;
bzero(buf1,40);
ret = read(fd,buf1,sizeof(buf1));
if(ret < 0)
{
printf("failed to read data from led_drv!\n");
return -1;
}
printf("buf1:%s\n",buf1);
ret = write(fd,buf,sizeof(buf));
if(ret < 0)
{
printf("failed to write to led_drv!\n");
return -1;
}
close(fd);
return 0;
}
//--------------------------------------
3. Filename: Makefile
obj-m += led_drv.o
#KERNELDIR := /lib/modules/$(shell uname -r)/build
KERNELDIR := /home/smbshare/qudong/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko