Linux驱动笔记
来源:互联网 发布:2016年房地产投资数据 编辑:程序博客网 时间:2024/05/22 15:05
source /etc/profile
service nfs start
service iptables stop
setenforce 0
source insigst注册码:SI3US-361500-17409
无法进行文件传输时考虑:
1、配置文件是否正确
2、服务是否启动
3、setenforce 0 是否执行
--------------------------------------------------------------
每次上课同学首先要做的工作
1、完善交叉编译环境
source /etc/profile
arm-linux-gcc -v
2、启动nfs服务
service nfs start
3、关闭防火墙
service iptables stop
4、关闭linux的安全机制
setenforce 0
上述4个步骤做完后,一旦启动开发板,自动把虚拟机/etc/exports文件中导出的/root/test目录
挂载到开发板上的/test目录
开机实现挂载?
修改/etc/fstab表实现挂载。
步骤:
1、把开发板的ip固定下来,在开发板上修改/etc/net.conf
IPADDR=192.168.0.156
NETMASK=255.255.255.0
GATEWAY=192.168.0.1
MAC=10:23:45:67:89:ab
2、修改开发板的/etc/fstab文件,加上一行
192.168.0.155:/root/test /test nfs defaults,nolock
3、修改linux内核启动时执行的文件/etc/init.d/rcS
sleep 10
mount -a
很重要的最后一步:把 板子上的 test 权限
chmod 777 /test
linux 内核 : cd /root/opt/Em
镜像文件在内核的 cdarch/arm/boot
4、开机自动挂载,在开发板上访问/test,即可看到虚拟机/root/test中的内容
#include <linux/init.h>
#include <linux/module.h>
static int __init hello_init(void)
{
return 0;
}
static void __exit hello_exit(void)
{
}
module_init(hello_init);
module_exit(hello_exit);
Makefile内容
第一种
KERNALDIR=/root/opt/EmbedSky/linux-2.6.30.4
obj-m :=hello.o
all:
make-C $(KERNALDIR) M=`pwd` modules
第二种
KERNALDIR=/root/opt/EmbedSky/linux-2.6.30.4
MODULENAME=firstchardriver
all:
make-C $(KERNALDIR) M=`pwd` modules
arm-linux-gcc-o test_$(MODULENAME) test_$(MODULENAME).c
clean:
make-C $(KERNALDIR) M=`pwd` clean
多个文件的处理(修改obj -m):
obj-m=exportimport.o
exportimport-objs:=export.o import.o
嵌入式系统:以应用为目的,以计算机技术为基础、软硬件可裁剪、应用种对功能、可靠性、成本、提及、功耗有特殊要求的专用计算机设备。
ARM:advanced RISC machine
1978年,英国物理学家在剑桥成立CPU,1979年公司更名为Acron公司,1990年ARM,最早的办公地点是一个谷仓
----------------------------------
linux操作系统命令:
1、软件的安装方式
1)rpm包安装
rpm -ivh xxx.rpm
-e
--replacepkgs 覆盖安装
--excludedocs 不安装帮助文档
--nodeps 不考虑依赖关系
--prefix 目录 指定安装目录
--replacefiles 替换文件安装
--force 强制安装
--test 只检测,不安装
查询软件是否安装
rpm -q 软件名称
rpm -qa|grep 软件名称
2)源代码安装
下载到的是一个tarball
解压缩
tar[-z|-j][xvf] xxx.tar.gz[bz2]
查看目录中README文件
./configure
make
make insatall
3)脚本安装
解压缩
tar[-z|-j][xvf] xxx.tar.gz[bz2]
查看目录中README文件
执行安装脚本 ./setup.sh
如果计算机联网,可以使用
yum install 软件名称
自动帮你查找最新版本,自动查找依赖的软件进行安装
yum update 软件名称
挂载:
1、挂载u盘
fdisk -l 查看目前分区
mount /dev/sdb /myupan
2、挂载iso镜像
mount -t iso9660 xxx.iso /mycd -o loop
cpio命令:
-i 将数据从文件或者设备中复制到系统
-o 将数据从系统中输出到文件或设备上
-v 显示文件名
-d 在需要的地方创建目录
实例:将/etc/inittab文件从安装包中
rpm2cpio xxx.rpm|cpio -ivd ./etc/inittab
用户和用户组:
添加用户:useradd 用户名 /etc/passwd
修改密码:passwd 用户名 /etc/shadow
删除用户:userdel 用户名
添加组:groupadd 组名 /etc/group
修改密码:gpasswd 组名 /etc/gshadow
删除组:groupdel 组名
把用户添加到组中:
gpasswd -a 用户名 组名
gpasswd -d 用户名 组名
gpasswd -A 用户名 组名
gpasswd -M 用户名 组名
chgrp 修改目录或者文件的所属组
chown 修改目录或者文件的所属者
chmod 修改目录或者文件的权限
1)chmod u|g|o [+|-|=] rwx 文件或目录名
2)chmod 三位8进制数 文件名或者目录名
网络相关命令:
查看ip地址 ifconfig
设置ip地址 ifconfig eth0 newip
查看网络是否通畅 ping ip
向某个在线用户发送信息 write
向所有的在线用户发送信息 wall
查看在线用户: w whoami who am i
创建文件的几种方法:
1、touch 文件名
2、echo "字符串">文件名
3、cat 回车
文件内容
<ctrl+d>
4、vi 文件名
5、dd if=/dev/zeroof=/test/file.swp
bs=1024 count=65536
查看文件内容:
1、cat ----文件的合并
2、more ----空格下一页,回车下一行
3、less ----支持pageup和pagedown
4、head -n 文件名
5、tail -n 文件名
帮助:
1、文件名 --help
2、man 文件名
3、info 文件名
三个特殊的属性:
1、set uid:针对文件,设置后用户属性权限部分显示成s,执行该命令的用户临时具备命令所属者的权限
chmod u+s xxx 或者 chmod 4xxx xxx
典型案例: passwd 命令
2、set gid:针对文件,设置后用户所属组权限部分显示成s,执行该命令用户临时具备所属组的权限
chmod g+s xxx 或者 chmod 2xxx xxx
典型的案例: locate执行时可以访问
/var/lib/mlocate/mlocate.db
3、sticky bit:针对目录有效,设置生效后其他用户执行权限位显示t,特点是所有用户可以在该目录中创建修改文件,但是其他用户只能看,不能修改和删除他人的文件
chmod o+t xxx 或者 chmod 1xxx xxx
典型案例:/tmp 目录
一、vi编辑
插入状态:
i光标前 I 行首
a 光标后 A 行末
o所在行下一行 O 所在行上一行
ese退出插入状态
:set nu
:q 退出
:w 保存
:wq!强制保存退出
:!linux命令
:r 文件名
:r !linux命令 ---把命令的执行结果合并
:set ic
定位:
H屏幕顶端 M中部 L底部
移动光标 h左 j下 k上 l右
定位行首 0 行末 $
首行 gg
末行 G
定位到n行 :n 或者 nG
定位到m列 m空格
修改替换:
单个字符替换 r
多个字符替换 R
撤销上一步工作 u
查找:
/字符串 从上向下
?字符串 自下而上 n 查找下一个
查找替换:
:%s/old/new/g
:n1,n2s/old/new/g
剪切删除:
dd
ndd 当前行开始的多行
x 删除当前字符
nx 删除从当前字符开始的多个字符
:n1,n2d 删除指定范围的多行
复制:
yy 复制
nyy 复制n行
粘贴:
p 当前行的下一行
P 当前行的上一行
快捷键:
格式 :map 快捷键 具体的命令
:unmap 快捷键
:map ^p I#<esc> ---^ 通过ctrl+v
批量行的注释
:n1,n2s/^/#/g ---配置类文件shell文件注释
:n1,n2s/^#//g ---取消注释
:n1,n2s/^/\/\//g ---c语言编程多行注释
打开文件时直接定位到第n行
vi 文件名 +n
二、gcc编译工具
-o 指定编译得到的可执行文件名
-I 指定头文件所在位置
-WALL 显示所有的警告信息
-E 对应输出.i文件
-S 对应输出.s文件
-c 对应输出.o文件
-g 调试信息
-l 指定库文件名(可以缩写)
-L 指定库文件所在位置
三、gdb调试工具
gdb 编译后的可执行文件(加入了g调试信息)
gdb启动后,通过如下命令
file 文件名
list 显示程序代码
break n 第n行设置断点
run 运行程序
next 单步执行,不进入函数内部
step 单步执行,进入函数内部
quit 退出
info break 查看断点信息
delete n 删除某个断点(n是断点号)
disable n
enable n
print 变量 ---查看变量的值
break n if 条件 --条件成立时在第n行断点生效
四、make工具
相关管理的工具,必须和makefile文件配合
makefile文件格式:
目标:依赖文件1 依赖文件2...
<tab>gcc -o 欲得到的可执行文件名。。。
虚目标:
<tab>删除文件命令
应用于多个c源程序编译,适用make工具
执行make命令时,找到makefile第一个目标,按照该目标规则执行对应的命令
make -f 编译规则文件
五、shell编程
#!/bin/bash ---可以省略
执行shell脚步
1、修改权限,加上可执行的权限,执行时注意路径./xxx.sh
2、执行时采用命令 bash xxx.sh
read -p "xxxxxx" 变量名
第2章 嵌入式Linux驱动开发环境搭建
1、交叉编译环境:开发板资源少,无法满足对驱动程序的编辑、编译、调试过程;将该部分的工作放在虚拟机linux下来完成,将编译好得到的可执行的驱动模块下载到开发板上运行。交叉编译环境可以使得在虚拟机linux下编译得到的可执行模块在开发板中被识别。
2、嵌入式开发串口终端(超级终端,minicom,secureCRT):使用终端查看开发板的运行过程,验证驱动是否正确。
3、三台机器间的通讯。(windows---虚拟机linux---开发板):samba:完成windows---虚拟机linux通讯(虚拟机工具,SSH协议支持的工具)
nfs网络文件系统:完成虚拟机linux和开发板通讯
=====================================================
1、交叉编译环境
步骤:
(1)下载压缩包 EABI....tar.bz2
(2)解压 tar -jxvf EABI...
(3)找到所用命令所在的路径
/root/opt/EmbedSky/4.3.3/bin/
(4)修改/etc/profile文件,最后位置添加代码
export PATH=$PATH :(3)中的路径
(5)执行 source /etc/profile 使得修改立即生效
(6)执行命令 arm-linux-gcc -v 显示版本号
2、嵌入式开发串口终端
(1)超级终端:windows
开始 |程序|附近|通讯|超级终端
步骤:
a、输入一个新建链接的名称,任意
b、选择对应的串口(例如com1,根据硬件设备管理器中选择)
c、设置参数:
波特率 115200
数据流控制 无
(2)secureCRT:windows
新建会话,选择串口协议,设置波特率,取消数据流控制
(3)minicom:运行在虚拟机linux下
步骤:
a、查看是否安装minicom
rpm -q 软件名称
rpm -qa|grep 软件名称
b、挂载Centos的镜像文件,在挂载目录中查找
find /mycd -name ‘minicom*’
c、安装minicom
rpm-ivh /mycd/CentOS/minicom....
注意:可能会依赖关系,需要安装ncurses软件(该软件是需要进行源代码安装)
d、启动minicom
minicom直接启动,然后按 ctrl+a z 进入帮助,按o设置
minicom -s 进入设置
选择 serial portsetup,设置波特率,全部设置为no
选择 save setup asdfl
保存后重新启动,如果遇到错误提示,如下进行操作:
d.1查看是否有minicom仍然在运行,使用kill强制杀死
d.2识别的串口到底是否为/dev/ttyS0
3、
windows和虚拟机linux通讯方法
(1)使用虚拟机工具,共享目录
windows下的共享目录(c:/centos)在linux中对应
/mnt/hgfs/centos,复制该文件到linux系统中即可
(2)ssh 文件传输软件,设置虚拟机linux的ip和windows的ip在同一网段,启动软件后输入linux的ip,登录访问
(3)samba软件的安装
安装步骤:
a、查询是否安装 rpm -q 软件名称
b、find /mycd -name 'samba*'
c、安装
rpm -ivh samba-common....
rpm -ivh samba-client...
rpm -ivh samba-3....
注意:依赖关系
rpm -ivh perl-Conver....rpm
d、启动samba服务
service smb start|stop|status|restart
/etc/rc.d/init.d/smb start|stop|status|restart
e、添加samba用户(该用户是一个已经存在的linux用户)
smbpasswd -a linux用户
f、开放共享目录
mkdir /smbtest
chmod 777 /smbtest
g、修改配置文件,使得f中创建的目录对windows可见
/etc/samba/smb.conf文件进行修改
[smbtest]
path=/smbtest
browseable=yes
writable=yes
guestok=yes
h、重新启动服务
i、关闭linux的防火墙
iptables -F
j、因为linux有一个安全机制 SELINUX
setenforce 0 关闭安全机制
windows端执行访问
开始|运行中输入linux 的ip
\\192.168.0.155
输入用户名和密码访问
samba服务:实现windows和linux之间通讯
1、rpm -q samba
2、mount -t iso9660Centos.xxx..iso /mycd -o loop
3、find /mycd -name'samba*'
4、rpm -ivhsamba-common...rpm
rpm -ivh samba-client....rpm
rpm -ivh samba-3.4.....rpm
可能依赖关系 rpm -ivh perl-Conver....rpm
5、mkdir /smbtest
chmod 777 /smbtest
6、配置/etc/samba/smb.conf
[smbtest]
path=/smbtest
browseable=yes
writable=yes
guest ok=yes
7、添加一个samba用户(必须是一个已经存在的linux用户)
useradd zs ----添加linux用户
smbpasswd -a zs ----添加samba用户
8、启动samba服务 ----不启动,根本无法访问ip
service smb start
9、关闭linux的防火墙
iptables -f
service iptables stop ---关闭防火墙
10、关闭linux的安全机制 ---权限不足,打不开文件夹
setenforce 0
windows端访问
\\192.168.0.155
输入用户名和密码----samba用户
-------------------------------------------------------------
nfs 网络文件系统 虚拟机linux和开发板之间通讯
步骤:
1、 rpm -q nfs-utils
rpm -qa nfs-utils
2、find /mycd -name'nfs*'
3、rpm -ivhnfs-utils-lib....rpm
rpm -ivh nfs-1....rpm
4、可能存在依赖关系 ncurses软件,源代码安装
解压
./configure
make
make install
5、创建目录,开放权限
mkdir /nfstest
chmod 777 /nfstest
6、修改配置文件 /etc/exports
/nfstest *(rw,no_root_squash)
一般格式:
导出目录 客户端主机地址(权限,.....)
地址:
* 所有客户端主机地址
192.168.0.156 具体的ip地址
192.168.0.1/15 表示地址范围,15台机器均可访问
*.test.com 指定域
权限:
rw 可读可写
ro 只读
no_root_squash 不做匿名映射
7、使添加的导出目录立即生效
exportfs -ra ----该命令使得/etc/exports修改立即生效
exportfs -v -----显示所有的导出目录
8、启动nfs服务
service nfs start
9、关闭防火墙
service iptables stop
在开发板部分做的工作:
1、确保开发板和虚拟机linux的ip在同一网段
2、在开发板上创建一个目录 /test
3、在开发板上执行挂载命令
mount 192.168.0.155:/nfstest /test -o nolock
4、可以进行通讯共享
nfs服务:
1、导出目录(存在目录,且权限开放)
mkdir /nfstest
chmod 777 /nfstest
2、修改/etc/exports文件进行导出
/nfstest *(rw,no_root_quash)
3、使该文件生效
exportfs -ra
可以使用exportfs -v 查看是否生效
4、启动nfs服务
service nfs start 或者 /etc/rc.d/init.d/nfs start
5、关闭防火墙
service iptables stop
6、关闭linux的安全机制
setenforce 0
在开发板上
1、确认开发板的ip地址和虚拟机linux在同一网段,可以ping通
2、挂载操作
mount 虚拟机ip地址:/nfstest /test -o nolock
在使用时出现问题考虑:
1、网络是否通畅
2、nfs是否启动
3、防火墙是否关闭
-----------------------------------------
tftp的安装和配置
1、查询
rpm -q tftp
rpm -qa|grep tftp
注意:tftp服务是包含在xinetd服务
2、安装
rpm -ivh tftp-server...rpm
rpm -ivh tftp-0...rpm
rpm -ivh xinetd...rpm
3、创建tftp服务目录
mkdir /tftpboot
chmod 777 /tftpboot
4、修改配置文件/etc/xinetd.d/tftp
server_args = -c -s/tftpboot
disable = no
5、启动服务
service xinetd start
6、在开发板上实现下载和上传服务
tftp-gr 文件名 ip地址
tftp-pr 文件名 ip地址
9、关闭防火墙
service iptables stop
10.
setenforce 0
例如:
tftp -gr yst.a 192.168.0.155
tftp -pr xyz.x 192.168.0.155
source /etc/profile
自动挂载:
在开发板上修改/etc/fstab
虚拟机ip地址:/root/test /test nfs defaults,nolock
出现的问题:
1、把虚拟机ip和开发板ip设置一样
192.168.0.155 192.168.0.156
2、开发板中的/etc/init.d/rcS
sleep 10
mount -a (重新挂载/etc/fstab中的设备)
======================================================
虚拟机机上每次:
1、source /etc/profile
2、service nfs start
3、service iptables stop
4、setenforce 0
5、连上开发板,启动时自动将虚拟机上的/root/test挂载到开发板上的/test目录中
================================================
系统移植:
1、winxp和嵌入式linux启动过程
winxp linux
上电
BIOS bootloader
引导os linux内核
识别硬盘分区 挂载文件系统
运行程序 运行程序
2、移植三个部分
(1)bootloader作用
第一阶段进行硬件的初始化工作(例如关闭中断,关闭看门狗等防止处理器被复位),该阶段工作由汇编代码完成。第二阶段由c程序完成,实现初始化硬件设备(检测内存,将内核的映象文件加载到内存,准备内核引导的参数,跳转到内核的第一条指令位置,控制权交给内核代码)
(2)linux内核
(3)文件系统
bootloader:u-boot,vivi,superboot...
linux内核:得到内核镜像的方法:
a、网上下载linux源代码,进行配置,最终编译,得到镜像
make menuconfig ---进行配置
make ----编译得到内核镜像 /arch/arm/boot
b、使用开发板给我们提供的默认配置,进行修改
c、直接使用开发板提供的和板子匹配的配置文件
配置文件使用方法:
拷贝该文件,覆盖掉第一层目录下的.config文件
执行make menuconfig有可能出现的问题:
1、your display is toosmall...
ctrl+"-"缩小显示,让屏幕可以显示更多的内容
2、make: *** No rule tomake target `menuconfig'. Stop.
没有在linux的第一层目录中执行
3、如果交叉编译环境没有配置号,提示错误
4、no nurses to....
安装nurses软件,源代码安装形式
----------------------------------------------------------------
nand/nor转换开关:
nand模式:指从nand启动,引导内核,挂载文件系统
nor模式:下载模式,可以进行系统移植
移植步骤:
1、开发板断电,拨动开关到nor模式
2、加电启动,显示菜单,通过菜单1,3,6依次下载uboot,内核,文件系统
3、下载时连接usb线,通过TQBoardDNW工具实现下载
移植中注意的问题:
1、要移植的文件放在英文名字命名的目录中,否则容易出错
2、连接usb线,安装dnw工具(必须安装.netframework4.0)
每次实现:
1、交叉编译环境(source/etc/profile)
2、nfs服务启动 (service nfsstart)
3、关闭防火墙 (service iptables stop)
4、关闭安全机制(setenforce 0)
5、自动挂载检测(在开发板/test目录下看到虚拟机/root/test)
----------------------------------------------------------------
交叉编译环境的命令:
arm-linux-gcc 编译命令
驱动程序起作用的方式:
1、静态编译进内核(编写好驱动源代码,通过配置,将其配置为内核的一部分,编译得到内核镜像,烧写到开发板)
优点:一次编译,多次使用
缺点:如需修改,重新编译和烧写,效率底
2、动态模块的形式
特点:按照模块化编写程序,可以通过模块操作的命令在需要的时候把驱动插入内核,不需要的时候删除,灵活
===================================================
驱动对三种设备:字符设备、块设备、网络设备
linux 内核 : cd /root/opt/Em
镜像文件在内核的 cdarch/arm/boot
静态编译到内核的步骤:
1、编写一个驱动源代码(模块化的形式)
2、复制该源程序到对应驱动存放的路径(drivers/char)
3、进入到drivers/char目录,修改Kconfig文件(该文件影响make menuconfig出现的菜单),添加如下代码
configMYHELLO
bool "firstmenu"
4、进入第一层目录,执行makemenuconfig命令
device drivers|character devices
在新出现的菜单位置按空格键,使其前面出现“*”,保存退出
在第一层目录下的.config文件,出现新变量
CONFIG_MYHELLO=y
5、进入drivers|char目录,修改Makefile,添加一行代码
obj-$(CONFIG_MYHELLO) +=hello.o
意味着将hello.o编译进内核,该位置也可以直接写成
obj-y += hello.o
6、在内核的第一层目录进行编译,得到内核镜像,烧写到开发板,查看效果
make
模块化框架:
1、引入头文件
#include <linux/init.h>
#include <linux/module.h>
2、编写初始化和卸载函数
static int __init hello_init(void)
{
...
return0;
}
static void __exit hello_exit(void)
{
...
}
3、对初始化和卸载的函数进行声明
module_init(hello_init);
module_exit(hello_exit);
补充:
内核编程中打印信息使用函数 printk("字符串\n");
应用程序中打印信息使用函数 printf
编译内核:
1、make menuconfig
2、make 编译----(arch/arm/boot) zImage
模块化驱动:
学习相关命令:
lsmod 查看当前内核安装了哪些模块
insmod 将驱动模块插入到内核 板子上的 执行的 如insmod hello.ko
rmmod 将模块从内核中卸载(可以不加.ko) 板子上的 执行的 如
modinfo 查询模块信息
在虚拟机上执行的 /root/test 中写源程序 编辑Makefile文件以及 make 生成.ko文件
如何编译称为模块 *.ko
利用make工具实现,必须有Makefile文件
vi Makefile
下边是里边的内容 注意:复制过去的话很容易出现错误,所以这点要注意一下子
KERNALDIR=/root/opt/EmbedSky/linux-2.6.30.4SSS
obj-m :=hello.o ----最终形成.ko模块文件
all:
<tab>make -C $(KERNALDIR) M=`pwd` modules
<tab>make -C $(KERNALDIR) M=`pwd` clean
接着在目录下make 编译
多个文件的处理(修改obj -m):
obj-m=exportimport.o
exportimport-objs:=export.o import.o
最后在板子上利用 模块的相关命令进行 插入等操作
要在 共享的test目录下执行
如:insmod hello.ko
-C 进入到指定的目录,该目录一定是内核第一层目录/root/opt/EmbedSky/linux-2.6.30.4,为什么?
答:因为在编译的过程中,需要用到第一层目录中的makefile文件,以及其他子目录中的makefile文件
SUBDIRS 等同于 M
为了在插入模块时不提出警告
MOUDLE_LICENSE("GPL");
驱动起作用的方式:
1、静态编译进内核
2、动态模块驱动的形式
相关命令:
1)lsmod ---查看内核中有哪些模块
2)insmod ---将模块插入到内核中
3)rmmod ---将模块卸载
4)modifo ---查看模块的信息,这些信息是编写驱动时利用宏添加到模块中,描述模块信息
MODULE_AUTHOR("")
MODULE_VERSION("")
MODULE_DESCRIPTION("")
MODULE_ALIAUS("")
5)modprobe:也用于加载模块,使用该命令加载时会自动解决依赖关系
编写MAKEFILE文件,使用make命令进行编译
KERNALDIR=/root/opt/EmbedSky/linux-2.6.30.4
MODNAME=模块名称
obj-m:=$(MODNAME).o
all:
<tab>make -C $(KERNALDIR) M=`pwd`modules
clean:
<tab>make -C $(KERNALDIR) M=`pwd`clean
myclean:
<tab>@rm -rf *.o *.mod.c Module.*...
==============================================
source insight工具:
一、认识启动界面:
1、标题栏(项目名称,软件的名称,当前打开文件的名称)
2、菜单栏:分类显示菜单,每个菜单命令实现特定功能
3、工具栏(菜单命令的快捷方式)
4、编辑区域(中间,编写程序源代码)
5、文件成员列表(左侧、头文件、变量定义、函数)
6、项目文件列表(右侧、该项目所有组成文件,可以快速定位项目中的某个文件)
7、上下文窗口(下边、显示成员的定义)
二、创建项目(把linux内核文件创建为一个项目)
1、创建文件夹lessontest,在该文件夹下创建si文件夹(放项目文件),内核文件夹
2、添加项目文件
add all:选择目录后,将目录中的内容添加到项目中
add tree:添加时包含子目录和字母中的内容
---自己做项目时选择add tree
3、为了今后查找内容更加快捷,执行
projiect|synchronize 同步文件
三、快捷键:
1、ctrl+f 查找本文档中的字符串
f3上一个 f4 下一个
2、ctrl+/ 在整个项目中查找字符串,找到文件后可以展开查看具体内容
3、已经访问过的多个文件,
alt+, 前一个窗口
atl+. 后一个窗口
4、快速定位到某一行
ctrl+g
5、ctrl+o 快速定位到项目列表查找对话框
6、标亮所有光标所在字符串 shift+f8
7、查找定义
ctrl+鼠标左键单击
设定添加到项目中的文件类型 文件过滤
options|document options|file filter
|screen font
|show line numbers
printk 内核打印函数:
在内核中,根据输出信息的紧急程度,附加不同的优先级进行信息输出
格式:
printk(优先级别 "字符串");
printk("<n> 字符串");
KERN_EMERG 处理紧急信息,通常系统崩溃前<0>
KERN_ALERT 立即处理信息<1>
KERN_CRIT 严重情况<2>
KERN_ERR 错误情况<3>
KERN_WARNING 有问题的情况<4>,默认级别
KERN_notice 正常情况,仍需注意 <5>
KERN_INFO 消息型信息<6>
KERN_DEBUG 用作调试信息 <7>
一旦内核运行后,如果需要查看内核
/proc/sys/kernel/printk 打印相关文件
7 4 1 7
7 最大可用的级别,小于该数字的级别均可显示
4 默认级别
1 可以设置的最高级别
可以用命令echo修改其内容
7 引导时默认的日志级别
多模块:模块间依赖关系
一个模块中定义的函数,被另一个模块调用
导出模块要做的工作:
1、定义变量或函数
2、通过宏导出:EXPORT_SYMBOL(X)
X要导出的变量名或者函数名
引用模块做的工作:
1、声明外部函数
2、调用函数
=========================================
测试步骤:
1、修改Makefile文件,将obj-m=。。。语句修改
2、在开发板中插入模块顺序
insmod exporttest.ko
insmodimporttest.ko
3、卸载时相反顺序
===========================================
多个C文件编译称为一个模块:
修改Makefile
obj-m=exporttest.o importtest.o
修改为:
obj-m=exportimport.o
exportimport-objs:=exporttest.o importtest.o
==========================================
模块传递参数:在将驱动模块插入内核时,传递参数到内核
insmod 模块
insmod 模块 参数
1)module_param(name,type,perm)---普通变量
type:支持bool,字符指针、int,long,uint,ulong,ushort
数组:
2)module_param_array(name,type,num,perm)--声明数组
perm:权限值,控制谁可以存取这些模块参数
3)MODULE_PARM_DESC() ---对参数进行说明
步骤:
1、编写模块驱动时,定义参数变量,并赋值
2、使用1-3)中的函数对参数进行声明及解释
3、在插入模块时可以对参数进行初始化
下次课预习:
P65页 ---设备分类、设备号
P87-97页 ---字符设备驱动重要数据结构
p105-112页 ---早期标准字符设备驱动模板
第5章 字符设备驱动程序
Linux操作系统下,一切皆是文件,包括硬件
Linux的设备:字符设备,块设备,网络设备
字符设备:以字节为单位顺序读写,发出读写请求后立即执行
块设备:可随机读写,一般不立即执行;要进行优化,使得读写效率更高
/DEV/目录下存放设备文件,以上两种均可见
驱动属于内核的一部分,驱使硬件正常工作:
1)初始化和释放设备
2)数据从内核传递到硬件,或者硬件读取数据到内核
3)读取应用程序中的数据给设备文件,或者把设备文件中的数据回传给应用程序
4)检测和处理设备出现的错误
应用程序-----------内核----------硬件
=============================================
一、设备号
设备号是一个32位的整型数据,类型dev_t
主设备号:对应的驱动程序,占12位
次设备号:不同的设备个体,低20位
重要的宏:
MAJOR() ---提取主设备号,参数是设备号
MINOR() ---提取次设备号,参数是设备号
MKDEV() ---构成设备号,两个参数,主次设备号
二、驱动工作原理:(见驱动工作原理图)
编写linux字符设备驱动流程:
1)查看原理图、数据手册、了解设备的操作方法
2)在内核中找到相近的驱动程序(source insight),以它为模版进行开发,某些情况从0开始
3)注册驱动程序,给用户空间提供访问的接口,例如在/dev目录下生成一个设备文件
4)设计要实现的操作,例如open(),read()。。
5)实现中断(不是必须)
6)将驱动插入内核
7)执行应用程序测试驱动效果
三、字符驱动重要的数据结构
1、struct file_operations结构
作用:完成应用程序中的函数和驱动中的函数的对应关系
.owner = THIS_MODULE,
一个指向拥有这个结构的模块的指针,通常都设置为THIS_MODULE,作用是用来在它的操作还在被使用时阻止模块被卸载。
其他成员都是一个返回函数指针的函数,这些函数实现和应用程序调用的对应关系
static const struct file_operations my_ops ={
.owner = THIS_MODULE,
.open = my_open,
.read = my_read,
.llseek = my_lseek,
.release =my_release,
};
2、struct inode结构:唯一表示文件,物理表示
ls-i
重要成员:设备号、cdev(代表字符设备结构体)
3、struct file结构:代表一个打开的文件,由内核在open时创建,系统每次打开一个文件,就会产生一个file
重要成员:
file-ops 文件操作集合
flags 文件打开标志,由应用程序open函数传入
f_pos 文件读写位置
private_data 私有数据
四、字符驱动编写框架
1、引入头文件
2、做函数对应关系,定义file_operations结构体对象,应用程序中函数与驱动中函数对应
3、编写驱动中函数(实现该函数功能)
4、模块初始化函数(函数,module_init())
注册(字符驱动)
申请内存
申请硬件资源
开启硬件
5、模块卸载函数(函数,module_exit())
---记住写许可证 MODULE_LICENSE();
五、注册驱动程序(设备文件----驱动程序---硬件设备)
早期标准字符设备的驱动模块
register_chrdev(major,*name,*fops)
major 主设备号
name 设备名称
fops file_operations 操作集合
补充:创建设备文件(/dev/my_led)
/dev创建设备文件使用命令 mknod
mknod devicefilename type major minor
例如:创建led灯对应的设备文件
mknod my_led c 111 0
六、实例-----开发板LED驱动(字符设备)
应用程序
|
硬件设备(LED灯)-----设备文件(/dev)
设备号:
主设备号:对应驱动程序
次设备号:标注使用同一驱动的不同设备,这些设备属于同一类型
sda1 sda2 scsi接口的第1块硬盘第1、2个分区
数据结构:
1、file_operations 对文件操作的函数指针的集合,该结构使得应用程序接口和驱动中的函数一一对应。特殊成员owner,总是定义成THIS_MODULE
2、inode:唯一表示物理文件的结构体,两个重要成员:设备号和设备结构体cdev
3、file:表示一个打开的文件,和inode区别
框架:
1、头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
2、许可证信息
MODULE_LICENSE("GPL");
3、接口对应关系,定义file_operations对象
4、实现驱动接口函数功能
5、插入模块初始化函数和卸载函数
早期(linux2.6内核版本之前)的驱动模型
注册驱动(初始化函数中执行)
register_chrdev(主设备号,设备名,&f_ops);
注销驱动(卸载函数中实现)
unregister_chrdev(主设备号,设备名);
cat/proc/devices下注册的字符设备
应用程序-----设备文件------内核中设备---驱动---硬件
设备文件产生:(早期必须手动创建)
mknod 设备文件名 类型 主设备号 次设备号
例如: mknod my_led c 111 0
驱动测试步骤:
1、编译驱动内核(xxx.c)
2、将驱动插入内核(xxx.ko)
3、知道主设备号,创建设备文件节点(/dev/my_led)
4、执行测试程序(testxxx.c)
根据测试步骤分析:
步骤2中,
内核中module_init(first_drv_init)
插入内核时first_drv_init运行
register_chrdev(111,"first_drv",&myfirst_fops);
在内核中注册一个字符设备,该设备主设备号111,设备名称first_drv,对该设备操作的集合是myfirst_fops
方便你理解:
内核中有一个数组(0--256)
其中下标为111的数组元素存放
111 first_drv myfirst_fops
执行完步骤2后,可以通过
cat /proc/devices查看注册的设备
步骤3中,通过mknod创建设备文件,
mknod /dev/my_led c 111 0
内核中的字符设备
first_drv 就和/dev/my_led建立了关联
步骤4中,运行测试程序
fd=open("/dev/my_led",O_RDWR);
应用程序中对该文件的操作都是通过fd来实现的,
构建一个file结构体对象,描述这个打开文件
成员file_operations由内核中的对应该设备的myfirst_fops进行初始化
.open=myfirst_open
使用inode结构体的成员初始化file结构体成员,执行myfirst_open中的语句
==========================================
早期字符设备驱动一般框架
主设备号:
1、人为指定主设备号
2、内核自动分配(256--->)
注册时
major=register_chrdev(0,"设备名称",&fops);
记住在卸载模块时
unregister_chrdev(major,"设备名称");
文件节点创建:
1、手动创建设备文件节点 mknod
2、使用shell脚本分离主设备号,创建文件节点
3、通过内核提供函数实现完全自动创建设备文件节点
mdev机制:udev的精简版,可以实现设备节点自动创建和设备的挂载。
ACTION :创建行为
DEVPATH:设备文件创建目录
/etc/init.d/rcS 该文件在系统启动时执行,设置PATH,挂载文件系统,实施mdev机制,如果用户希望启动时自动执行某些命令,在该文件中添加命令行
mount -t iso9660 xxx.iso /mycd -o loop
echo /sbin/mdev >/proc/sys/kernel/hotplug
在增删设备时执行/sbin/mdev
内核提供函数:
struct class * class_create(隶属于谁,类的名称)
class是一个设备类结构,注册一个类结构,在该函数执行后,mdev机制使得在/sys/class/目录下创建一个“类的名称"命名的目录
类的名称 ----myclass
struct device *device_create(类指针,*parent,设备号,*drvdata,const char* fmt)
类指针是class_create()执行的返回值
*parent 通常设置为 null
设备号 通常使用MKDEV(major,0)
*drvdate 通常设置为null
/dev/下创建的设备文件节点名 ---my_led
mdev机制,在device_create()执行后,在/sys/class/myclass目录中创建目录my_led
该目录下生成三个文件
dev uevent subsystem
其中dev中的内容 major minor
/sbin/mdev命令 /etc/init.d/rcS支持热插拔,监控/sys/class目录的变化,一旦dev文件产生,自动执行
mknod /dev/my_led c major minor
注意:
在模块卸载时,要删除设备和类
device_destroy(类指针,设备号)
class_destroy(类指针)
复习:
自动创建设备文件节点(mdev机制)
struct class *my_cls;
struct device *my_cls_dev;
my_cls=class_create(THIS_MODULE,"my_cls");
*在/sys/class目录下创建一个名为my_cls的目录
my_cls_dev=device_create(my_cls,NULL,MKDEV(major,0),NULL,"led_dev");
*在/sys/class/my_cls下创建新的目录 led_dev
并在该目录中创建三个文件
dev(252 0) uevent subsystem
mdev机制:
echo /sbin/mdev >/proc/sys/kernel/hotplug
mdev -s
热插拔,观察/sys/class目录有没有变化,将使
mdev -s 命令执行,该命令会根据目录的变化自动创建设备文件节点
1)先找到类目录my_cls
2)找到设备目录 led_dev
3)查看dev文件的内容 252 0
4)执行mknod /dev/led_dev c 252 0
========================================
考虑:早期字符设备驱动,设备号或者静态指定,或者在注册时获取;没有自动创建设备文件节点(除非使用class_create和device_create实现)
杂项设备---自动产生设备文件节点之外,还可以使用系统帮用户定义的一个设备框架
杂项设备 miscdevice
主设备号固定为10,使用时非常简单,只需要做简单的设置,会自动帮助用户生成设备文件
步骤:
1、设置杂项设备结构体成员:
minor ---MISC_DYNAMIC_MINOR 或者 255
name ---设备节点 /dev目录下创建
fops ---处理函数的入口指针
2、注册杂项设备(模块初始化函数)
misc_register();
3、注销杂项设备(模块卸载函数)
misc_deregister();
=========================================
linux2.6标准字符设备驱动模型
struct cdev; ---描述一个字符设备
注册函数:
1、register_chrdev_region();--静态注册指定设备编号
函数参数:
first:设备编号范围的起始值,通常设置为0
count:所请求的连续设备编号的个数
name:设备名称
2、alloc_chrdev_region(); ---向内核动态申请设备编号
函数参数:
dev:设备号 (250 0) 250 1 2502
firstminor: 第一个次设备号 ,通常是0
count:所请求的连续设备编号的个数
name:设备名称
---cat /proc/devices 看到主设备号和设备名称
注销函数:
unregister_chrdev_region();
first:设备编号范围的起始值,通常设置为0
count:所请求的连续设备编号的个数
注册一个独立的cdev设备的基本过程:
1、为struct cdev分配空间
struct cdev *my_cdev=cdev_alloc();
2、初始化cdev
cdev_init(cdev,fops);
3、初始化cdev.owner
cdev.owner=THIS_MODULE;
4、cdev设置完成,进行添加
cdev_add(dev,num,count);
5、在模块卸载函数中,
cdev_del(dev);
测试步骤:
1、插入模块到内核
2、cat /proc/devices 查看主设备号
3、mknod创建设备文件
4、执行测试程序
字符设备LED的驱动
步骤:
1、查看底板原理图(查看LED的电路图,找到引脚)nLED_1---nLED_4
2、核心板原理图(查看引脚对应cpu的控制引脚)
GPB5-8
3、查看tq2440用户手册,查看控制引脚对应的io端口设置
port B 端口设置----靠寄存器(GPBCON,GPBDAT)
对应引脚位输入输出的值----GPBDAT
设置为输入还是输出状态----GPBCON
为了在GPB5-8输出低电平,需要
1)设置GPBCON 10-17位为 01010101
2)设置GPBDAT 5-8位为 0000
一、地址映射函数实现LED灯控制
ioremap(cookie,size)
cookie:物理地址 size:映射的空间大小,字节为单位
iounmap(cookie)
cookie:ioremap得到的虚拟地址
例如:
volatile 防止系统优化 该单词意思“易变的”
gpbdat=1;gpbdat=0;gpbdat=1;gpbdat=0
如果不加volatile,在编译时会自动优化,gpbdat=0
volatile unsigned long *gpbcon;
volatile unsigned long *gpbdat;
在模块初始化函数中,进行地址映射
gpbcon=(unsigned long*)ioremap(0x56000010,16);
gpbdat=gpbcon+1;
在模块卸载函数中,进行释放
iounmap(gpbcon);
在打开函数open()中:
对LED端口寄存器初始化设置
//设置GPBCON对应GPB5-8为输出状态
*gpbcon&=~((0x3<<(2*5))|(0x3<<(2*6))|(0x3<<(2*7))|(0x3<<(2*8)));
*gpbcon|=((0x1<<(2*5))|(0x1<<(2*6))|(0x1<<(2*7))|(0x1<<(2*8)));
//设置GPBDAT对应5-8为低电平,灯全亮
*gpbdat&=~((1<<5)|(1<<6)|(1<<7)|(1<<8));
位操作:
gpbcon xxxxxxxxxxxxxxxx
& 00000000xxxxxxxx
|01010101xxxxxxxx
在写操作函数write()中:
#inlcude <asm/uaccess.h>
应用程序传递的数据要交给内核,必须使用特定的函数
copy_from_user();
如果内核希望传递数据给应用程序,必须使用特定函数
copy_to_user();
将跑马灯应用程序关闭命令:
/etc/rc.d/init.d/leds stop
========================================
二、使用内核系统调用实现LED灯控制
内核中已经有一些关于开发板引脚设置的函数:
s3c2410_gpio_setpin(); ---设置引脚的值(高低电平)
s3c2410_gpio_cfgpin(); ---配置引脚(输入输出状态)
需要头文件:
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
补充:头文件引用时规律
<linux/xxx.h>
linux系统下的头文件,与平台无关
<asm/xxx.h>
arm体系结构下的通用文件,与arm相关
<mach/xxx.h>
三星24xx系列的专用文件
<plat/xxx.h>
三星arm平台专用文件,与三星arm相关
==========================================
驱动设备中函数实现内容:
1、open接口函数:
检查特定设备错误,如果第一次打开,初始化设备
如需要,分配并填充私有数据file->private_data;
2、release接口函数:
释放open分配在file->private_data的任何东西
关闭设备
3、write接口(明确从应用接收数据写入内核)
copy_from_user();
接收到数据后进行处理
4、read接口(明确从内核读数据传给应用)
准备传递给应用程序的数据
copy_to_user();
5、llseek接口(移动设备的文件指针)
实现该接口后,可以根据偏移量只读写设备部分数据
最主要的工作是改变位移量,将改变后的位移量赋值给file->f_pos,在读写文件时会根据该成员定位读写位置
6、实现llseek接口后的write接口实现(考虑f_pos参数)
首先判断count是否过大,如果过大,进行修改为=空间大小-文件指针位置;
copy_from_user((void*)(&led_buffer[*f_pos]),...);
*f_pos+=count;
6、实现llseek接口后的read接口实现(考虑f_pos参数)
首先判断count是否过大,如果过大,进行修改为=空间大小-文件指针位置;
copy_to_user(buf,&led_buffer[0+*f_pos]),);
*f_pos+=count;
GPBDAT --XXXXXXXXXX
GPBDAT& (1 << 5)
0000100000
--- 0000X00000
~ 按位取反
! 逻辑取反 0000100000 ---->0
0000000000 ---->1
=========================================
ioctl 控制接口:在驱动编程中,除了读写之外的操作一般都在ioctl()中完成,可以完成相对复杂的操作
应用程序中: ioctl(fd,cmd,arg...)
内核中:
cmd 对应一个32位的命令码,包括4个部分
type ---幻数(魔数) 0-255,实际编程中往往使用字 符“a-z"或者”A-Z"
为什么定义幻数:设备驱动从传递来的命令中获取魔数,与自身的魔数进行比较,如相同则处理,不同则不处理
nr ----序列号,定义的命令数,当前是第几条命令
datatype ---数据类型
定义命令的形式:
#define MEM_IOC_MAGIC 'm'
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC,0,int)
#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC,1,int)
...
驱动中实现ioctl的命令一般定义在一个头文件当中(*.h)
#ifndef _MY_CMD_IOCTL_H_
#define _MY_CMD_IOCTL_H_
所有的命令定义
#endif
原因:例如有两个c文件,都include了同一个头文件,一起编译成一个可执行文件,此时如果没有#ifdef,会出现大量的声明冲突。
驱动中ioctl的实际处理方法:
swich (cmd)
{
casea:
...
caseb:
...
break;
...
}
检测命令的有效性:
_IOC_TYPE(cmd) 获取魔数
_IOC_NR(cmd) 获取命令中的nr(第几条命令)
检测参数空间是否可以访问
_IOC_DIR(cmd) ---分离出方向
_IOC_READ 读
_IOC_WRITE 写
_IOC_NONE 无数据传递
_IOC_READ|_IOC_WRITE 双向
_IOC_SIZE(cmd) ---分离出数据大小
open接口:检测设备,初始化,私有数据准备
release接口:与open相反,释放私有数据空间,关闭设备
write接口:接收应用传递的数据,写入内核(对硬件寄存器位进行控制)
copy_from_user(*to,*from,count);
read接口:内核中准备数据,传给应用程序(查询硬件寄存器状态,传递给应用程序)
copy_to_user(*to,*from,count)
llseek接口:改变文件指针,位移量修改,最终影响的是
file->f_pos
注意:一旦使用llseek(),那么read()和write()做相应的调整
led灯亮灭实现:地址映射ioremap(),创建类及设备自动生成设备文件节点(不用mknod创建设备文件)
===============================================
ioctl控制接口:
除了读写操作之外的其他操作,都可以通过ioctl来实现。例如:弹出光驱(传递命令和参数)
用户空间:ioctl(fd,cmd,arg)
内核空间:
static int testioctl(struct inode *inode,struct file *file, unsigned int cmd, unsigned long arg)
具体实现:在ioctl中一般都有
switch(cmd)
{
casea:
...
break;
caseb:
...
break;
...
}
==========================================
cmd 一个整数,简单操作,直接传递给内核
arg 参数,一个整数,在内核直接访问。如果是指针,需要进行有效性检测access_ok()
cmd一般对应一个32位的命名码
设备类型:8位 ---魔数(幻数)
序列号:8位 ----定义的命令的序号
方向:2位 ----读写方向
数据尺寸:8--14位 传递的参数占用空间大小
写驱动程序时,一般把命令定义在一个头文件中 *.h
定义命令:
type 魔数 ,取值范围0-255,一般使用大小写字母
nr 序号 从0开始
_IO(type,nr) ---该命令不牵涉读写操作,不需传参
_IOR(type,nr,datatype) ---读(内核)
_IOW(type,nr,datatype) ---写(内核)
_IOWR(type,nr,datatype) --双向操作(内核)
在定义命令时,如果最后的参数写的是datatype例如int,会自动转换成数据占用的空间sizeof(int)
头文件名字 mycmdioctl.h
把头文件单词分离,用下划线分隔,“."转化为"_",h大写,在前后分别加上下划线
#ifndef _MY_CMD_IOCTL_H_
原因:该形式一般出现在头文件中。例如有两个c源程序都include 该头文件,一同编译产生一个可执行的文件,此时会出现大量的声明冲突
实现命令:
检测用到宏:(从cmd命令中提取4个组成项)
_IOC_TYPE(cmd) 幻数
_IOC_NR(cmd) 命令序号
_IOC_SIZE(cmd) 参数大小(占用空间)
_IOC_DIR(cmd) 方向
access_ok(type,addr,size)
检测arg是否是一个合法的指针
type ---对应VERIFY_WRITE 面向应用程序
VERIFY_READ 面向应用程序
返回1表示地址有效,0表示失败
临界资源共享:
1、多任务操作系统,单CPU,多个执行单元,都要求访问同一个共享临界资源
2、多CPU系统,SMP,执行多个执行单元,都访问同一个共享的临界资源
实例:打开文件时只允许打开一次
1、简单变量实现
static int canopen=1;
open()函数
if(--canopen!=0)
{
printk("file was already opened,only once !\n");
canopen++;
return -EBUSY;
}
close()函数
canopen++;
分析:存在弊端,--canopen!=0分三个步骤实现,容易出现特殊情况,同时多个执行单元打开文件
2、原子变量实现:最小的执行单元,绝不会在执行完毕前被任何其他的任务或事件打断
类型:atomic_t
头文件:#include <asm/atomic.h>
app函数:
2.1 ATOMIC_INIT(); 定义原子变量并初始化
static atomic_t my_atomic= ATOMIC_INIT(1);
或者
static atomic_t my_atomic;
在init函数(插入模块)
atomic_inc(&my_atomic);
2.2 atomic_inc();参数为原子变量指针
原子变量自加1操作
2.3 atomic_dec_and_test();参数为原子变量指针
进行自减1操作,判断结果是否为0,如果为0,返回真,否则返回假值
3、信号量
数据类型 struct semaphore
头文件 #include <linux/semaphore.h>
操作原理:(操作临界共享资源,必须先获得信号量)
访问时,获取信号量(-1),负数无法获得信号量,在等待队列中挂起;非负数获得信号量,访问临界资源
释放:使用完毕,释放(+1),如果非正数,标明有任务在等待访问临界资源
注意:信号量分为两种
1)互斥信号量(初值为1),又称为互斥锁
2)计数信号量(初值大于1)
相关app:
3.1 DECLARE_MUTEX(name);
声明一个信号量并初始化为1
也可以通过下面的方法实现:
struct semaphore my_mutex;
在init中初始化
init_MUTEX(&my_mutex);
3.2 init_MUTEX(信号量指针); 初始化信号量值为1
3.3 down_trylock(信号量指针);
尝试获取信号量,如果能获得,返回0;否则不能获得,返回非0值
3.4 up(信号量指针);
释放信号量,加1操作,如果非正数,表明有任务等待
注意:down_trylock(信号量指针);获取信号量,当信号量值为0时,不再进行自减赋值操作,只进行自减看结果
使用计数信号量,打开2次?
struct semaphore sem_open;
在init中初始化为2
sema_init(&sem_open,2);
open打开时判断
if (down_trylock(&sem_open))
{
printk("file was already opened,only once !\n");
printk("%d\n",sem_open.count);
return -EBUSY;
}
close函数中进行释放
up(&sem_open);
===========================================
4. 自旋锁
多用在SMP(多CPU系统)中,自旋锁不会引起调用者休眠,会一直循环在那里等待
获取自旋锁访问临界资源的执行单元时间都非常短,效率高
比较:
信号量适合于保持事件较长(访问临界资源时间较长)
自旋锁效率高,访问时保持时间短
任何时刻,最多有一个执行单元获得自旋锁
访问方式:
获取锁
对临界资源操作
释放锁
数据类型:spinlock_t;
#include <linux/spinlock_types.h>
app:
4.1 DEFINE_SPINLOCK(x);
定义一个自旋锁并初始化它
或者spinlock_t lock;
在init中初始化
spin_lock_init(spinlock_t *lock);
4.2 spin_lock_init(自旋锁指针);动态初始化
4.3 spin_lock();参数是自旋锁指针
获得自旋锁,如果能够立即获得,立即返回(就可以对临界共享资源操作),否则自旋等待
4.4 spin_unlock();参数是自旋锁指针
释放自旋锁,与spin_lock()配对使用
使用自旋锁,打开2次?
原子操作、互斥锁、自旋锁应用到LED驱动,使得文件每次只能打开一次。
一、自旋锁SMP并发分析
SMP:多处理器系统 system ofmutiple processor
共享的临界资源同步访问
1、单cpu系统,多任务,多个线程访问同一个临界资源
2、多cpu系统,多个线程在被多个cpu处理,访问同一个临界资源
自旋锁特点:
1)被自旋锁保护的临界区代码执行时,不能进入休眠
2)。。。,不能被其他进程中断中断。
3)。。。,内核不能被抢占
单cpu系统,为例:
内核请求到一个自旋锁,并且在临界区做工作,进行到中间某处,失去cpu
当前进程A进入休眠1) 或者内核抢占3)
其他进程B要获取同一个自旋锁,如果B进程和A进程运行在同一个处理器上,B进程获得CPU要去获取自旋锁,因为A占有自旋锁,所以B自旋等待(占有cpu),cpu无法处理A释放自旋锁,进入死锁状态
中断---中断信号----执行中断处理程序
驱动程序A获得锁,该锁控制访问设备,
中断处理程序B需要获得锁,但是锁被占,持有cpu旋转等待,A无法获得cpu处理,自旋锁无法释放,导致死锁
被自旋锁保护的临界区代码在执行时,不能因为任何原因放弃处理器。
===========================================
等待队列:
希望等待特定事件的进程,主动放弃控制权,把自己放进一个等待队列中
条件满足时,唤醒进程,获得控制权继续执行
等待队列头:wait_queue_head_t;
使用等待队列的一般步骤:
1)定义一个等待队列头对象;
2)在需要阻塞的地方调用一个wait_event_*(等待节点,condition);使当前进程进入休眠,放弃控制权,condition为假时放弃控制权,挂入等待队列
3)当条件满足时,在内核的另一侧,将条件condition置为真,调用wake_up()唤醒进程
实例:
1、测试1执行字符设备驱动程序,执行后首先打开文件,然后执行read()读操作,此时驱动中对应的read函数执行,挂起(进入等待队列)wait_event_*(等待节点,condition);
2、测试2执行,打开文件,执行写操作,传递一个数值到内核,得到该数据后,唤醒刚才的等待队列节点,进行读取操作
常用API:
1、DECLARE_WAIT_QUEUE_HEAD(wq);
定义一个等待队列头,名字是wq
使用init_waitqueue_head(wq)进行初始化
2、wait_event_interruptible(wq,condition);
condition为假时将当前进程挂入wq对应的等待队列
3、wake_up_interruptible();
唤醒进程
等待队列添加头文件:
#include <linux/wait.h>
#include <linux/sched.h>
按键驱动:
GPF GPF0-2 GPF4
首先设置为输入状态GPFCON 0-589 设置为00
读取对应数据控制高或低电平GPFDAT 0-2 4 设为0
LED驱动:
GPB
首先设置为输出状态GPFCON 10--16设置为01
写入GPFDAT 对应数据位 5-8设置为低电平 0---灯亮
============================================
使用系统调用实现:
1、引入头文件
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
2、配置输入状态(GPF0-2 GPF4)
s3c2410_gpio_cfgpin(S3C2410_GPF0,S3C2410_GPF0_INP)
3、read函数中读取引脚值,将输入传递给应用程序,进行打印
==========================================
弊端:测试程序中,读取使用while(1)循环,CPU效率非常低
中断:硬件设备被操作时,产生中断信号,CPU进行处理,执行中断处理函数
基本理论:
linux内核管理硬件设备两种方式:
1)轮询:cpu不断查询硬件设备的状态
2)中断:当硬件设备需要时向内核发送信号
中断是一种电信号,由硬件设备产生,然后由中断控制器向处理器发送相应的信号,处理器一旦检测到该信号,便中断当前正在处理的工作,转而去处理中断。
不同的设备对应的中断不同,每个中断都通过一个唯一的数字标示,中断号(中断请求线)
中断上下文:环境(保存中断时需要存储的环境信息)
进程上下文:应用程序的进程到内核中执行,保存的环境信息(变量数据)
对硬件而言:os希望能迅速对其中断进程服务
对其他设备而言:希望中断处理程序能够尽可能短的时间内运行完成
中断处理流程:
1、获取中断号
一旦有中断请求,进行处理时,一般要禁止中断响应
1)中断号入栈(中断号对应中断服务程序)
2)寄存器信息入栈
2、中断串行化
多个cpu同时产生某一中断的串行化处理
当一个cpu正在处理相同中断的时候,设置一个“触发”标记,正在处理同一中断的cpu完成一次处理后,进行检查“触发”标记,如果设置,再次响应
3、关中断条件下的中断处理
注册中断处理函数,简单读取寄存器中的中断状态,并清除中断标志(登记中断)
4、开中断条件下的软中断
每个CPU有一组中断号,中断有对应的优先级,每个CPU处理属于自己的中断
5、开中断条件下的tasklet (或者工作队列)
中断处理函数中再次进行一次调用 tasklet_schedule
执行一个tasklet处理函数
中断号--->中断处理程序-->tasklet处理函数
tasklet实现---中断的所有事情,处理相对来说不是非常紧急的事件
中断处理的一般过程:
1、打开文件时进行注册中断(中断号,中断处理程序)
2、在没有中断产生时,当前进程进入休眠
3、如果有中断产生,执行中断处理函数(做相应操作,唤醒休眠的进程)-->tasklet处理函数
注意:如果中断处理的内容非常少,能在尽可能短的时间内完成,也可以都在中断处理函数中实现
tasklet_init 任务链初始化,对应某个名字的任务链,它的处理函数是什么,传递给该函数什么参数
request_irq 注册中断,对应某个中断号,其中断处理程序是什么,什么方式会触发中断,为中断处理函数传递什么参数
按键驱动引入中断:
中断实现:中断的处理分成2个部分;
顶半部:实现尽可能少,比较紧急的工作,登记中断,读取寄存器中的中断状态,清除中断标志
底半部:实现大部分的中断处理,处理对时间要求不高
tasklet机制(实现底半部的一种实现方法)
1、引入头文件 #include<linux/interrupt.h>
2、定义中断号 irq
3、实现中断处理函数
4、注册中断(init()中注册,open()注册)
request_irq(中断号,中断处理函数,中断标志,设备名称,传递给中断处理函数的参数);
5、定义一个小任务结构体 mytasklet
6、实现小任务的处理函数(处理对时间要求不高的中断处理大部分操作放在该位置)
7、初始化小任务(在模块的init函数中)
tasklet_init(小任务结构体对象,小任务处理函数,传给给处理函数的参数);
实质上,小任务是中断处理函数执行过程中的又一个函数调用(tasklet_shcedule)
=======================================
修改程序使用中断实现按键驱动:
1)应用程序一直查询,cpu效率低
2)中断,在设备需要时产生中断,cpu进行处理,效率高
修改程序:
1、考虑,在应用读取数据时,由于此时没有按键被按下,将读取操作挂入等待队列,休眠
2、在按键按下时,产生一个中断,执行对应的中断处理程序,读取对应寄存器位的值,如果读到某个按键按下的位,将该数据传递给应用程序
test 驱动 中断
read
myfirst_read,休眠
一般情况,等待,如果 按键按下,执行中断处理
检测对应寄存器位,准 备传给应用程序的数据
vpress=1,唤醒休眠队列
copy_to_user()
read接收数据,打印
测试:
/root/opt/EmbedSky/linux-2.6.30.4/drivers/input/keyboard
1、tq2440_buttons.c
irqflags = IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING;
添加上IRQF_SHARED
2、回到内核第一层目录,执行make,获取内核镜像
3、/arch/arm/boot/zImage
4、烧写该镜像到开发板
5、执行我们的测试程序
中断按键驱动:
1、#include<linux/interrupt.h>
2、注册引脚为中断状态,在open()函数中
设置引脚为中断状态
request_irq(中断号,中断处理程序,中断标志,设备名称,传递给中断处理程序的参数);//注册中断
3、实现中断处理程序
判断哪个按键被按下,准备数据,设置全局变量key_val
vpress=1;
wake_up_interruptible(&key_queue);
4、read()函数中,传递内核准备好的数据(哪个按键被按下)给应用程序
wait_event_interruptible(key_queue,vpress);//vpress=0
vpress=0;
copy_to_user();
应用程序中:
while(1)
read(); ----引发驱动中的read函数执行,休眠
printf();
如何在开发板中查看所有的中断:
cat /proc/interrupts
任何在开发板中查看所有的设备:
cat /proc/devices
/devices/input/keyboard/tq2440-buttons.c
中断注册时,没有共享 IRQF_SHARED,所以我们的中断按键驱动,没有办法生效,修改该文件,编译重新得到镜像文件,烧写到内核
============================================================
考虑:驱动中的read函数在进行休眠时,等待按键被按下,如果按键没有被按下,将不再返回应用程序,无法做其他的操作
引入:poll机制,设置一个等待时间,在这个时间内,如果按键被按下,执行数据的传递,打印数据;如果超时,返回应用程序,执行其他的操作。
应用中调用帮助 man 2 poll
应用程序修改:
1、引入头文件 #include <poll.h>
2、定义结构体数组:
struct pollfd key_pollfd[1];
3、对该数组成员进行初始化
key_pollfd[0].fd=fd;
key_pollfd[0].events=POLL_IN|POLLRDNORM;
4、调用poll()函数
ret=poll(key_pollfd,1,5000);
5、根据返回值进行判断:
1)如果返回-1,错误
2)如果返回0,超时,5s内没有按键被按下
3)返回0x0001,说明数据准备就绪(按键被按下),接收数据打印
驱动修改:
#include <linux/poll.h>
1、修改read()函数,去掉休眠的语句;
2、添加poll()函数,该函数执行时
unsigned int mask=0;
poll_wait();进入休眠,当按键被按下,执行中断处理程序,准备好数据,同时被vpress=1,唤醒
if (vpress=1)
mask=POLL_IN|POLLRDNORM;
return mask;
3、
poll机制:(对中断按键驱动进行改写)
一、应用程序中:
struct pollfd key_pollfd[1];
key_pollfd[0].fd=fd; //当前打开的文件描述符
key_pollfd[0].events=POLLIN; //期望实现的事件,POLLIN读就绪
ret=poll(key_pollfd,1,5000); //ms为单位,5s的超时时间
if (ret==-1)
printf("error!\n");
else if (ret==0)
{
printf("time out!\n");
......
}
else //返回POLLIN对应的值,0x0001
{
read();
printf();
}
二、驱动中修改
1、引入头文件 #include <linux/poll.h>
2、修改file_operations对应关系
.poll =myfirst_poll,
3、实现myfirst_poll()函数
unsigned int mask=0;
poll_wait(); //进入休眠,等待按键按下(准备数据,vpress=1,唤醒队列),或者超时
if (vpress)
mask |= POLLIN|POLLRDNORM;
return mask;
=================================================
引入内核定时器:
按键本身的物理特性,发生尖锐抖动的现象,导致在按下时出现多次中断请求,执行多次中断处理程序,每次都会输出修改的数据
如何消除抖动:
当第一个上升沿出现后,中断处理程序中的指令不立即执行,延迟一段时间再执行
延迟多久?
一般情况,抖动10ms,当中断发生后,10ms后再去输出数据
如何实现?
利用内核定时器,定时时间是10ms,当定时时间到,读取数据
========================================================
内核定时器:
<linux/timer.h>
jiffes:内核中的一个全局变量,用来记录自系统启动后产生的节拍数
timer结构体中的重要成员:(编程时使用)
unsigned long expires; // 超时时候jiffies的值
void (*function)(unsigned long); // 超时处理凼数
unsigned long data; // 内核调用超时处理凼数时传递给它的参数
tick:它是HZ的倒数,也就是每发生一次硬件定时器中断的时间间隔。如HZ为200,tick为5毫秒=1/200
时钟中断:
由系统的定时硬件以周期性的时间间隔发生,返个间隔(也就是频率)由内核根据常数HZ来确定
HZ常数
它是一个不体系结构无关的常数,可以配置50-1200之间,可以在内核中配置-----当前HZ=1000 ---> tick=1ms
5s
key_timer.expires =jiffes+5*HZ
定时器实现的一般步骤:
1)定义一个内核定时器 key_timer
2)初始化 init_timer(&key_timer);
3)对定时器成员赋值
key_timer.expires =... //决定了定时时长
key_timer.function=... //决定了到时间后执行哪个函数
key_timer.data =... //执行函数时接收的参数
4)添加定时器
add_timer();
5)在工作结束时,删除定时器
del_timer();
第12章 平台设备驱动模型
一、字符设备驱动程序编写的步骤:
1)引入头文件
2)许可证
3)file_operations建立对应关系
4)实现驱动中的函数(myfirst_open,myfirst_read...)
5)初始化和卸载函数
6)声明 module_init() module_exit()
特别提醒:当编写和硬件相关的驱动时,硬件资源(物理地址)通过地址映射在驱动中获得逻辑地址,从而操作相关的寄存器位,实现功能
特点:硬件资源和驱动代码写在同一个程序中
平台设备驱动模型:分离分层的概念,把硬件设备资源放在一个文件中(平台设备),把实际的驱动代码放在一个文件中(平台驱动)
平台设备资源会由内核统一管理,当驱动代码需要资源时,通过app函数进行申请操作
linux2.6内核中引入总线设备驱动模型。假设存在一条虚拟总线,称为platform总线(内核实现的,用户不需要自己定义总线的类型)。用户在实现时只需要添加相应的platform device 和platform driver
=========================================================
一、platform device 平台设备
1、重要的数据结构
1) struct resource
start 资源的起始位置(起始地址)
end 资源的结束位置(结束地址)
*name 资源的名称
flags 资源的类型(I/O,内存,中断)
2) struct platform_device
name =设备驱动的名字(***)
id =-1 (通常设置为-1,仅有当前一个该类型设备)
num_resource 资源的个数
resource 资源结构体对象
2、编写的一般步骤:
1)定义一个struct resource结构体对象,进行初始化(物理地址)
2)定义一个structplatform_device结构体对象,为其初始化
3)注册platform_device
platform_device_register(structplatform_device *dev);
4)注销platform_device
platform_device_unregister(structplatform_device *dev);
=========================================================
二、platform driver
1、重要的数据结构
struct platform_driver
proble 探测函数---注册驱动时,执行
remove 移除函数---释放probe对应的工作
structdevice_driver driver;
name=设备驱动的名字(***)
2、编写的一般步骤:
1)定义一个structplatform_driver结构体对象,为其初始化
2)实现两个重要的函数
probe() 获取资源(平台设备),普通字符设备驱动编写时的工作(注册字符设备,创建类,创建设备文件节点)
remove() 与probe工作恰好相反
3)注册platform_driver
platform_driver_register(structplatform_driver *drv);
4)注销
platform_driver_unregister(structplatform_driver *drv);
补充:
获取资源 struct resource*platform_get_resource();从而访问到平台设备中定义的各种硬件资源
==================================================
实例:控制天嵌开发板上的led灯,让其亮灭(采用平台设备驱动的形式)
led_driver_platform.c ---平台驱动(控制灯的驱动代码)
led_device_platform.c ---平台设备(硬件资源)
Makefile ---对多个模块编译
test_led_devdri_platform.c ----测试程序(控制灯亮灭)
块设备驱动程序:
一、与字符设备异同
共同点:/dev目录下都有对应的文件节点
不同点:可随机访问,访问时大小都是512的倍数
块设备一般要实施优化:(请求不会立即执行,优化后执行)
request--->request_queue
为了提高访问的效率:
1、硬盘:(由于硬盘读写最耗时间,寻找读写位置的机械操作,如果按照字符设备的形式写块设备驱动,会造成效率降低),进行优化后读写
1)先不执行,放入队列
2)优化后(调整读写顺序)再执行
2、flash:存储特征是若干块的罗列,每个块包含若干扇区,操作特点是整块读写,如果要修改扇区0和扇区1,则单独对任何一个扇区操作都要执行如下步骤:
1)读整块到buffer
2)修改buffer中的扇区0
3)擦除整块
4)烧写整块
如何优化:
1)先不执行,放入队列
2)优化后(合并操作)再执行
========================================================
对应块设备:
当块设备进行读写操作时,不立即执行,对应的操作产生一个request请求,该请求放入到request_queue的队列中,根据块设备的类型进行优化(调整顺序或者合并),统一操作
二、块设备驱动编写框架
app: open read write "1.txt"
----------------------------------------------对文件的读写
文件系统: vfat,ext2,ext3,yaffs,jffs(把对文件的操作,转化为对扇区的操作
-----------ll_rw_block--------扇区的读写
1、把“读写”放入队列
2、调用队列处理函数(优化,合并,顺序)
块设备驱动中完成
-------------------------------------
硬件: 硬盘 flash
文件的读写----(文件系统)---对扇区的操作
通过跟踪访问ll_rw_block()的执行,最终调用q->request_fn(q)
=======================================================
重要的数据结构:
request_queue 请求队列
request 一个读写请求
gendisk 表示一个块设备
blk_device_operations 块设备的相关操作
块设备驱动编写框架:
1)分配一个gendisk结构体
2)对结构体进行设置
2.1 分配/设置队列 request_queue //blk_init_queue
2.2 设置gendisk的其他信息(容量、主设备号,blk_device_operations)
3)添加块设备 add_disk
drivers\block\xd.c 或者 drivers\blick\z2ram.c
----------------------------------------------------------------------
实例
1、简单的块设备驱动程序
测试效果:插入内核后执行请求处理函数,死机(没有做任何实际的操作)
2、修改请求处理函数,对请求队列中每一个请求做操作
end_request(req,1); //如果第二个参数为1,成功执行
虚拟的块设备驱动:
1、由于对请求队列中的请求无实质性处理(死机)
2、使用电梯调度算法,依次取出每个请求,使用
end_request(req,1); //参数为1,表示正确处理
3、格式化、挂载(真正分配一块空间,完成读写操作)
格式化:mkdosfs /dev/myramblock
挂载: mount /dev/myramblock /tmp
尝试创建文件,复制文件到当前位置
分区,格式化,挂载
分区: fdisk /dev/myramblock
m 帮助
n 新建分区
p主分区
。。。。。
w 保存退出
- Linux驱动笔记:SPI驱动
- linux驱动阅读笔记
- linux i2c驱动笔记
- linux 8250驱动笔记
- Linux 驱动学习笔记
- linux驱动模型---笔记
- linux i2c驱动笔记
- Linux IIC驱动笔记
- linux i2c驱动笔记
- linux驱动移植笔记
- LINUX SPI驱动笔记
- linux IIC驱动笔记
- arm linux驱动笔记
- 笔记~~linux驱动
- linux IIC驱动笔记
- Linux IIC驱动笔记
- linux驱动 pci笔记
- linux驱动学习笔记
- 对文本分类的函数
- ubuntu安装sshd错误
- AI³驱动,全联接牵引:实现向智能制造升级
- Linux-----Linux源码安装软件
- 51Nod-1394-差和问题
- Linux驱动笔记
- Oracle存储过程知识学习
- request的获取路径用法小结
- zookeeper 分布式共享锁
- 跨平台设置NODE_ENV
- FragementPagerAdapter白屏问题
- Vue-基础
- 第四章:控制执行流程
- unity解决APP在手机发烫问题