第一章 第一个linux驱动程序 单词统计
1 编写linux驱动程序的步骤
建立linux驱动骨架 :module_init module_exit
注册和注销设备文件:misc_register misc_deregister
指定与驱动相关的信息
指定回调函数
编写业务逻辑
编写makefile文件
编译linux驱动程序
安装和卸载linux驱动程序
2 统计单词个数驱动程序
Word_count.c文件内容如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
//定义设备文件名
#define DEVICE_NAME "wordcount"
//保存向设备文件写入的数据
static unsigned char mem[10000];
//单词数
static int word_count = 0;
#define TRUE 255
#define FALSE 0
//检查字符是否为空格
static unsigned char is_spacewhite(char c)
{
if (c == 32 || c == 9 || c == 13 || c == 10)
return TRUE;
else
return FALSE;
}
//统计单词数
static int get_word_count(const char *buf)
{
int n = 1;
int i = 0;
char c = ' ';
char flag = 0; // 处理多个空格分隔的情况,0:正常情况,1:已遇到一个空格
if (*buf == '\0')
return 0;
// 第1个字符是空格,从0开始计数
if (is_spacewhite(*buf) == TRUE)
n--;
// 扫描字符串中的每一个字符
for (; (c = *(buf + i)) != '\0'; i++)
{
// 只由一个空格分隔单词的情况
if (flag == 1 && is_spacewhite(c) == FALSE)
{
flag = 0;
}
// 由多个空格分隔单词的情况,忽略多余的空格
else if (flag == 1 && is_spacewhite(c) == TRUE)
{
continue;
}
// 当前字符为空格是单词数加1
if (is_spacewhite(c) == TRUE)
{
n++;
flag = 1;
}
}
// 如果字符串以一个或多个空格结尾,不计数(单词数减1)
if (is_spacewhite(*(buf + i - 1)) == TRUE)
n--;
return n;
}
//从设备文件读取数据
//file:指向设备文件
//buf:保存可读取的数据
//count:可读取的字节数
//ppos:读取数据的偏移量
static ssize_t word_count_read(struct file *file,char __user *buf,size_t count,loff_t *ppos){
unsigned char temp[4];
temp[0] = word_count >> 24;
temp[1] = word_count >> 16;
temp[2] = word_count >> 8;
temp[3] = word_count;
if (copy_to_user(buf, (void*) temp, 4))
{
return -EINVAL;
}
printk("read:word count:%d", (int) count);
return count;
}
//向设备文件写入数据
//file:指向设备文件
//buf:保存写入的数据
//count:写入数据的字节数
//ppos:写入数据的偏移量
static ssize_t word_count_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos){
size_t written=count;
//将用户空间的数据,复制到内核空间
if(copy_from_user(mem,buf,count)){
return -EINVAL;
}
mem[count]="\0";
//统计单词个数
word_count=get_word_count(mem);
//输出已写入的字节数
printk("written count:%d",(int)word_count);
return written;
}
//描述与设备文件触发的事件对应的回设函数指针
//owner:设备事件回调函数应用于哪此驱动模块
//指定调用的函数
static struct file_operations dev_fops={
.owner= THIS_MODULE,
.read=word_count_read,
.write=word_count_write
};
//描述设备文件的信息
//minor:指定次设备号,此处是动态生成
//name:设备文件名称
static struct miscdevice misc={
.minor=MISC_DYNAMIC_MINOR,
.name=DEVICE_NAME,
.fops=&dev_fops
};
//初始化linux驱动
static int word_count_init(void){
int ret;
//建立设备文件
ret=misc_register(&misc);
//输出日志信息
printk("word_count_init_success\n");
return ret;
}
//退出linux驱动
static void word_count_exit(void){
misc_deregister(&misc);
printk("word_count_init_exit_sucess\n");
}
//注册初始化linux驱动的函数
module_init(word_count_init);
//注册退出linux驱动的函数
module_exit(word_count_exit);
//指定驱动的相关信息
//模块作者
MODULE_AUTHOR ("retacn_yue");
//模块描述
MODULE_DESCRIPTION("statistics of word count.");
//模块别名
MODULE_ALIAS("word count module.");
//开源协议
MODULE_LICENSE("GPL");
在主机linux环境下运行
编写makefile文件
ifneq ($(KERNELRELEASE),)
obj-m :=word_count.o
else
#KERNELDIR=/usr/src/kernels/2.6.35.14-106.fc14.i686/
KERNELDIR=/usr/src/linux-headers-3.2.0-29-generic
PWD :=$(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD)
clean:
rm -rf *.ko *.o *.mod.c *.mod.o *.symvers *.order
endif
Make编译程序:
root@vm:/opt/linux/driver/word_count# make
make -C /usr/src/linux-headers-3.2.0-29-generic M=/opt/linux/driver/word_count
make[1]: 正在进入目录 `/usr/src/linux-headers-3.2.0-29-generic'
LD /opt/linux/driver/word_count/built-in.o
CC [M] /opt/linux/driver/word_count/word_count.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/linux/driver/word_count/word_count.mod.o
LD [M] /opt/linux/driver/word_count/word_count.ko
make[1]:正在离开目录 `/usr/src/linux-headers-3.2.0-29-generic'
会以当前目录生成.ko的库文件
在主机安装驱动程序测试
#安装linux驱动
root@vm:/opt/linux/driver/word_count# insmod word_count.ko
#查看是否安装成功
root@vm:/opt/linux/driver/word_count# lsmod | grep word_count
word_count 20942 0
#查看驱动输出的日志信息
root@vm:/opt/linux/driver/word_count# dmesg | grep word_count | tail -n 2
[ 1917.206964] word_count_init_success
编写在linux下的测试程序test_word_count.c示例代码如下:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int testdev;
unsigned char buf[4];
testdev = open("/dev/wordcount", O_RDWR);
if (testdev == -1)
{
printf("Cann't open file \n");
return 0;
}
if (argc > 1)
{
write(testdev, argv[1], strlen(argv[1]));
printf("string:%s\n", argv[1]);
}
read(testdev, buf, 4);
int n = 0;
// 将4个字节还原成int类型的值
n = ((int) buf[0]) << 24 | ((int) buf[1]) << 16 | ((int) buf[2]) << 8
| ((int) buf[3]);
printf("word byte display:%d,%d,%d,%d\n", buf[0], buf[1], buf[2], buf[3]);
printf("word count:%d\n", n);
close(testdev);
return 0;
}
编译程序:
root@vm:/opt/linux/driver/word_count# gcc test_word_count.c -o test_word_count
root@vm:/opt/linux/driver/word_count# ls
built-in.o modules.order word_count_book.c word_count.mod.o
linux_test Module.symvers word_count.c word_count.o
Makefile test_word_count word_count.ko
Makefile_arm test_word_count.c word_count.mod.c
查看dev中的目录设备
crw------- 1 root root 10, 55 8月 8 10:51 wordcount
注: 可以看出主设备号 为10,是简单字符设备
Misc_register只能设置次设备号
查看当前系统中的主设备和主设备号
root@vm:/opt/linux/driver/word_count# cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
5 ttyprintk
6 lp
7 vcs
10 misc
13 input
测试驱动程序
root@vm:/opt/linux/driver/word_count# ./test_word_count "hello retacn"
string:hello retacn
word byte display:0,0,0,2
word count:2
卸载驱动程序
root@vm:/opt/linux/driver/word_count# rmmod word_count
查看模块信息
root@vm:/opt/linux/driver/word_count# modinfo word_count.ko
filename: word_count.ko
license: GPL
alias: word count module.
description: statistics of word count.
author: retacn_yue
srcversion: 2CD81D366F7598BAB6C1384
depends:
vermagic: 3.2.0-29-generic SMP mod_unload modversions
开源协议
GPL 强迫使用该开源协议的软件开源
LGPL 通类库引用的方式使用LGPL类库,不需要开源软件的代码
BSD 可以自由修改可开源,也可不开源
Appach licence 2.0 协议 同 BSD协议
MIT协议 同BSD 作者只保留版权
注册注销设备文件
Extern int misc_register(struct miscdevice * misc);
Extern int misc_deregister(struct miscdevice * misc);
指定回调函数
在fileoperation中指定回调函数
内核空间和用户空间的数据互访 copy_to_user copy_from_user
编写编译角本
可以在ubuntu linux mini6410开发板和android模拟器上测试单词统计驱动
Common.sh角本文件内容如下:
#ubuntu内核目录
UBUNTU_KERNEL_PATH=/usr/src/linux-headers-3.2.0-29-generic
#开发板linux内核目录
6410_KERNEL_PATH=/opt/kernel/linux-2.6.38
#开发板android内核目录
S3C6410_ANDROID_KERNEL_PATH=/opt/kernel/linux-2.6.36-android
#android源码
OK6410_ANDROID_SRC_PATH=/opt/kernel/Android-2.3.4
S3C6410_ANDROID_SRC_PATH=/opt/kernel/Android-2.3.4
#设定交叉编译工具链路径
export PATH=$PATH:/opt/FriendlyARM/toolschain/4.5.1/bin
selected_device="" # "":无可用Android设备
selected_target=1 # 选择目标, 1: ubuntu linux 2: Android设备
#选择目标设力备
function select_target()
{
echo "1:Ubuntu Linux"
echo "2:MINI6410开发板"
echo "3:Android模拟器"
read -p "请选择要在那类设备上运行(1)" selected_target
if [ "$selected_target" == "" ]; then
selected_target=1
fi
}
#查找android设备
function find_devices()
{
device_list=$(adb devices)
if [ "${device_list:0:4}" != "List" ]; then
device_list=$(adb devices)
if [ "${device_list:0:4}" != "List" ]; then
exit
fi
fi
value=$(echo $device_list | cut -d' ' -f5)
if [ "$value" == "" ]; then
echo "无可用Android设备"
else
selected_device=$value # 假设只有1个设备
value=$(echo $device_list | cut -d' ' -f7)
# 多个设备
if [ "$value" != "" ]; then
i=5
index=1
value="~~~"
echo "可用设备列表"
while [ "" == "" ]
do
value=$(echo $device_list | cut -d' ' -f$i)
let "i=$i+2"
if [ "$value" == "" ]; then
break;
fi
echo "$index: $value"
let "index=$index+1"
done
read -p "您想选择哪个Android设备?请输入序号(1):" number
if [ "$number" == "" ]; then
number=1
fi
let "number=3 + $number * 2"
selected_device=$(echo $device_list | cut -d' ' -f$number)
fi
fi
}
Build.sh内容如下
source /opt/driver/common.sh
select_target
if [ $selected_target ==1 ]; then
source ./build_ubuntu.sh
elif [ $selected_target ==2 ]; then
source ./build_mini6410.sh
elif [ $selected_target == 3 ]; then
source ./build_emulater.sh
Fi
build_ubuntu.sh 文件内容如下:
#build_ubuntu.sh
source /opt/linux/driver/common.sh
make -C $UBUNTU_KERNEL_PATH M=${PWD}
testing=$(lsmod | grep "word_count")
if [ "$testing" != "" ]; then
rmmod word_count
fi
insmod ${PWD}/word_count.ko
#build_mini6410.sh文件内容如下:#build_mini6410.sh
source /opt/linux/driver/common.sh
#make -C $MINI6410_KERNEL_PATH M=${PWD}
make -C $MINI6410_ANDROID_KERNEL_PATH M=${PWD}
find_devices
if [ "$selected_device" == "" ]; then
exit
else
adb -s $selected_device push ${PWD}/word_count.ko /data/local
testing=$(adb -s $selected_device shell lsmod | grep "word_count")
if [ "$testing" != "" ]; then
adb -s $selected_device shell rmmod word_count
fi
adb -s $selected_device shell "insmod /data/local/word_count.ko"
adb -s $selected_device shell "chmod 777 /dev/wordcount"
fi
编译 安装 卸载linux驱动
查看日志输出信息和驱动模块信息
root@vm:/opt/linux/driver/word_count# dmesg | tail -n 1
[10256.765760] word_count_init_success
root@vm:/opt/linux/driver/word_count# modinfo word_count.ko
filename: word_count.ko
license: GPL
alias: word count module.
description: statistics of word count.
author: retacn_yue
srcversion: 2CD81D366F7598BAB6C1384
depends:
vermagic: 2.6.29-g46b05b2-dirty mod_unload modversions ARMv7
root@vm:/opt/linux/driver/word_count#
安装linux驱动命令 insmod 和modprobe ,两者的区别的modprobe可以检查模块的依赖性
在使用modprobe安装驱动前,要先使用depmod命令检测linux驱动模块的依赖关系
测试linux驱动
使用ubuntu linux测试驱动程序
可以使用如下命令进行测试:
root@vm:/opt/linux/driver/word_count# echo 'hello retacn yue' > /dev/wordcount
root@vm:/opt/linux/driver/word_count# dmesg
.....
[ 148.327839] hub 2-2:1.0: unable to enumerate USB device on port 2
[10128.904880] word_count_init_success
[10224.430495] word_count_init_exit_sucess
[10224.432402] word_count_init_success
[10256.763563] word_count_init_exit_sucess
[10256.765760] word_count_init_success
[13319.946934] written count:3
编写测试程序
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int testdev; //打开设备文件的句柄
unsigned char buf[4]; //表示单词个数的四个字节
//打开设备文件
testdev = open("/dev/wordcount", O_RDWR);
//打开文件失败,输出错误信息
if (testdev == -1)
{
printf("Cann't open file \n");
return 0;
}
if (argc > 1)
{
//向设备文件写入待统计的字符串
write(testdev, argv[1], strlen(argv[1]));
printf("string:%s\n", argv[1]);
}
//读取设备文失件中的单词数
read(testdev, buf, 4);
int n = 0;
// 将4个字节还原成int类型的值
n = ((int) buf[0]) << 24 | ((int) buf[1]) << 16 | ((int) buf[2]) << 8
| ((int) buf[3]);
//输出从设备文件获取的四个字节的值
printf("word byte display:%d,%d,%d,%d\n", buf[0], buf[1], buf[2], buf[3]);
//输出统计的单词数
printf("word count:%d\n", n);
//关闭设备文件
close(testdev);
return 0;
}
编译执行程序
//编译程序
root@vm:/opt/linux/driver/word_count# gcc test_word_count.c -o test_word_count
//没有命令行参数时
root@vm:/opt/linux/driver/word_count# test_word_count
word byte display:0,0,0,3
word count:3
//有命令行参数时
root@vm:/opt/linux/driver/word_count# test_word_count "hell retacn yue test"
string:hell retacn yue test
word byte display:0,0,0,4
word count:4
在android模拟器上使用原生c代码进行测试驱动程序
在使用android的linux内核进行编译时需要进行设置,内核默认不允许动态加载linux驱动,
在编译内核之前要进行如下配置:
[*] Enable loadable module support ---> 选中前三项,重新编译内核
按mini6410 编译内核的方式进行
使用编译好的内核创建模拟器,也可直接将系统烧写到开发板进行测试
此处使用的的在将内核烧写到开发板后进行测试
/ # echo 'hello retacn yue' > /dev/wordcount
/ # dmesg
//可以看到如下运行结果
<4>word_count_init_success
<6>request_suspend_state: wakeup (0->0) at 1889525693085 (2000-10-30 03:22:13.088438257 UTC)
<4>word_count_init_exit_sucess
<4>word_count_init_success
<4>written count:3/ #
使用test_word_count.c测试程序进行测试
编写android.mk文件,文件内容如下:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
#指定要编译的源代码文件
LOCAL_SRC_FILES := test_word_count.c
#指定模块名,也就是编译后生成的可执行文件名
LOCAL_MODULE :=test_word_count
#指定在什么模式下编译,optional是在任何模式下都可以编译
#可以设置的模式有:
#user 限制用户对android系统的访问,适用于发布产品
#userdebug 类似于user模式,但有root权限
#eng 一般在开发过程中设置该模式,有重多调试工具
#optional
LOCAL_MODULE_TAGS :=optional
#表示建立可执行文件
#静态库文件为BUILD_STATIC_LIBRARY
#动态库文件为BUILD_SDARED_LIBRARY
include $(BUILD_EXECUTABLE)
交叉编译测试程序(android源码编译不成功)
可以将word_count目录复制到android源码目录下
也可以在android源码目录中为word_count建立一个符号链接
这里我们使用第二种方式,在android源码目录中建立一个符号链接,命令如下:
root@vm:/opt/linux/driver/word_count# ln -s /opt/linux/driver/word_count/ /opt/kernel/Android-2.3.4/development
此时查看android源码目录下的development目录下会生成word_count目录的一个符号链接
初始化编译命令:
root@vm:/opt/kernel/Android-2.3.4# source ./build/envsetup.sh
编译程序的方法有两种:
一 进入~/word_count目录执行 mm命令
二 在根目下执行mmm命令
编译成功后可以在 /out/target/product/generic/system/bin下找到编译后的文件
将文件上传到开发板,未完成(源码未编译成功????????)
使用android NDK测试linux驱动程序,示例代码如下:
package cn.yue.android.word.count.ndk;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity;
/**
* linux驱动单词统计
*
* ndk测试程序
*
* @version
*
* @Description:
*
* @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>
*
* @since 2014-10-21
*
*/
public class MainActivity extends Activity implements OnClickListener {
/* 要写入的字符串内容 */
private EditText edt_write_word;
/* 显示单词个数 */
private TextView txt_word_count;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
}
/**
* 实例化控件
*/
private void findView() {
edt_write_word = (EditText) this.findViewById(R.id.edt_write_word);
txt_word_count = (TextView) this.findViewById(R.id.txt_read_wrod);
// 从设备文件读出单词个数
this.findViewById(R.id.btn_read).setOnClickListener(this);
// 向设备文件写入字符串
this.findViewById(R.id.btn_write).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_read:// 读取单词个数
txt_word_count.setText("单词数为: " + String.valueOf(readWordCountFromDev()));
Toast.makeText(MainActivity.this, readWordCountFromDev()+"", Toast.LENGTH_SHORT).show();
break;
case R.id.btn_write:// 向设备写入字符串
writeStringToDev(edt_write_word.getText().toString());
Toast.makeText(MainActivity.this, "已向/dev/word_count写入字符串"+edt_write_word.getText().toString(), Toast.LENGTH_SHORT).show();
break;
}
}
// 本地方法
public native int readWordCountFromDev();
public native void writeStringToDev(String str);
static {
System.loadLibrary("ndk_test_word_count");
}
}
#include <string.h>
#include <jni.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
jint Java_cn_yue_android_word_count_ndk_MainActivity_readWordCountFromDev(
JNIEnv* env, jobject thiz) {
int dev;
jint wordcount = 0;
unsigned char buf[4];
dev = open("/dev/wordcount", O_RDONLY);
read(dev, buf, 4);
int n = 0;
n = ((int) buf[0]) << 24 | ((int) buf[1]) << 16 | ((int) buf[2]) << 8
| ((int) buf[3]);
wordcount = (jint) n;
close(dev);
return wordcount;
}
char* jstring_to_pchar(JNIEnv* env, jstring str) {
char* pstr = NULL;
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
jstring strencode = (*env)->NewStringUTF(env, "utf-8");
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
"(Ljava/lang/String;)[B");
jbyteArray byteArray = (jbyteArray)(
(*env)->CallObjectMethod(env, str, mid, strencode));
jsize size = (*env)->GetArrayLength(env, byteArray);
jbyte* pbyte = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);
if (size > 0) {
pstr = (char*) malloc(size);
memcpy(pstr, pbyte, size);
}
return pstr;
}
void Java_cn_yue_android_word_count_ndk_MainActivity_writeStringToDev(
JNIEnv* env, jobject thiz, jstring str) {
int dev;
dev = open("/dev/wordcount", O_WRONLY);
char* pstr = jstring_to_pchar(env, str);
if (pstr != NULL) {
write(dev, pstr, strlen(pstr));
}
close(dev);
}
直接使用java代码测试 略
将驱动编译进linux内核进行测试
将动驱程序编译进android系统的linux内核进行测试
A将word_count.c放到linux内核代码中
root@vm:/opt/linux/driver/word_count# cp word_count.c /opt/kernel/linux-2.6.36-android/drivers/char
B 修改kconfig文件,在endmenu前添加如下代码:
config WORD_COUNT //word_count驱动模块的变量名为CONFIG_WROD_COUNT
bool "word_count_driver" //表示该驱动只参有两项设置
help //设置菜单项的帮助信息
this is a word count driver. It can get a word count from /dev/word_count
Endmenu
C 修改makefile文件,添加如下内容
obj-$(CONFIG_ATARI_DSP56K) += dsp56k.o
obj-$(CONFIG_MOXA_SMARTIO) += mxser.o
obj-$(CONFIG_COMPUTONE) += ip2/
obj-$(CONFIG_WORD_COUNT)+= word_count.o
obj-$(CONFIG_RISCOM8) += riscom8.o
obj-$(CONFIG_ISI) += isicom.o
D 设置.config文件,可以通过make menuconfig来进行配置
Device Drivers ---> Character devices ---> [*] word_count_driver
打开.config文件可以看到该项设置为y
CONFIG_WORD_COUNT=y
E 编译linux内核
Make
烧写到开发板进行测试
/ # chmod 777 /dev/wordcount
/ # echo "hello" >/dev/wordcount
/ # dmesg
可以看到如下内容
<4>written count:1written count:2
使用eclipse开发和测试linux驱动程序(linux下eclipse创建c工程有问题 ?需要重新安装cdt作测试)
建立c工程
File--->new--->other--->c project--->next--->工程名