当前位置:文档之家› 精通Linux设备驱动程序开发-第7章-输入设备驱动

精通Linux设备驱动程序开发-第7章-输入设备驱动

精通Linux设备驱动程序开发-第7章-输入设备驱动
精通Linux设备驱动程序开发-第7章-输入设备驱动

第7章 输入设备驱动

内核的输入子系统是为了对分散的、多种不同类别的输入设备(如键盘、鼠标、跟踪球、操纵杆、辊轮、触摸屏、加速计和手写板)进行统一处理的驱动。输入子系统带

来了如下好处:

?统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论

PS/2、USB,还是蓝牙,都被同样处理。

?提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。X Windows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。

?抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬

件输入设备的访问。

图7.1展示了输入子系统的操作。此子系统包括一前一后运行的两类驱动:事件驱动和设备驱动。事件驱动负责和应用程序的接口,而设备驱动负责和底层输入设备的通信。鼠标事件产生者mousedev,是前者的实例;而PS/2鼠标驱动是后者的实例。事件驱动和设备驱动都可以利用输入子系统的高效、可重用的核心提供的服务。

图 7.1. 输入子系统

事件驱动是标准的,对所有的输入类都是可用的,所以你更可能的是实现设备驱动而不是事件驱动。你的设备驱动可以利用一个已经存在的、合适的事件驱动通过输入核心和用户应用程序接口。需要注意的是本章使用的名辞“设备驱动”指的是输入设备驱动,而不是输入事件驱动。

输入事件驱动

输入子系统提供的事件接口已经发展成为很多图形窗口系统理解的标准。事件驱动提供一个硬件无关的抽象,以和输入设备交互;如同帧缓冲接口(在第12章《视频设备驱动》中讨论)提供一个通用的机制以和显示设备通信一样。事件驱动和帧缓冲驱动一起,将图形用户接口(GUI)和各种各样的底层硬件隔离开来。

Evdev接口

Evdev是一个通用的输入事件驱动。Evdev产生的每个事件包都有如下格式,定义于i nclude/linux/input.h:

struct input_event {

struct timeval time; /* Timestamp */

__u16 ty pe; /* Event Ty pe */

__u16 code; /* Event Code */

__s32 value; /* Event Value */

};

为了学习如何使用evdev,让我们来实现一个虚拟鼠标的输入设备驱动。

设备例子:虚拟鼠标

我们的虚拟鼠标工作过程如下:一个应用程序(coord.c)模拟鼠标移动,并通过一个sysfs节点/sys/devices/platform/vms/coordinates分发坐标信息给虚拟鼠标驱动(vms. c)。虚拟鼠标驱动(vms驱动)通过evdev向上层传送这些移动信息。图7.2展示了详细过程:

图 7.2. 虚拟鼠标的输入驱动

通用目的鼠标(gpm)是服务器,让你在文本模式下使用鼠标,而无需X服务器。Gpm 能够理解evdev消息,因此vms驱动能够直接和其通信。一切就绪后,你将会看到随着由coord.c产生虚拟鼠标移动,光标在屏幕上跳动。

清单7.1包含coord.c,它连续产生随机的X和Y坐标。鼠标与操纵杆或触摸屏不同,它产生的是相对坐标[MS1] 而非绝对坐标,这就是coord.c所做的工作。vms驱动在清单7.2中。

清单 7.1. 模拟虚拟移动的应用程序(coord.c)

Code View:

#include

int

main(int argc, char *argv[])

{

int sim_fd;

int x, y;

char buffer[10];

/* Open the sysfs coordinate node */

sim_fd = open("/sys/devices/platform/vms/coordinates", O_RDWR); if (sim_fd < 0) {

perror("Couldn't open vms coordinate file\n");

exit(-1);

}

while (1) {

/* Generate random relative coordinates */

x = random()%20;

y = random()%20;

if (x%2) x = -x; if (y%2) y = -y;

/* Convey simulated coordinates to the virtual mouse driver */

sprintf(buffer, "%d %d %d", x, y, 0);

write(sim_fd, buffer, strlen(buffer));

fsync(sim_fd);

sleep(1);

}

close(sim_fd);

}

清单 7.2. Input Driver for the Virtual Mouse (vms.c)

Code View:

#inclu de

#include

#include

#include

#include

struct input_dev *vms_input_dev; /* Representation of an input device */ static struct platform_device *vms_dev; /* Device structure */

/* Sysfs method to input simulated

coordinates to the virtual

mouse driver */

static ssize_t

write_vms(struct device *dev,

struct device_attribute *attr,

const char *buffer, size_t count)

{

int x,y;

sscanf(buffer, "%d%d", &x, &y);

/* Report relative coordinates via the event interface */

input_report_rel(vms_input_dev, REL_X, x);

input_report_rel(vms_input_dev, REL_Y, y);

input_sync(vms_input_dev);

return count;

}

/* Attach the sysfs write method */

DEVICE_ATTR(coordinates, 0644, NULL, write_vms);

/* Attribute Descriptor */

static struct attribute *vms_attrs[] = {

&dev_attr_coordinates.attr,

NULL

};

/* Attribute group */

static struct attribute_group vms_attr_group = {

.attrs = vms_attrs,

};

/* Driver Initialization */

int __init

vms_init(void)

{

/* Register a platform device */

vms_dev = platform_device_register_simple("vms", -1, NULL, 0); if (IS_ERR(vms_dev)) {

PTR_ERR(vms_dev);

printk("vms_init: error\n");

}

/* Create a sysfs node to read simulated coordinates */

sysfs_create_group(&vms_dev->dev.kobj, &vms_attr_group);

/* Allocate an input device data structure */

vms_input_dev = input_allocate_device();

if (!vms_input_dev) {

printk("Bad input_alloc_device()\n");

}

/* Announce that the virtual mouse will generate

relative coordinates */

set_bit(EV_REL, vms_input_dev->evbit);

set_bit(REL_X, vms_input_dev->relbit);

set_bit(REL_Y, vms_input_dev->relbit);

/* Register with the input subsystem */

input_register_device(vms_input_dev);

printk("Virtual Mouse Driver Initialized.\n");

return 0;

}

/* Driver Exit */

void

vms_cleanup(void)

{

/* Unregister from the input subsystem */

input_unregister_device(vms_input_dev);

/* Cleanup sysfs node */

sysfs_remove_group(&vms_dev->dev.kobj, &vms_attr_group);

/* Unregister driver */

platform_device_unregister(vms_dev);

return;

}

module_init(vms_init);

module_exit(vms_cleanup);

让我们仔细阅读清单7.2中的代码。初始化期间,vms驱动注册自身为输入设备驱动。为此,它首先使用内核API input_allocate_device()分配input_dev结构:

vms_input_dev = input_allocate_device();

然后,声明虚拟鼠标产生相对性事件:

set_bit(EV_REL, vms_input_dev->evbit); /* 事件类型为EV_REL */

下一步,声明虚拟鼠标产生的事件的编码:

set_bit(REL_X, vms_input_dev->relbit); /* Relative 'X' movement */

set_bit(REL_Y, vms_input_dev->relbit); /* Relative 'Y' movement */

如果你的虚拟鼠标也能产生按钮点击事件,还需要将其加入vms_init():

set_bit(EV_KEY, vms_input_dev->evbit); /* Event Type is EV_KEY */

set_bit(BTN_0, vms_input_dev->keybit); /* Event Code is BTN_0 */

最后,进行注册:

input_register_device(vms_input_dev);

write_vms()是sysfs中的store()方法,和/sys/devices/platform/vms/coordinate s相关联。当coord.c写入X/Y坐标对进此文件时,write_vms()做如下操作:

input_report_rel(vms_input_dev, REL_X, x);

input_report_rel(vms_input_dev, REL_Y, y);

input_sync(vms_input_dev);

第一条语句会产生REL_X事件,或设备在X方向的相对移动。第二条语句产生REL_Y 事件,或设备在Y方向的相对移动。input_sync()表明此事件已经完成,因此输入子系统将这两个事件组成一个evdev包,并通过/dev/input/eventX发送出去,eventX中的X是分配给vms驱动的接口序号。读该文件的应用程序将以前面描述的input_event格式接收事件包。为了将gpm关联至此事件接口,从而追逐光标的跳动,需要做如下操作:

bash> gpm -m /dev/input/eventX -t evdev

在“触摸控制器”和后面的“加速度传感器”章节中讨论的ADS7846触摸控制器驱动以及加速度传感器驱动,也都使用了evdev。

更多的事件接口

vms驱动利用通用的evdev事件接口,但像键盘、鼠标和触摸屏这些输入设备必须使用定制的事件驱动。在讨论相应的设备驱动时,我们将一一学习。

为了编写你自己的事件驱动,并通过/dev/input/mydev提供给用户空间,你必须利用input_handler结构体,并将其向输入核心注册:

Code View:

static struct input_handler my_event_handler = {

.event = mydev_event, /* Handle event reports sent by

input device drivers that use

this event driver's services */

.fops = &my dev_fops, /* Methods to manage

/dev/input/mydev */

.minor = MYDEV_MINOR_BASE, /* Minor number of

/dev/input/mydev */

.name = "mydev", /* Event driver name */

.id_table = my dev_ids, /* This event driver can handle

requests from these IDs */

.connect = mydev_connect, /* Invoked if there is an

ID match */

.disconnect = my dev_disconnect, /* Called when the driver unregisters

*/

};

/* Driver Initialization */

static int __init

my dev_init(void)

{

/* ... */

input_register_handler(&my_event_handler);

/* ... */

return 0;

}

mousedev(drivers/input/mousedev.c)的具体实现中可以看到完整的例子。

输入设备驱动

下面,让我们将注意转向键盘、鼠标以及触摸屏这些通用输入设备的驱动上。但首先,让我们快速浏览一下那些输入驱动可以利用的、现成的硬件访问方法。

Serio

Serio层提供了访问老式输入硬件(如i8042兼容键盘控制器和串口)的库例程。PS/ 2键盘和鼠标与前者相连,串行触摸控制器和后者连接在一起。为了与serio提供服务的硬件通信,如发送命令给PS/2鼠标,需要用serio_register_driver()向serio注册规定的回调例程。

为了添加新的驱动作为serio的一部分,需要用serio_register_port()注册open()/ close()/start()/stop()/write()入口函数。drivers/input/serio/serport.c中可以看到具体的例子。

正如你在图7.1中所看到的,serio仅仅是访问底层硬件的一条路径。有些输入设备驱动依赖的是底层的总线层的支持,例如USB和SPI。

键盘

键盘多种多样――从老式的PS/2,到USB,蓝牙以及红外等。每种类型都有特定的输入设备驱动,但所有的都使用相同的键盘事件驱动,以确保提供给用户一致的接口。然而,和其它的事件驱动相比,键盘事件驱动有其独特之处:它传送数据给另一个内核子系统(tty层),而不是通过/dev节点传送给用户空间。

PC键盘

PC键盘(也成为PS/2键盘或AT键盘)通过i8042兼容键盘控制器与处理器接口。桌上电脑通常由专用的键盘控制器,而笔记本电脑的键盘接口由通用嵌入式控制器负责(参见第20章“嵌入式控制器”中的“更多的设备及驱动”)。当你在PC键盘上按下一个键时,会产生以下步骤:

1.键盘控制器(或嵌入式控制器)扫描键盘矩阵,译码,并做按键去抖处理。

2.键盘设备驱动在serio的帮助下,针对每个键的按下与释放,从键盘控制器读取原始的扫描码。按下与释放之间的区别在最高位,在释放时最高位被置位。例如,当“a”键被按下、释放后,会产生一对

扫描码:0x1e和0x9e。专用键用0xE0做转义,因此按下、释放右箭头键,产

生的序列为(0xE0 0x4D 0xE0 0xCD)。你可以使用showkey工具观察控制

器发出的扫描码(跟在->符号后面的是对前面内容的解释):

bash> showkey -s

kb mode was UNICODE

[ if you are trying this under X, it might not work since

the X server is also reading /dev/console ]

press any key (program terminates 10s after last

keypress)...

[MS2] ...

0x1e 0x9e -> "a" 键按下与松开

3.基于输入模式,键盘设备驱动转换接收到的扫描码为键值。为了查看和“a”键对应的键值:

bash> showkey

...

keycode 30 press -> A push of the "a" key

key code 30 release -> Release of the "a" key

为了向上报告键值,驱动产生一个输入事件,将控制权交给键盘事件驱动。

4.根据加载的键盘映射,键盘事件驱动进行键值翻译。(查看loadkeys的man帮助,以及在/lib/kbd/keymaps中提供的map文件)。它检查翻译后的键值是否和虚拟控制台或系统重启等相联系。

添加如下代码至键盘事件驱动(drivers/char/keyboard.c)的Ctrl+Alt+Del处理程序,可将Ctrl+Alt+Del的行为设置为点亮CAPSLOCK和NUMLOCK灯,而不是重启系统,

static void fn_boot_it(struct vc_data *vc,

struct pt_regs *regs)

{

+ set_vc_kbd_led(kbd, VC_CAPSLOCK);

+ set_vc_kbd_led(kbd, VC_NUMLOCK);

- ctrl_alt_del();

}

5.对于一般的键,译码后得到的键值被送至相联的虚拟终端,以及N_TTY线路规程(在第6章“串行驱动”中讨论过虚拟终端与线路规程)。这些由drivers/char/keyboard.c中的代码完成:

/* Add the keycode to flip buffer */

tty_insert_flip_char(tty, keycode, 0);

/* Schedule */

con_schedule_flip(tty);

N_TTY线路规程处理从键盘接收的输入,回送至虚拟控制台,以使用户空间的应用程序从与虚拟控制台相连的/dev/ttyX节点读取字符。

图7.3显示了从你按下键盘开始,到回显至虚拟控制台的整个过程的数据流。图的左半部是和特定硬件相关的,右半部是通用的。按照输入子系统的设计目的,底层硬件接口对键盘事件驱动和tty层是透明的。输入核心和定义明确的事件接口将input用户程序和复杂的硬件隔离开来。

图 7.3. PS/2兼容键盘的数据流

USB与蓝牙键盘

USB规范中有关人机接口设备(HID)的部分规定了USB键盘、鼠标、小键盘(Keypad)以及其他输入外设使用的通信协议。在Linux上,它们是通过usbhid USB客户端驱动来实现的,它负责USB HID类(0x03)设备。Usbhid注册自身作为输入设备驱动。它和输入AP I保持一致,并报告输入事件至相连的HID。

为了理解USB键盘的代码流,回到图7.3中,并修改左半部中的硬件相关部分。将输入硬件框中的键盘控制器用USB控制器代替,serio用USB核心层代替,输入设备驱动框用usbhid驱动代替即可。

对于蓝牙键盘,在图7.3中将键盘控制器用蓝牙芯片代替,serio用蓝牙核心层代替,输入设备驱动框用蓝牙hidp驱动代替。

USB和蓝牙在第11章“通用串行总线”和第16章“无线Linux”中分别详细讨论。鼠标

类似于键盘,鼠标接口选项多种多样。让我们从通用的看起。

PS/2鼠标

鼠标在X和Y坐标上产生相对移动,有一个或多个按钮,一些还有滚轮(scroll whee l)。PS/2兼容的老式鼠标依赖于serio层和底层的控制器交互。鼠标的输入事件驱动称为mousedev,通过/dev/input/mice报告鼠标事件给用户应用程序。

设备例子:辊轮鼠标(Roller Mouse)

为了感受真实的鼠标设备驱动,让我们将第4章“打下基础”中所讨论的辊轮(rolle r wheel)变换为通用PS/2鼠标的变种。“辊轮鼠标(roller mouse)”在Y轴产生一维的移动。辊轮(wheel)顺时针和逆时针的旋转分别产生正的和负的相对Y坐标(类似鼠标上的滚轮scroll wheel),按下辊轮则会产生鼠标左按钮事件。辊轮鼠标对于在智能手机、手持设备以及音乐播放器等设备上操作菜单是理想的选择。

辊轮鼠标设备驱动实现于清单7.3,工作于像X Windows这样的窗口系统。查看rolle r_mouse_init()可清楚此驱动如何实现类似鼠标的功能。不像第4章清单4.1中的辊轮驱动,辊轮鼠标驱动不需要read()和poll()方法,因为这些事件通过输入API来报告。辊轮中断处理例程roller_isr()也做了相应的改变。中断处理例程中的事务管理使用了一个等待队列,一个自旋锁,以及store_movement()例程用于支持read()和poll()。

清单7.3中开始的“+”和“-”指示了和第4章清单4.1中辊轮驱动实现的区别。

清单 7.3. 滚轮鼠标驱动

Code View:

+ #include

+ #include

+ /* Device structure */

+ struct {

+ /* ... */

+ struct input_dev dev;

+ } roller_mouse;

+ static int __init

+ roller_mouse_init(void)

+ {

+ /* Allocate input device structure */

+ roller_mouse->dev = input_allocate_device();

+

+ /* Can generate a click and a relative movement */

+ roller_mouse->dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);

+ /* Can move only in the Y-axis */

+ roller_mouse->dev->relbit[0] = BIT(REL_Y);

+

+ /* My click should be construed as the left button

+ press of a mouse */

+ roller_mouse->dev->key bit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT);

+ roller_mouse->dev->name = "roll";

+

+ /* For entries in /sy s/class/input/inputX/id/ */

+ roller_mouse->dev->id.bustype = ROLLER_BUS;

Linux设备驱动程序举例

Linux设备驱动程序设计实例2007-03-03 23:09 Linux系统中,设备驱动程序是操作系统内核的重要组成部分,在与硬件设备之间 建立了标准的抽象接口。通过这个接口,用户可以像处理普通文件一样,对硬件设 备进行打开(open)、关闭(close)、读写(read/write)等操作。通过分析和设计设 备驱动程序,可以深入理解Linux系统和进行系统开发。本文通过一个简单的例子 来说明设备驱动程序的设计。 1、程序清单 //MyDev.c 2000年2月7日编写 #ifndef __KERNEL__ #define __KERNEL__//按内核模块编译 #endif #ifndef MODULE #define MODULE//设备驱动程序模块编译 #endif #define DEVICE_NAME "MyDev" #define OPENSPK 1 #define CLOSESPK 2 //必要的头文件 #include //同kernel.h,最基本的内核模块头文件 #include //同module.h,最基本的内核模块头文件 #include //这里包含了进行正确性检查的宏 #include //文件系统所必需的头文件 #include //这里包含了内核空间与用户空间进行数据交换时的函数宏 #include //I/O访问 int my_major=0; //主设备号 static int Device_Open=0; static char Message[]="This is from device driver"; char *Message_Ptr; int my_open(struct inode *inode, struct file *file) {//每当应用程序用open打开设备时,此函数被调用 printk ("\ndevice_open(%p,%p)\n", inode, file); if (Device_Open) return -EBUSY;//同时只能由一个应用程序打开 Device_Open++; MOD_INC_USE_COUNT;//设备打开期间禁止卸载 return 0; } static void my_release(struct inode *inode, struct file *file)

虚拟声卡驱动程序VirtualAudioCable使用方法

一:安装软件 点击 选择是(Y) 选择I accept 选择Install 安装成功,点击“确定”按钮即完成安装。 二、软件的设置 点击桌面开始按钮所有程序---Virtual Audio Cable —Control panel 进入软件初始化 设置。 在Cables 中选择1(即首次设置一个虚拟通道),点击旁边的Set 按钮生成通道Cable1. 在参数设置区将Line 、Mic (可选可不选)、S/PDIF (可选可不选)三个选项后面的方框打钩,选中之后点击参数设置区内的设置按钮Set ,即完成了,对虚拟声卡通道1 的设置。 鼠标右键点击桌面右下角的喇叭------ 调整音频属性---- < 或者点击开始—控制面板--- 声音、 语音和音频设备--- 声音和音频设备>弹出: 选择语音 此时语音部分的设置为原系统默认的设备,保持不变。 选择音频: 改变声音播放、录音的选项内容:

如上图将声音播放、录音的默认设备全部改为Virtual Cable 1 。点击应用--- 确定即可。 三、打开录音机录音--- 录制电脑里播放出来的音频(不包含麦克风 里的声音) - 即“内录” 开始--- 所有程序—附件--- 娱乐--- 录音机 点击确定即可开始录音(注:此时可在电脑中打开相应的音频文件,开始录音) 此时音频波段显示有声音输入,但是电脑的耳机听不到正在播放的音频文件(属正常现象)。若想同时听到音频文件的内容点击桌面开始按钮所有程序---Virtual Audio Cable —Audio Repeater 。 修改为 点击Start 即可听到正在录制的音频文件。此时的录音即是通过虚拟声卡通道录制电脑里的声音的。 四、同时录电脑里播放的声音和麦克风收集的外部声音----- 即混录 <通过这种方法解决现有笔记本无“立体声混音”或“波形音”选项的问题> 在《三打开录音机录音--- 录制电脑里播放出来的音频(不包含麦克风里的声音)------------ 即“内录”》的同时,在打开一个irtual Audio Cable —Audio Repeater 窗口将其设置为: 即将外部麦克风收集的声音转移到虚拟声卡通道Cable1 中,同电脑里播放的声音一起被录音软件收录为音频文件。

Linux网络设备驱动开发实验

实验三:Linux网络设备驱动开发实验 一、实验目的 读懂linux网络设备驱动程序例子,并且实际加载驱动程序,加载进操作系统以后,会随着上层应用程序的触发而执行相应动作,具体执行的动作可以通过代码进行改变。 ●读懂源码及makefile ●编译驱动程序 ●加载 ●多种形式触发动作 二、预备知识 熟悉linux驱动基本原理,能读懂简单的makefile。 三、实验预计时间 80-120分钟左右 四、驱动程序部分具体步骤 要求读懂一个最简单的驱动程序,在驱动程序的诸如“xxx_open”、“xxx_read”等标准接口里面加入打印语句。可参考多模式教学网上的驱动样例。 五、用于触发驱动动作的应用程序及命令 驱动程序就是以静态的标准接口库函数形式存在,网络设备驱动会受到两大类情况的触发,一种是linux里面的控制台里面的命令,另一种是套接口应用程序,首先要搞清都有哪些具体的命令和应用程序流程,应用程序参考多模式教学网的例子。 六、运行测试 提示:需要将驱动程序以dll加载进系统中,并且触发应用程序调用各种文件操作的接口函数,使得驱动有所动作,打印出相关信息。 1.编译驱动: cd /某某目录/vnetdev/ make clean make 2.加载驱动与打开网卡: insmod netdrv.ko

ifconfig vnet0 up 3.运行应用程序 ../raw 4.通过命令“修改网卡MTU”触发驱动执行动作: ifconfig vnet0 mtu 1222 5.显示内核打印: cat /var/log/messages 6.卸载: ifconfig vnet0 down rmmod netdrv.ko 7.修改代码中的某些函数中的打印信息,重新试验上述流程。 至此大家都应该真正理解和掌握了驱动程序-操作系统-应用程序的三者联动机制。 七、实验结果 由图可知能正常加载网卡驱动,并且能够打印调试信息。

一个简单的演示用的Linux字符设备驱动程序.

实现如下的功能: --字符设备驱动程序的结构及驱动程序需要实现的系统调用 --可以使用cat命令或者自编的readtest命令读出"设备"里的内容 --以8139网卡为例,演示了I/O端口和I/O内存的使用 本文中的大部分内容在Linux Device Driver这本书中都可以找到, 这本书是Linux驱动开发者的唯一圣经。 ================================================== ===== 先来看看整个驱动程序的入口,是char8139_init(这个函数 如果不指定MODULE_LICENSE("GPL", 在模块插入内核的 时候会出错,因为将非"GPL"的模块插入内核就沾污了内核的 "GPL"属性。 module_init(char8139_init; module_exit(char8139_exit; MODULE_LICENSE("GPL"; MODULE_AUTHOR("ypixunil"; MODULE_DESCRIPTION("Wierd char device driver for Realtek 8139 NIC"; 接着往下看char8139_init( static int __init char8139_init(void {

int result; PDBG("hello. init.\n"; /* register our char device */ result=register_chrdev(char8139_major, "char8139", &char8139_fops; if(result<0 { PDBG("Cannot allocate major device number!\n"; return result; } /* register_chrdev( will assign a major device number and return if it called * with "major" parameter set to 0 */ if(char8139_major == 0 char8139_major=result; /* allocate some kernel memory we need */ buffer=(unsigned char*(kmalloc(CHAR8139_BUFFER_SIZE, GFP_KERNEL; if(!buffer { PDBG("Cannot allocate memory!\n"; result= -ENOMEM;

编译hello设备驱动程序详细过程

编译hello world设备驱动程序详细过程 1、安装与你的开发板相同的内核版本的虚拟机,我的板子内核是2.6.8.1,虚拟机是2.6.9, 一般是虚拟机的内核只能比板子内核新,不能旧 #uanme –a [1](在任何目录下,输入此命令,查看虚拟机的内核版本,我的内核版本是2.6.9) 2、在虚拟机上设置共享目录,我的共享目录在linux下的/mnt/hgfs/share [2]share是自己命名的,我的物理机上,即Windows下的目录是G:/share, 3、在Windows下,把开发板的的交叉开发工具链[3],内核源码包[4],复制到物理机的共享目录下[5] 即Windows下的目录是G:/share, 4、#cp /mnt/hgfs/share/cross-3.3.2.tar.bz2 /usr/local/arm [6] 在Linux下,把交叉工具链,复制到/usr/local/arm目录下 5、#cd /usr/local/arm 6、#tar jxvf cross-3.3.2.tar.bz2 [7] 并在当前目录/usr/local/arm下解压它cross-2.95.3.tar.bz2和gec2410-linux-2.6.8.tar.bz2也是用同样的命令去解压 7、#export PATH=/usr/local/arm/3.3.2/bin:$PATH [8] 安装交叉工具链,在需要使用交叉编译时,只要在终端输入如下命令 #export PATH=/usr/local/arm/版本/bin:$PATH 即可,在需要更改不同版本的工具链时,重新启动一个终端,然后再一次输入上面的命令即可,使用哪个版本的交叉工具链,视你要编译的内核版本决定,编译2.4版本的内核,则用2.95.3版本的交叉工具链,而2.6版本内核的,则要用3.3.2版本的交叉工具链。 8、#cp gec2410-linux-2.6.8.tar.bz2 /root [9]把内核拷贝到/root目录下, 9、#cd /root 10、#tar gec2410-linux-2.6.8.tar.bz2 [10] 在/root解压开发板的内核源码压缩包gec2410-linux-2.6.8.tar.bz2,得到gec2410-linux-2.6.8.1文件夹 11、#cd /root/ gec2410-linux-2.6.8.1 12、#cp gec2410.cfg .config [11] gec2410.cfg文件是广嵌开发板提供的默认内核配置文件,在这里首先把内核配置成默认配置,然后在此基础上用make menuconfig进一步配置,但在这里,不进行进一步的配置,对于内核配置,还需要看更多的知识,在这里先存疑。 13、#make [12]在内核源代码的根目录gec2410-linux-2.6.8.1下用make命令编译内核,注意,先安装交叉工具链,再编译内核,因为这里编译的hello.ko驱动模块最终是下载到开发板上运行的,而不是在虚拟机的Linux系统运行的,如果是为了在虚拟机的Linux系统运行的,则不用安装交叉编译工具链arm-linux-gcc,而直接用gcc,用命令#arm-linux-gcc –v 可以查看当前已经安装的交叉编译工具链的版本。这里编译内核不是为了得到内核的映象文件zImage(虽然会得到内核的映象文件zImage),而是为了得到编译hello.o模块需要相关联,相依赖(depends on)的模块。 14、#cd /root 12、#mkdir hello [13]在/root目录下建立hello文件夹, 13、#cd hel 14 、#vi hello.c [12]编辑hello.c文件,内容是《Linux设备驱动程序》第三版22页的hello world程序。 15、#vi Makefile [13]在hello文件夹下编辑Makefile文件, 16、obj-m := module.o [14] 这是Makefile的内容,为obj-m := module.omodule.o视你编辑的.c文件而定,这里则要写成hello.o,写完后,保存退出。 17、cd /root/hello

Linux网络设备驱动

嵌入式培训专家
Linux网络设备驱动
主讲:宋宝华
https://www.doczj.com/doc/8818419527.html,

华清远见
今天的内容
vLinux网络设备驱动架构 vLinux网络设备驱动数据流程
? NON-NAPI模式数据接收流程 ? NAPI模式数据接收流程 ? 数据发送流程
vLinux网络协议栈的实现
? TCP/UDP/IP/MAC各层数据传递 ? 网络系统调用与socket

华清远见
Linux网络设备驱动架构

华清远见
net_device
struct net_device_ops { int (*ndo_open)(struct net_device *dev); int (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev); int (*ndo_set_mac_address)(struct net_device *dev, void *addr); int (*ndo_do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd); void (*ndo_tx_timeout) (struct net_device *dev); ... }
struct net_device { struct net_device_stats stats; const struct net_device_ops *netdev_ops; const struct ethtool_ops *ethtool_ops; ... }
struct ethtool_ops { int (*get_settings)(struct net_device *, struct ethtool_cmd *); int (*set_settings)(struct net_device *, struct ethtool_cmd *); void (*get_drvinfo)(struct net_device *, struct ethtool_drvinfo *); int (*get_regs_len)(struct net_device *); ... }

Linux设备驱动程序学习(18)-USB 驱动程序(三)

Linux设备驱动程序学习(18)-USB 驱动程序(三) (2009-07-14 11:45) 分类:Linux设备驱动程序 USB urb (USB request block) 内核使用2.6.29.4 USB 设备驱动代码通过urb和所有的 USB 设备通讯。urb用 struct urb 结构描述(include/linux/usb.h )。 urb以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个 urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。 一个 urb 的典型生命循环如下: (1)被创建; (2)被分配给一个特定 USB 设备的特定端点; (3)被提交给 USB 核心; (4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动; (5)被 USB 主机控制器驱动处理, 并传送到设备; (6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。 urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB 核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。 struct urb

struct list_head urb_list;/* list head for use by the urb's * current owner */ struct list_head anchor_list;/* the URB may be anchored */ struct usb_anchor *anchor; struct usb_device *dev;/* 指向这个 urb 要发送的目标 struct usb_device 的指针,这个变量必须在这个 urb 被发送到 USB 核心之前被USB 驱动初始化.*/ struct usb_host_endpoint *ep;/* (internal) pointer to endpoint */ unsigned int pipe;/* 这个 urb 所要发送到的特定struct usb_device 的端点消息,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.必须由下面的函数生成*/ int status;/*当 urb开始由 USB 核心处理或处理结束, 这个变量被设置为 urb 的当前状态. USB 驱动可安全访问这个变量的唯一时间是在 urb 结束处理例程函数中. 这个限制是为防止竞态. 对于等时 urb, 在这个变量中成功值(0)只表示这个 urb 是否已被去链. 为获得等时 urb 的详细状态, 应当检查 iso_frame_desc 变量. */ unsigned int transfer_flags;/* 传输设置*/ void*transfer_buffer;/* 指向用于发送数据到设备(OUT urb)或者从设备接收数据(IN urb)的缓冲区指针。为了主机控制器驱动正确访问这个缓冲, 它必须使用 kmalloc 调用来创建, 不是在堆栈或者静态内存中。对控制端点, 这个缓冲区用于数据中转*/ dma_addr_t transfer_dma;/* 用于以 DMA 方式传送数据到 USB 设备的缓冲区*/ int transfer_buffer_length;/* transfer_buffer 或者 transfer_dma 变量指向的缓冲区大小。如果这是 0, 传送缓冲没有被 USB 核心所使用。对于一个 OUT 端点, 如果这个端点大小比这个变量指定的值小, 对这个USB 设备的传输将被分成更小的块,以正确地传送数据。这种大的传送以连续的 USB 帧进行。在一个 urb 中提交一个大块数据, 并且使 USB 主机控制器去划分为更小的块, 比以连续地顺序发送小缓冲的速度快得多*/

linux设备驱动中常用函数

Linux2.6设备驱动常用的接口函数(一) ----字符设备 刚开始,学习linux驱动,觉得linux驱动很难,有字符设备,块设备,网络设备,针对每一种设备其接口函数,驱动的架构都不一样。这么多函数,要每一个的熟悉,那可多难啦!可后来发现linux驱动有很多规律可循,驱动的基本框架都差不多,再就是一些通用的模块。 基本的架构里包括:加载,卸载,常用的读写,打开,关闭,这是那种那基本的咯。利用这些基本的功能,当然无法实现一个系统。比方说:当多个执行单元对资源进行访问时,会引发竞态;当执行单元获取不到资源时,它是阻塞还是非阻塞?当突然间来了中断,该怎么办?还有内存管理,异步通知。而linux 针对这些问题提供了一系列的接口函数和模板框架。这样,在实际驱动设计中,根据具体的要求,选择不同的模块来实现其功能需求。 觉得能熟练理解,运用这些函数,是写号linux设备驱动的第一步。因为是设备驱动,是与最底层的设备打交道,就必须要熟悉底层设备的一些特性,例如字符设备,块设备等。系统提供的接口函数,功能模块就像是工具,能够根据不同的底层设备的的一些特性,选择不同的工具,方能在linux驱动中游刃有余。 最后就是调试,这可是最头疼的事。在调试过程中,总会遇到这样,那样的问题。怎样能更快,更好的发现并解决这些问题,就是一个人的道行咯!我个人觉得: 发现问题比解决问题更难! 时好时坏的东西,最纠结! 看得见的错误比看不见的错误好解决! 一:Fops结构体中函数: ①ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以-EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型). ②ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数 ③loff_t (*llseek) (struct file *, loff_t, int); llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述). ④int (*open) (struct inode *, struct file *);

常用网络设备设备

物理层设备 1.调制解调器 调制解调器的英文名称为modem,来源于Modulator/Demodulator,即调制器/解调器。 ⑴工作原理 调制解调器是由调制器与解调器组合而成的,故称为调制解调器。调制器的基本职能就是把从终端设备和计算机送出的数字信号转变成适合在电话线、有线电视线等模拟信道上传输的模拟信号;解调器的基本职能是将从模拟信道上接收到的模拟信号恢复成数字信号,交给终端计算机处理。 ⑵调制与解调方式 调制,有模拟调制和数字调制之分。模拟调制是对载波信号的参量进行连续地估值;而数字调制使用载波信号的某些离散状态来表征所传送的信息,在接收端对载波信号的离散参量进行检测。调制是指利用载波信号的一个或几个参数的变化来表示数字信号的一种过程。 调制方式相应的有:调幅、调频和调相三种基本方式。 调幅:振幅调制其载波信号将随着调制信号的振幅而变化。 调频:载波信号的频率随着调制信号而改变。 调相:相位调制有两相调制、四相调制和八相调制几种方式。 ⑶调制解调器的分类 按安装位置:调解解调器可以分为内置式和外置式 按传输速率分类:低速调制解调器,其传输速率在9600bps以下;中速调制解调器,其传输速率在9.6~19.2kbps之间;高速调制解调器,传输速率达到19.2~56kbps。 ⑷调制解调器的功能 ?差错控制功能:差错控制为了克服线路传输中出现的数据差错,实现调制解调器至远端调制解调器的无差错数据传送。 ?数据压缩功能:数据压缩功能是为了提高线路传输中的数据吞吐率,使数据更快地传送至对方。 ⑸调制解调器的安装 调制解调器的安装由两部分组成,线路的连接和驱动程序的安装。 线路连接: ?将电话线引线的一端插头插入调制解调器后面LINE端口。

Linux设备驱动模型之platform总线深入浅出

Linux设备驱动模型之platform总线深入浅出 在Linux2.6以后的设备驱动模型中,需关心总线,设备和驱动这三种实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。 对于依附在USB、PCI、I2C、SPI等物理总线来这些都不是问题。但是在嵌入式系统里面,在Soc系统中集成的独立外设控制器,挂接在Soc内存空间的外设等却不依附在此类总线。基于这一背景,Linux发明了一种总线,称为platform。相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。 platform总线相关代码:driver\base\platform.c 文件相关结构体定义:include\linux\platform_device.h 文件中 platform总线管理下最重要的两个结构体是platform_device和platform_driver 分别表示设备和驱动在Linux中的定义如下一:platform_driver //include\linux\platform_device.h struct platform_driver { int (*probe)(struct platform_device *); //探测函数,在注册平台设备时被调用int (*remove)(struct platform_device *); //删除函数,在注销平台设备时被调用void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); //挂起函数,在关机被调用int (*suspend_late)(struct platform_device *, pm_message_t state); int (*resume_early)(struct platform_device *); int (*resume)(struct platform_device *);//恢复函数,在开机时被调用struct device_driver driver;//设备驱动结构}; 1 2 3 4 5 6 7 8

虚拟设备驱动程序的设计与实现

虚拟设备驱动程序的设计与实现 由于Windows对系统底层操作采取了屏蔽的策略,因而对用户而言,系统变得 更为安全,但这却给众多的硬件或者系统软件开发人员带来了不小的困难,因为只要应用中涉及到底层的操作,开发人员就不得不深入到Windows的内核去编写属 于系统级的虚拟设备驱动程序。Win 98与Win 95设备驱动程序的机理不尽相同,Win 98不仅支持与Windows NT 5.0兼容的WDM(Win32 Driver Mode)模式驱动程序 ,而且还支持与Win 95兼容的虚拟设备驱动程序VxD(Virtual Device Driver)。下面介绍了基于Windows 9x平台的虚拟环境、虚拟设备驱动程序VxD的基本原理和 设计方法,并结合开发工具VToolsD给出了一个为可视电话音频卡配套的虚拟设备 驱动程序VxD的设计实例。 1.Windows 9x的虚拟环境 Windows 9x作为一个完整的32位多任务操作系统,它不像Window 3.x那样依 赖于MS-DOS,但为了保证软件的兼容性,Windows 9x除了支持Win16应用程序和 Win32应用程序之外,还得支持MS-DOS应用程序的运行。Windows 9x是通过虚拟机 VM(Virtual Machine)环境来确保其兼容和多任务特性的。 所谓Windows虚拟机(通常简称为Windows VM)就是指执行应用程序的虚拟环 境,它包括MS-DOS VM和System VM两种虚拟机环境。在每一个MS-DOS VM中都只运 行一个MS-DOS进程,而System VM能为所有的Windows应用程序和动态链接库DLL(Dynamic Link Libraries)提供运行环境。每个虚拟机都有独立的地址空间、寄存器状态、堆栈、局部描述符表、中断表状态和执行优先权。虽然Win16、Win32应用程序都运行在System VM环境下,但Win16应用程序共享同一地址空间, 而Win32应用程序却有自己独立的地址空间。 在编写应用程序时,编程人员经常忽略虚拟环境和实环境之间的差异,一般认为虚拟环境也就是实环境。但是,在编写虚拟设备驱动程序VxD时却不能这样做 ,因为VxD的工作是向应用程序代码提供一个与硬件接口的环境,为每一个客户虚 拟机管理虚设备的状态,透明地仲裁多个应用程序,同时对底层硬件进行访问。这就是所谓虚拟化的概念。 VxD在虚拟机管理器VMM(Virtual Machine Manager)的监控下运行,而VMM 实 际上是一个特殊的VxD。VMM执行与系统资源有关的工作,提供虚拟机环境(能产

实验七 驱动程序设计实验

实验七 驱动程序设计实验 1. 实验目的 该实验是编写最简单的字符驱动程序,这里的设备也就是一段内存,实现简单的读写功能,并列出常用格式的Makefile 以及驱动的加载和卸载脚本。读者可以体会到字符设备驱动的整个编写流程。 2. 实验内容 该实验要求实现对虚拟设备(一段内存)的打开、关闭、读写的操作,并要通过编写测试程序来测试虚拟设备及其驱动运行是否正常。 3. 实验步骤 (1) 编写代码。 (2) 编译代码。 (3) 加载和卸载模块。 (4) 编写测试代码。 4. 实验结果 首先在虚拟设备驱动源码目录下编译并加载驱动模块。再编译并运行测试程序。 测试程序运行效果如下: 最后,卸载驱动程序: $ make clean;make $ ./test_drv_load $ gcc –o test test.c $ ./test Input some words to kernel(enter 'quit' to exit):Hello, everybody! The read string is from kernel:Hello, everybody! /* 从内核读取的数据 */ Input some words to kernel(enter 'quit' to exit):This is a simple driver The read string is from kernel: This is a simple driver Input some words to kernel(enter 'quit' to exit):quit The read string is from kernel:quit

驱动程序安装方法

驱动程序安装方法 初识电脑的人,可能为安装驱动程序而头疼。因为对驱动程序了解得不多就会在安装过程中走不少弯路,下面就给大家介绍一下安装驱动程序的两种常用方法和一些实用技巧。 一、安装即插即用设备的驱动程序 安装前的准备工作很重要,一般我们拿到要安装的新硬件时,首先要查看外包装盒,了解产品的型号、盒内部件及产品对系统的最低要求等信息。紧接着就要打开包装盒,取出硬件产品、说明书和驱动盘(光盘或软盘),认真阅读说明书或驱动盘上的ReadMe 文件,一般说明书上写有安装方法和步骤,以及安装注意事项。除了阅读说明书外,还应记得硬件产品上印刷的各种信息以及板卡产品使用的主要芯片的型号。这些信息就是确定产品型号及厂家的重要依据,只有知道这些,才能在网上查找最新的驱动程序。最后按照说明书上介绍的方法来安装硬件。通常安装内置板卡、内置驱动器,使用串口或PS /2接口的设备都应关机断电后再操作,而安装USB设备、笔记本电脑的PC卡时可以带电热插拔。当然,如果是Win2000系统则均可热插拔。完成前面的准备工作之后,就可以启动Windows 来安装驱动程序了。通常情况下,Windows 能够自动检测到PCI 卡、AGP卡、ISA卡、USB设备以及多数打印机和扫描仪等外设,并提示用户插入安装盘。以YAMAHA724声卡为例,其在Win98下安装驱动程序的详细步骤如下。 1.Win98在启动过程中会自动检测即插即用设备,一旦发现了新设备,并且在INF目录下有该设备的.inf 文件,系统将自动安装驱动程序;如果这是一个新设备,INF目录下没有相应的.inf 文件,那么系统就会启动硬件向导。我们单击“下一步”让安装向导自动搜索设备驱动程序,然后再单击“下一步”。 2.在图3中只选中“指定位置”,插入驱动光盘,并单击“浏览”,根据说明书的介绍,选择简体中文版驱动程序所在的目录“E:\Lx_so u n d /Yamaha /Win9X”,点“确定”后单击“下一步”。需要注意的是:Win95的安装向导没有自动搜索功能,我们必须选择“从磁盘安装”,并指定驱动程序所在的位置。驱动程序所在的目录通常是驱动盘上的“Win95”、“Win9X”或“Windows98”目录。 3.硬件安装向导会在指定目录下查找与设备相符的.inf 文件,此例中,硬件向导将在指定目录下找到并向作户报告发现YAMAHA724声卡驱动程序,继续按“下一步”。 4.硬件安装向导显示Windows 准备安装的驱动程序的信息,单击“下一步”后,硬件向导便会根据.inf 文件的内容把指定的文件拷贝到相应的目录下,并在注册表中写入相应的信息,安装成功后显示出对话框。 5.对多数设备而言,到这里驱动程序就算安装完毕了。但如果你安装的是声卡那就还未结束,因为刚才的步骤只能装完声卡的主体部分。单击“完成”后,Windows 又会报告发现了两个新硬件,分别是声卡的DOS 仿真部件和声卡上的游戏控制端口。由于此时SBPCI9X.inf 文件已经被拷到“Windows /INF /Other”子目录下,所以Windows 能够自动安装好这两种设备的驱动程序。 6.驱动程序安装完毕后,我们需要检查设备能否正常工作。检查前还要进行额外的设置,例如使用网卡之前必须先安装和设置网络协议,用调制解调器上网之前要先“新建连接”等。此例中,在“控制面板”里打开“系统”→“设备管理器”→“声音、视频和游戏控制器”,可以看见下面多了三个设备,只要设备的小图标上没有黄色惊叹号,就表示驱动程序运行正常。 二、安装非即插即用设备的驱动程序

USB上位机开发指南

第10章 上位机程序开发 在USB设备开发中,上位机程序是用于与用户进行接口的。上位机程序通过USB设备驱动程序和外部的USB硬件进行通信,USB固件程序执行所用的硬件操作。一般来说,根据选择开发平台的不同,可以使用Visual C++、Visual C#和LabVIEW等开发上位机程序。 本章首先介绍了Visual C++中控制USB设备的相关函数,接着介绍了Visual C#中读写USB设备的主意函数,最后介绍了在LabVIEW中如何读写USB设备。本章内容包括: Visual C++读写USB设备; Visual C#读写USB设备; LabVIEW读写USB设备。 10.1 Visual C++读写USB设备 在USB设备开发过程中,上位机程序可以采用广泛应用的Visual C++来实现。对于Cypress公司的EZ-USB系列芯片,其提供了全面的CY3684开发包。在该开发包中,可以使用CYIOCTL控制函数类和CyAPI控制函数类来实现Visual C++环境下对USB设备的读写。 10.1.1 CYIOCTL控制函数类 CYIOCTL控制函数类为Cypress公司的EZ-USB FX2LP系列USB接口芯片,提供了简单的控制接口。在使用Cypress公司提供的驱动程序基础上,只需在主机Visual C++程序中加入头文件cyioctl.h,然后便可以调用相应的控制函数。 为了能够使用这些函数,主机程序必须首先获得USB设备的控制句柄。可以通过以下的代码在程序中获得连接到主机的USB设备句柄。 CCyUSBDevice *USBDevice = new CCyUSBDevice(); //USB设备 HANDLE hDevice = USBDevice->DeviceHandle(); //打开设备句柄 其中,hDevice即为获得的USB设备句柄。在退出程序的时候,需要释放该USB设备句柄,使用如下的语句即可: delete USBDevice; 在主程序获得USB设备的控制句柄后,便可以调用CYIOCTL控制函数类提供的接口控制函数,下面分别进行介绍。 1.中止I/O端点的请求接口IOCTL_ADAPT_ABORT_PIPE 中止I/O端点的请求接口IOCTL_ADAPT_ABORT_PIPE用于中止I/O端点的请求,其使用示例代码如下: DWORD dwBytes = 0; =0x82; //地址 Address UCHAR DeviceIoControl(hDevice, IOCTL_ADAPT_ABORT_PIPE, //DeviceIoControl函数 &Address, sizeof (UCHAR),

Linux设备驱动程序学习(20)-内存映射和DMA-基本概念

Linux设备驱动程序学习(20)-内存映射和DMA-基本概念 (2011-09-25 15:47) 标签: 虚拟内存设备驱动程序Linux技术分类:Linux设备驱动程序 这部分主要研究 Linux 内存管理的基础知识, 重点在于对设备驱动有用的技术. 因为许多驱动编程需要一些对于虚拟内存(VM)子系统原理的理解。 而这些知识主要分为三个部分: 1、 mmap系统调用的实现原理:它允许设备内存直接映射到一个用户进程地址 空间. 这样做对一些设备来说可显著地提高性能. 2、与mmap的功能相反的应用原理:内核态代码如何跨过边界直接存取用户空间的内存页. 虽然较少驱动需要这个能力. 但是了解如何映射用户空间内存到内 核(使用 get_user_pages)会有用. 3、直接内存存取( DMA ) I/O 操作, 它提供给外设对系统内存的直接存取. 但所有这些技术需要理解 Linux 内存管理的基本原理, 因此我将先学习VM子 系统的基本原理. 一、Linux的内存管理 这里重点是 Linux 内存管理实现的主要特点,而不是描述操作系统的内存管理理论。Linux虚拟内存管理非常的复杂,要写可以写一本书:《深入理解Linux 虚拟内存管理》。学习驱动无须如此深入, 但是对它的工作原理的基本了解是必要的. 解了必要的背景知识后,才可以学习内核管理内存的数据结构. Linux是一个虚拟内存系统(但是在没有MMU的CPU中跑的ucLinux除外), 意味着在内核启动了MMU 之后所有使用的地址不直接对应于硬件使用的物理地址,这些地址(称之为虚拟地址)都经过了MMU转换为物理地址之后再从CPU的内存总线中发出,读取/写入数据. 这样 VM 就引入了一个间接层, 它是许多操作成为可能: 1、系统中运行的程序可以分配远多于物理内存的内存空间,即便单个进程都可拥有一个大于系统的物理内存的虚拟地址空间. 2、虚拟内存也允许程序对进程的地址空间运用多种技巧, 包括映射程序的内存到设备内存.等等~~~ 1、地址类型 Linux 系统处理几种类型的地址, 每个有它自己的含义: 用户虚拟地址:User virtual addresses,用户程序见到的常规地址. 用户地址在长度上是 32 位或者 64 位, 依赖底层的硬件结构, 并且每个进程有它自己 的虚拟地址空间.

实验七(补充)RamDisk块设备驱动实例开发讲解

RamDisk块设备驱动实例开发讲解 主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。 共享资源,欢迎转载:https://www.doczj.com/doc/8818419527.html, 一、开发环境 主机:VMWare--Fedora 9 开发板:Mini2440--64MB Nand, Kernel:2.6.30.4 编译器:arm-linux-gcc-4.3.2 二、块设备基本概念 扇区(Sectors):任何块设备硬件对数据处理的基本单位。通常,1个扇区的大小为512byte。 块(Blocks):由Linux制定对内核或文件系统等数据处理的基本单位。通常,1个块由1个或多个扇区组成。 段(Segments):由若干个相邻的块组成。是Linux内存管理机制中一个内存页或者内存页的一部分。 页、段、块、扇区之间的关系图如下: 综合上描述:块设备驱动是基于扇区(sector)来访问底层物理磁盘,基于块(block)来访问上层文件系统。扇区一般是2的n次方大小,典型为512B,内核也要求块是2的n次方大小,且块大小通常为扇区大小的整数倍,并且块大小要小于页面大小,典型大小为512B、1K或4K。 三、块设备在Linux中的结构 1.首先我们来看一下,块设备在整个Linux中应用的总体结构,如图:

2.块设备驱动层(Block Device Driver)在总体结构中扮演的角色。 从上图可以看出,块设备的应用在Linux中是一个完整的子系统。 首先,我们先看一下,块设备驱动是以何种方式对块设备进行访问的。在Linux中,驱动对块设备的输入或输出(I/O)操作,都会向块设备发出一个请求,在驱动中用request结构体描述。但对于一些磁盘设备而言请求的速度很慢,这时候内核就提供一种队列的机制把这些I/O请求添加到队列中(即:请求队列),在驱动中用request_queue结构体描述。在向块设备提交这些请求前内核会先执行请求的合并和排序预操作,以提高访问的效率,然后再由内核中的I/O调度程序子系统(即:上图中的I/O调度层)来负责提交I/O请求,I/O调度程序将磁盘资源分配给系统中所有挂起的块I/O请求,其工作是管理块设备的请求队列,决定队列中的请求的排列顺序以及什么时候派发请求到设备,关于更多详细的I/O调度知识这里就不深加研究了。 其次,块设备驱动又是怎样维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系呢?这就是上图中通用块层(Generic Block Layer)要做的事情了。在通用块层中,通常用一个bio结构体来对应一个I/O请求,它代表了正在活动的以段(Segment)链表形式组织的块IO 操作,对于它所需要的所有段又用bio_vec结构体表示。 再次,块设备驱动又是怎样对底层物理磁盘进行反问的呢?上面讲的都是对上层的访问对上层的关系。Linux提供了一个gendisk数据结构体,用他来表示一个独立的磁盘设备或分区。在gendisk中有一个类似字符设备中file_operations的硬件操作结构指针,他就是block_device_operations结构体,他的作用相信大家已经很清楚了。

相关主题
文本预览
相关文档 最新文档