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 保存退出

 

原创粉丝点击