一、linux系统将设备分为3类:字符设备、块设备、网络设备

1、字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。
2、块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。

每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。

本文主要是介绍字符设备驱动程序,从驱动程序开始,涉及文件操作,一共四个函数:包括文件的打开,读,写,删除。还有文件的注册和注销。

废话不多说,上源代码,以代码为例开始解释:

1.mydriver.c:

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

#if CONFIG_MODVERSIONS == 1
#define MODVERSIONS
#include <linux/version.h>
#endif

#define DEVICE_NUM 0 //随机产生一个设备号

static int device_num = 0; //用来保存创建成功后的设备号
static char buffer[1024] = "mydriver"; //数据缓冲区
static int open_nr = 0; //打开设备的进程数,用于内核的互斥

//函数声明
// inode;linux下文件的管理号。
//file:linux一切皆文件。文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构
static int mydriver_open(struct inode *inode, struct file *filp);
static int mydriver_release(struct inode *inode, struct file* filp);
static ssize_t mydriver_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos); //loff-t:long long 型
static ssize_t mydriver_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos); //__user表明参数是一个用户空间的指针,不能在kernel代码中直接访问。
//size_t:一个基本的无符号整数的C / C + +类型
//填充file_operations结构相关入口
static struct file_operations mydriver_fops = {
.read = mydriver_read,
.write = mydriver_write,
.open = mydriver_open,
.release = mydriver_release,
};

//打开函数
static int mydriver_open(struct inode *inode, struct file *filp)
{
printk("\nMain device is %d, and the slave device is %d\n", MAJOR(inode->i_rdev), MINOR(inode->i_rdev)); //把主从设备号传入
if (open_nr == 0) {
open_nr++;
try_module_get(THIS_MODULE); //尝试打开模块
return 0;
}
else {
printk(KERN_ALERT "Another process open the char device.\n");//进程挂起
return -1;
}
}

//读函数
static ssize_t mydriver_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
{
//if (buf == NULL) return 0;
if (copy_to_user(buf, buffer, sizeof(buffer))) //读缓冲 ,第一个参数是to:用户空间的地址,第二个参数是from,内核空间的地址,第三个参数是要从内核空间拷贝的字节数
{
return -1;
}
return sizeof(buffer);
}

//写函数,将用户的输入字符串写入
static ssize_t mydriver_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
{
//if (buf == NULL) return 0;
if (copy_from_user(buffer, buf, sizeof(buffer))) //写缓冲
{
return -1;
}
return sizeof(buffer);
}

//释放设备函数
static int mydriver_release(struct inode *inode, struct file* filp)
{
open_nr--; //进程数减1
printk("The device is released!\n");
module_put(THIS_MODULE); //释放模块
return 0;
}

//注册设备函数
static int __init mydriver_init(void)
{
int result;

printk(KERN_ALERT "Begin to init Char Device!"); //注册设备
//向系统的字符登记表登记一个字符设备
result = register_chrdev(DEVICE_NUM, "mydriver", &mydriver_fops); //第一个参数等于0,则表示采用系统动态分配的主设备号;不为0,则表示静态注册。 第二个参数命名, 第三个参数为其地址

if (result < 0) {
printk(KERN_WARNING "mydriver: register failure\n");
return -1;
}
else {
printk("mydriver: register success!\n");
device_num = result;
return 0;
}
}

//注销设备函数
static void __exit mydriver_exit(void)
{
printk(KERN_ALERT "Unloading...\n");
unregister_chrdev(device_num, "mydriver"); //注销设备
printk("unregister success!\n");
}

//模块宏定义
module_init(mydriver_init); //模块加载函数
module_exit(mydriver_exit); //设备卸载函数

MODULE_LICENSE("GPL"); // "GPL" 是指明了 这是GNU General Public License的任意版本

因为注释内容有限,在后面贴上一些对源代码的注释:

inode;linux下文件的管理号。(静态的)
file:linux一切皆文件。文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。(动态的)
__user:The use of char __user *buf is typically found in the linux kernel...denoting that this address is in the user space. For example when writing to disk the kernel copies the contents of *buf into a kernel space buffer before writing it out to disk. Similarly when a process requests a read operation...the device driver at the behest of the kernel reads the desired disk blocks into a kernel space buffer...and then copies them into the user space buffer pointed to by *buf.
__user表明参数是一个用户空间的指针,不能在kernel代码中直接访问。也方便其它工具对代码进行检查。
file-operations:
用来存储驱动内核模块提供的 设备进行各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的事务的函数的地址。
MAJOR(inode->i_rdev), MINOR(inode->i_rdev):
如果inode代表一个设备,则i_rdev的值为设备号。为了代码更好地可移植性,获取inode的major和minor号应该使用imajor和iminor函数
#include <linux/module.h> :
写内核驱动的时候 必须加载这个头文件,作用是动态的将模块加载到内核中去
#include <linux/version.h> :
当设备驱动需要同时支持不同版本内核时,在编译阶段,内核模块需要知道当前使用的内核源码的版本,从而使用相应的内核 API
try_module_get(&module), module_put(&module):
灵活的模块计数管理接口
#include <linux/kernel.h> :
kernel.h中包含了内核打印函数 printk函数 等
#include <linux/uaccess.h>:
包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。(copy_to_user()完成用户空间到内核空间的复制,函数copy_from_user()完成内核空间到用户空间的复制)
copy_to_user(buf, buffer, sizeof(buffer)//
#include <linux/fs.h> :
包含了文件操作相关struct的定义,例如大名鼎鼎的struct file_operations

#include <linux/init.h> :
内核模块的初始化和注销函数就在这个文件中

2. makefile:

# if KERNELRELEASE is defined, we've been invoked from the

# kernel build system and can use its language.

ifeq ($(KERNELRELEASE),)

# Assume the source tree is where the running kernel was built

# You should set KERNELDIR in the environment if it's elsewhere

KERNELDIR ?=  /lib/modules/$(shell uname -r)/build

# The current directory is passed to sub-makes as argument

PWD := $(shell pwd)

modules:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:

rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

.PHONY: modules modules_install clean

else

# called from kernel build system: just declare what our modules are

obj-m := mydriver.o

endif

3.

  1. test.c

#include <sys/types.h>

#include <sys/stat.h>

#include <stdlib.h>

#include <string.h>

#include <stdio.h>

#include <fcntl.h>

#include <unistd.h>

#define MAX_SIZE 1024

int main(void)

{

int fd;

char buf[MAX_SIZE];

char get[MAX_SIZE];

char devName[20], dir[49] = "/dev/";

system("ls /dev/");

printf("Please input the device's name you wanna to use :");

gets(devName);

strcat(dir, devName);

fd = open(dir, O_RDWR | O_NONBLOCK);

if (fd != -1)

{

read(fd, buf, sizeof(buf));

printf("The device was inited with a string : %s\n", buf);

// 测试写

printf("Please input a string  :\n");

gets(get);

write(fd, get, sizeof(get));

//测试读

read(fd, buf, sizeof(buf));

system("dmesg");

printf("\nThe string in the device now is : %s\n", buf);

close(fd);

return 0;

}

else

{

printf("Device open failed\n");

return -1;

}

}

以上源代码均可直接使用,以下是本人对基础知识的一些拙见,如果您有什么高见请务必指出,多谢各位大佬!

一.什么是Makefile
(1)KERNELRELEASE在linux内核源代码中的顶层makefile中有定义
(2)shell pwd会取得当前工作路径
(3)shell uname -r会取得当前内核的版本号
(4)由于make 后面没有目标,所以make会在Makefile中的第一个不是以.开头的目标作为默认的目标执行。
于是modules成为make的目标
(5)首先-C表示到存放内核的目录(KERNELDIR)执行其makefile,其中保存有内核的顶层makefile,M=选项的作用是,
当用户需要以某个内核为基础编译一个外部模块的话,需要在make modules 命令中加入M=$(PWD),
程序会自动到你所指定的dir目录中查找模块源码,将其编译,生成KO文件。
(6)obj-m是表示该文件要作为模块编译,只是编译得到globalmem.o而不链接进内核
(7)每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译
,也很利于保持文件的清洁——clean
(8)make modules_install是把编译好的模块拷贝到系统目录下(一般是/lib/modules/),
拷贝到系统目录下的目的是方便使用,加载驱动就使用modprobe globalmem命令,该命令从系统目录下查找名为globalmem的模块

二.什么是模块化编程?为什么要模块化编程?
(1)内核模块是一些可以让操作系统内核在需要时载入和执行的代码,同时在不需要的时候可以卸载。
这是一个好的功能,扩展了操作系统的内核功能,却不需要重新启动系统,是一种动态加载的技术。
(2)内核模块代码运行在内核空间,而应用程序在用户空间。应用程序的运行会形成新的进程,而内核模块一般不会
。每当应用程序执行系统调用时,Linux执行模式从用户空间切换到内核空间。
答:Linux 内核的整体结构非常庞大,其包含的组件也非常多。我们怎样把需要的部分都包含在内核中呢?
一种方法是把所有需要的功能都编译到 Linux 内核。
这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。
有没有一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,
其对应的代码可被动态地加载到内核中呢?
Linux 提供了这样的一种机制,这种机制被称为模块(Module),可以实现以上效果。模块具有以下特点。
1.模块本身不被编译入内核映像,从而控制了内核的大小。
2.模块一旦被加载,它就和内核中的其他部分完全一样。

三.如何理解设备号,什么是主设备号,什么是次设备号?

一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。
  linux内核中,设备号用dev_t来描述,2.6.28中定义如下:
  typedef u_long dev_t;
  在32位机中是4个字节,高12位表示主设备号,低12位表示次设备号。

四.整个驱动从编译到用测试程序进行测试的全过程,需要掌握相关的各种操作命令
1.在globalmen这个目录里面,先make一下,把globalmem.c编译成模块globalmem.ko
2.lsmod查看这个Linux里面有没有globalmem模块
3.insmod globalmem.ko 加载globalmem模块
4.lsmod查看是否加载成功
5.cat /proc/devices查看加载驱动程序时生成的设备 (proc目录是一个虚拟文件系统,
可以为linux用户空间和内核空间提供交互,它只存在于内存中,而不占实际的flash或硬盘空间)
6.mknod /dev/globalmem c 200 0 给globalmem设备创建设备节点,主设备号为200,次设备号为0
7.cd /dev 进入dev目录
8.ls 查看dev目录下是否有globalmem设备
9.echo '老师你真的很帅!'>/dev/globalmem 向globalmem设备写入一句话
10.cat /dev/globalmem 查看globalmem设备里面的内容
11.gcc -o test.c test 用gcc来编译测试程序
12../test 运行测试程序

五.三个重要的数据结构(结构体):file_operations、innode、file,尤其是file_operations
file文件结构:
struct file,定义于<linux/fs.h>.文件结构代表一个打开的文件.(它不特定给设备驱动;
系统中每个打开的文件有一个关联的 struct file 在内核空间).
它由内核在 open 时创建,并传递给在文件上操作的任何函数, 直到最后的关闭.
在文件的所有实例都关闭后, 内核释放这个数据结构
.struct file的指针常常称为filp("file pointer"),源代码里面没有这个结构体,
但是在函数里面会经常用到它,例如
int globalmem_open(struct inode *inode, struct file *filp);
int globalmem_release(struct inode *inode, struct file *filp);
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos);
static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos);
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig);
static long globalmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);

inode结构:
inode 结构由内核在内部用来表示文件. 因此, 它和代表打开文件描述符的文件结构是不同的.
可能有代表单个文件的多个打开描述符的许多文件结构, 但是它们都指向一个单个 inode 结构.
inode 结构包含大量关于文件的信息. 作为一个通用的规则, 这个结构只有 2 个成员对于编写驱动代码有用:
dev_ti_rdev;
对于代表设备文件的节点,这个成员包含实际的设备编号.
structcdev *i_cdev;
structcdev 是内核的内部结构, 代表字符设备; 这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时.

file_operations结构:
结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。
该结构体的每个域都对应着驱动内核模块用来处理某个被请求的 事务的函数的地址。
如下为C99语法的使用该结构体的方法,并且没有显示声明的结构体成员都被gcc初始化为NULL
static const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_ioctl,/* 在2.6.x的内核版本中,文件操作结构体中,
才会有ioctl的字段,高版本中使用unlocked_ioctl */
.open = globalmem_open,
.release = globalmem_release,
};

Linux 字符设备驱动及一些简单的Linux知识的更多相关文章

  1. 深入理解Linux字符设备驱动

    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...

  2. Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】

    本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042 一.字符设备基础知识 1.设备驱动分类 linux系统将设备分为3类:字符 ...

  3. Linux字符设备驱动基本结构

    1.Linux字符设备驱动的基本结构 Linux系统下具有三种设备,分别是字符设备.块设备和网络设备,Linux下的字符设备是指只能一个字节一个字节读写的设备,不能随机读取设备内存中某一数据,读取数据 ...

  4. (57)Linux驱动开发之三Linux字符设备驱动

    1.一般情况下,对每一种设备驱动都会定义一个软件模块,这个工程模块包含.h和.c文件,前者定义该设备驱动的数据结构并声明外部函数,后者进行设备驱动的具体实现. 2.典型的无操作系统下的逻辑开发程序是: ...

  5. Smart210学习记录----beep linux字符设备驱动

    今天搞定了beep linux字符设备驱动,心里还是很开心的,哈哈...但在完成的过程中却遇到了一个非常棘手的问题,花费了我大量的时间,,,, 还是把问题描述一下吧,好像这个问题很普遍的,网上许多解决 ...

  6. Linux字符设备驱动实现

    Linux字符设备驱动实现 要求 编写一个字符设备驱动,并利用对字符设备的同步操作,设计实现一个聊天程序.可以有一个读,一个写进程共享该字符设备,进行聊天:也可以由多个读和多个写进程共享该字符设备,进 ...

  7. linux字符设备驱动学习笔记(一):简单的字符设备驱动

    最近在鼓捣lnux字符设备驱动,在网上搜集的各种关于linux设备驱动的代码和注释,要么是针对2.4的,要么是错误百出,根本就不能运行成功,真希望大家在发博客的时候能认真核对下代码的正确性,特别是要把 ...

  8. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  9. Linux字符设备驱动

    一.字符设备基础 字符设备 二.字符设备驱动与用户空间访问该设备的程序三者之间的关系 三.字符设备模型 1.Linux内核中,使用 struct cdev 来描述一个字符设备 动态申请(构造)cdev ...

随机推荐

  1. python中的format

    format()格式化字符串,将占位符替换成内容,举个例子: 1 a = "hello {0} welcome to {1}" 2 a1 = a.format("dlrb ...

  2. 一、Jmeter环境介绍

    1.安装 从Apache网站http://jmeter.apache.org/download_jmeter.cgi下载二进制包. .tgz后缀名的为Linux使用的包 .zip后缀的为windows ...

  3. android stuido的代码排版的快捷建CTRL+ALT+L

    CTRL+ALT+L 需要主要留意的地方是QQ的与其冲突的 我将qq的中快捷方式给去除

  4. MySql Delete不走索引问题

    如果delete语句带有查询,写法不对会导致不走索引. 简单粗暴的办法:拆两条sql,一条查询,一条delete ======================= [不走索引的写法] DELETE FR ...

  5. Office2019都有哪些强大功能

    前阵子是微软一年一度的Ignite大会.而其中最引人注目.也是与我们一般人最息息相关的消息,当然是Office 2019的正式发布. 尽管Office 2019所更新的功能,对于Office 365的 ...

  6. idea 常见快捷键记录下

    keymaps 选择的是eclipse ctrl shif u              大小写转换 ctrl  o                   类方法列表 ctrl shif alt u  ...

  7. sourcetree 添加私钥

    参考网址: https://www.jianshu.com/p/2a4a39e3704f

  8. 腾讯助理PHP开发工程师外包岗面经

    校招错过腾讯了,在社招上看到腾讯有招外包岗,要求比正式岗低,于是抱着试一试的心态投了简历,没一会就收到了笔试题,还算简单. 第二天收到面试官的面试邀请,然后去面试了…… 腾讯里面真是漂亮,光是看装潢就 ...

  9. myeclipse提示错误。

    Syntax error, parameterized types are only available if source level is 1.5 解决方法:编译器问题.注意myeclipse10 ...

  10. PostgresQL 中有没有rownum这样的,显示结果集的序号

    select * from (select row_number() over() as rownum,tablename from pg_tables) t where rownum<10;