这节我们来分享一下sqlite3的交叉编译和移植过程。
先来简单介绍一下sqlite3.
SQLite3是一个非常强大的小型开源数据库软件,特别嵌入到一些软件中存储数据,当然,移植到嵌入式系统上也是非常有必要的。众所周知的Symbian,Android, IOS这几大移动平台都是使用的sqlite作数据存储,使用SQLite的厂商包括Adobe, Airbus, Apple, Dropbox, Mozilla, GE, Google, McAfee, Microsoft, PHP, Python, Toshiba等等。目前SQLite的赞助商包括Oracle, Adobe, Mozilla等。官网的介绍在这里http://sqlite.org/about.html。
优点:
1、体积非常小巧,shell版的可执行文件仅600K左右,所有运行库加起来也不过3M多。
2、性能很好,和mysql相比性能完全不在其之下。
3、整个数据库存储在一个单一的文件里,备份恢复都非常容易。
缺点:
对于并发的支持不好,其对于数据库的读写是完全加锁的。
先说一下我的上位机环境:
PC:XP sp3,深度完美 纯净标准版
虚拟机: VMware® Workstation,版本:9.0.2 build-1031769
ubuntu:12.04
开发板环境:
FS4412开发板:采用三星ARM Exynos 4412四核处理器,Linux3.14内核版本,Uboot 2013.03,gcc-4.6.4交叉编译链
我们最终目的是在我们的开发板上能够执行使用了sqlite3库的可执行文件。
要达到这个目的我们需要几个步骤:
1、需要使用交叉编译链交叉编译sqlite3库;
2、将生成的动态库加入到文件系统的lib目录中;
3、使用交叉编译出来的sqlite3库,交叉编译.c文件,然后在开发板中执行文件;
安装前准备:
sqlite3源码包下载地址:http://sqlite.org/download.html
我移植的是目前最新的版本 sqlite-autoconf-3130000.tar.gz
这个版本值得一提的是这个版本的说明是C source code as an amalgamation. Also includes a "configure" script and TEA makefiles for the TCL Interface.
它包含一个TEA的makefiles,提供了TCL接口,在之前的版本,TCL接口是默认安装在sqlite3中的,在交叉编译的时候可能会由于没有TCL库而报错:
/src/tclsqlite.c: In function `DbUpdateHandler':
/src/tclsqlite.c:333: warning: passing arg 3 of `Tcl_ListObjAppendElement' makes pointer from integer without a cast
/src/tclsqlite.c: In function `tclSqlFunc':
/src/tclsqlite.c:419: warning: passing arg 1 of `Tcl_NewByteArrayObj' discards qualifiers from pointer target type
这个都时tcl相关的错误,可以先安装ActiveTcl以解决.假如你不需要tcl支持,那么这个错误可以这样避免:
./configure --disable-tcl --prefix=/home/linux/sqlite-ix86-linux/
但是这个版本有单独的makefile给TCL,所以只是使用默认的makefile是不需要考虑TCL的,给我们带来了不少的方便。
第一步:交叉编译sqlite3库
跟一般的安装一样,开始使用configure生成Makefile:
解压sqlite-autoconf-3130000.tar.gz得到sqlite-autoconf-3130000源码包目录。
创建sqlite3目标文件夹mkdir /home/linux/sqlite3/sqlite3.13,用于保存生成的glib库文件。
键入命令:./configure --host=arm-linux --prefix=/home/linux/sqlite3/sqlite3.13
应该不会遇到任何问题,并生成了Makefile文件。
然后就是三部曲中的
make
make insall
这个时候就会在sqlite3.13文件夹下看到交叉编译好的sqlite3库文件。里面会有bin,include,lib,share四个文件夹,分别有我们编译和运行时的文件和库。
第二步:将生成的动态库加入到文件系统的lib目录中
现在我们需要将编译好的sqlite3动态库放到nfs文件系统的lib目录下,这样我们的程序在开发板上执行的时候就可以调用sqlite3动态库了。
我们输入命令cp libsqlite3.so.0.8.6 /nfs/rootfs/lib/
切换到/nfs/rootfs/lib/目录下,然后建立两个指向libsqlite3.so.0.8.6的硬链接,libsqlite3.so和libsqlite3.so.0
cd /nfs/rootfs/lib/
sudo in libsqlite3.so.0.8.6 libsqlite3.so
sudo in libsqlite3.so.0.8.6 libsqlite3.so.0
也可以使用cp -a命令:
cp -a libsqlite3.* /nfs/rootfs/lib/
然后在删除静态编译库文件 libsqlite3.la
rm libsqlite3.la
这样就完成了将生成的动态库加入到文件系统的lib目录中。
第三步:使用交叉编译出来的sqlite3库,交叉编译.c文件,然后在开发板中执行文件
我们现在拥有了sqlite3的交叉编译库,而且在nfs文件系统中也有了动态库,我们需要使用一下这个库编译一个有sqlite3函数的.C文件,并在开发板上运行起来。
我们写一个操作数据库的student.c文件来作为我们的源程序:
#include <stdio.h>
#include <sqlite3.h>
#define DATABASE "student.db"
#define N 128
int do_insert(sqlite3 *db);
int do_query(sqlite3 *db);
int callback(void* arg, int f_num , char ** f_value , char ** f_name);
int do_delete(sqlite3 *db);
int do_update(sqlite3 *db);
int do_close(sqlite3 *db);
int main(int argc, const char *argv[])
{
sqlite3 *db;
char *errmsg;
int n = 0;
if (sqlite3_open(DATABASE,&db) != SQLITE_OK)
{
sqlite3_errmsg(db);
}
else
{
printf("database open success!\n");
}
if (sqlite3_exec(db, "create table stu(id int , name char , score int);", NULL, NULL, &errmsg) != SQLITE_OK)
{
printf("%s\n", errmsg);
}
else
{
printf("create table success.\n");
}
while (1)
{
printf("************************************\n");
printf("1 : insert \n");
printf("2 : delete \n");
printf("3 : update \n");
printf("4 : query \n");
printf("5 : quit \n");
printf("************************************\n");
printf("Input:");
scanf("%d", &n);
switch (n)
{
case 1:
do_insert(db);
break;
case 2:
//
do_delete(db);
break;
case 3:
//
do_update(db);
break;
case 4:
do_query(db);
break;
case 5:
do_close(db);
return 0;
break;
default:
printf("Invalid data n!\n");
break;
}
}
return 0;
}
int do_insert(sqlite3 *db)
{
char *errmsg;
char sql[N] = {};
int id;
char name[32] = {};
int score;
printf("Input id:");
scanf("%d",&id);
printf("Input name:");
scanf("%s", name);
printf("Input score:");
scanf("%d", &score);
sprintf(sql, "insert into stu values(%d, '%s', %d);", id, name, score);
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg)!= SQLITE_OK)
{
printf("%s\n", errmsg);
}
else
{
printf("Insert done!\n");
}
return 0;
}
int callback(void* arg, int f_num , char ** f_value , char ** f_name)
{
int i = 0;
printf("f_num = %d \n", f_num);
for (i = 0; i < f_num; i++)
{
printf("%s\t", f_value[i]);
}
putchar(10);
return 0;
}
int do_query(sqlite3 *db)
{
char *errmsg;
char sql[N] = {};
int id;
char name[32] = {};
int score;
printf("Input id:");
scanf("%d",&id);
sprintf(sql, "select * from stu where id = %d", id);
if (sqlite3_exec(db, sql,callback, NULL, &errmsg)!= SQLITE_OK)
{
printf("%s\n", errmsg);
}
else
{
printf("Select done!\n");
}
}
int do_delete(sqlite3 *db)
{
char *errmsg;
char sql[N] = {};
int id;
char name[32] = {};
int score;
printf("Input id:");
scanf("%d",&id);
sprintf(sql, "delete from stu where id = %d;", id);
if (sqlite3_exec(db, sql,NULL, NULL, &errmsg)!= SQLITE_OK)
{
printf("%s\n", errmsg);
}
else
{
printf("Select done!\n");
}
}
int do_update(sqlite3 *db)
{
char *errmsg;
char sql[N] = {};
int id, n;
char name[32] = {};
int score;
int oldid, newid;
char oldname[32] , newname[32];
int oldscore, newscore;
printf("************************************\n");
printf("1 : id \n");
printf("2 : name \n");
printf("3 : score \n");
printf("************************************\n");
printf("Input:");
scanf("%d", &n);
switch (n%3)
{
case 1:
printf("Input oldid newid:");
scanf("%d %d", &oldid, &newid);
sprintf(sql, "update stu set id=%d where id = %d", newid, oldid);
if (sqlite3_exec(db, sql,NULL, NULL, &errmsg)!= SQLITE_OK)
{
printf("%s\n", errmsg);
}
else
{
printf("update done!\n");
}
break;
case 2:
printf("Input oldname newname:");
scanf("%s %s", oldname, newname);
sprintf(sql, "update stu set name='%s' where name = '%s'", newname, oldname);
if (sqlite3_exec(db, sql,NULL, NULL, &errmsg)!= SQLITE_OK)
{
printf("%s\n", errmsg);
}
else
{
printf("update done!\n");
}
break;
case 0:
printf("Input oldscore newscore:");
scanf("%d %d", &oldscore, &newscore);
sprintf(sql, "update stu set score=%d where score = %d", newscore, oldscore);
if (sqlite3_exec(db, sql,NULL, NULL, &errmsg)!= SQLITE_OK)
{
printf("%s\n", errmsg);
}
else
{
printf("update done!\n");
}
break;
default:
break;
}
}
int do_close(sqlite3 *db)
{
char *errmsg;
if (sqlite3_close(db)!= SQLITE_OK)
{
printf("%s\n", errmsg);
}
else
{
printf("close done!\n");
}
}
然后我们需要知道一些编译时的参数,比如头文件路径的设置,库文件路径的设置,在编译时使用 -I加上路径来表示头文件的路径,-L表示库文件的路径。
在ubuntu上我们编译sqlite3库文件时使用的方法是gcc xxx.c -o xxx -sqlite3 这里的-o后面是生成的目标文件名,不是必须要的,如果不加会生成a.out文件,-l后面是我门使用的库文件名,是去掉了lib开头的库文件名,这里为什么我们没有使用-I -L呢,因为我们已经把这些文件放到了系统默认路径中,编译的时候会自动去默认路径下找相应的文件。交叉编译的时候我们也需要对应的书写编译命令,这里我先把命令写出来,然后在讲解,编译命令如下:
arm-linux-gcc student.c -o student-arm -I/home/linux/sqlite3/sqlite3.13/include -L/home/linux/sqlite3/sqlite3.13/lib -lsqlite3
其中arm-linux-gcc是交叉编译用的gcc,还有一个arm-none-linux-gnueabi-gcc命令,其实和它一模一样的;
student.c 是我们要编译的源码文件,-o student-arm我们要生成的目标文件;
-I/home/linux/sqlite3/sqlite3.13/include我们头文件路径;
-L/home/linux/sqlite3/sqlite3.13/lib我们库文件路径;
-lsqlite3我们调用的库文件;
然后我们将生成的student-arm文件复制到nfs文件系统目录中,cp student-arm /nfs/rootfs
将开发板设置为nfs挂载,然后我们启动开发板,运行./student-arm,哈哈看到运行结果了,和电脑上的运行结果一样,表示我们移植成功了。
[root@farsight ]# ./student-arm
./student-arm: error while loading shared libraries: libsqlite3.so.0: cannot open shared object file: No such file or directory
[root@farsight ]# ./student-arm
database open success!
table stu already exists
************************************
1 : insert
2 : delete
3 : update
4 : query
5 : quit
************************************
Input:4
Input id:1003
f_num = 3
1003 lisi 87
Select done!
************************************
1 : insert
2 : delete
3 : update
4 : query
5 : quit
************************************
Input:
下面我简单讲一下脚本文件的编写:
当我们完成这一系列的工作后最好的习惯是将这个步骤编写成一个脚本,让我们以后再做以上工作的时候可以进行简单的修改,运行一个文件就可以完成整个过程。想一想都感觉很爽。
就那我们这次的移植过程为例来制作一个移植脚本build-sqlite3-arm.sh:
#!/bin/sh
SRC_FLIE=sqlite-autoconf-3130000
TARGET_DIR=$PWD/sqlite3.13
tar xvf $SRC_FLIE.tar.gz
cd $TARGET_DIR
./configure --host=arm-linux --prefix=$TARGET_DIR
make;make install
echo "shell finish!"
运行前需要给脚本文件提供可执行权限
chmod 777 build-sqlite3-arm.sh
这里就是简单的几行,我也简单的说一下,第一行表示这是一个脚本文件;
第二行设置要操作的sqlite3源码压缩文件;
第三行设置安装库文件的目标目录;
第四行是解压源码文件;
第五行是切换到生成的库文件目录;
第六行执行configure;
第七行执行make和make install;
第八行输出到终端提示脚本执行完成。
我们在进行sqlite3的交叉编译的时候之需要根据情况修改SRC_FLIE和TARGET_DIR,然后执行./build-sqlite3-arm.sh
这里是最简单的shell脚本文件,通过对脚本语言的学习,我们还可以写出个更完善的脚本。