| « | 三月 2010 | » | ||||
|---|---|---|---|---|---|---|
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 | ||||
Berkeley DB和SQLite
下面介绍的是两个开放源代码的嵌入式数据库,Berkeley DB和SQLite。同时侧重介绍如何应用Java连接这两种嵌入式数据库。
一. Berkeley DB
1. 简介
Berkeley DB是一款健壮的,高速的工业级嵌入式数据库产品,你可以在它的官方主页(见参考链接一)上发现很多知名的公司都采用了这款嵌入式数据库。 Berkeley DB的一个很重要的特点是就是高速存储。在高流量,高并发的情况下,Berkeley DB要比非嵌入式的数据库表现得更加出色。所以在一些技术实现上,Berkeley DB被作为大型关系数据库的中间数据缓冲层,用来快速的保存数据,可能会在适当的时刻再导入到大型数据库中,进而应用大型数据库所提供的更为高级的特性。
Berkeley DB虽然是开源的产品,但对某些条件下的商业性应用,却不是免费的,而且价格颇为昂贵。这些商业条件排除了开源的情况,不发放分布版本的情况,等等。比如,如果你的程序是开放源代码的或者仅仅应用到单一的网站上,在这种情况下,Berkeley DB是免费的。
2. 获得Java与Berkeley DB的接口
Berkeley DB目前的版本是4.1.25,自带了Java接口。下载的压缩包中包含C和Java语言的源代码和编译配置文件。在Windows平台,可以用MS Visual C++ 6.0或MS VC.Net编译。用VC6编译的操作如下介绍:在源代码的build_win32路径下打开VC的工程文件,之后在Build菜单中的Set Active Configuration选择db_java win32 release编译选项。在VC的Tools菜单Options选项中指定JNI.H等Java本地化接口编译时所需要头文件的位置。你会在JDK的 include路径下找到这些头文件, 例如加入的路径可能会是这样的:C:jdk1.4include和C:jdk1.4include win32。最后在Tools菜单中Options选项还要设置Javac.exe和Jar.exe的执行路径,这个设置会使VC开发环境也能调用 Java编译器,从而在VC环境下直接完成对Java接口类的编译和打包。在编译后,在release路径下的文件中找到db.jar, libdb41.dll, libdb_java41.dll,这三个文件组成了Berkeley DB的Java接口程序包。
3. 应用Java与Berkeley DB的接口
Berkeley DB并不是一个关系型的数据库。不能应用标准的SQL语句对数据库操作,对它的操作要调用专用的API实现。这些API提供了查询、插入、删除等功能。比如com.sleepycat.db.Db类代表数据库对象。Db类的put()方法完成的是插入功能,get()方法完成的是读出数据的功能。 com.sleepycat.db.Dbc是Berkeley DB的游标类,提供了遍历数据库记录的功能。
Berkeley DB每一个记录都有一个键值和对应的数据值,而键值和数据必须是类com.sleepycat.db.Dbt的对象或其子类的对象。Dbt提供了一些方法可以将byte数组或Object对象保存到Dbt的对象中去。比如,Dbt类中的set_data(byte[])或set_object (Object)方法。注意到目前Berkeley DB中的Java API命名方法并不符合Java的命名规范,比如set_data()方法应该命名为setData()方法。Berkeley DB许诺在下一个版本中会提供符合命名规范的Java API。
Berkeley DB对任何存入的数据都是直接原样存储到数据文件中去,无论其是二进制数据还是ASCII或Unicode等编码的文本。通常可以利用这一特性和Java串行化的概念方便的进行数据的存取。例如声明一个类
public class AccountInfo implements Serializable{
//帐户信息
public String loginName;
public String password;
public boolean auotLogin;
}
在这个AccountInfo类中仅仅包含了数据项的定义。我们完全可以将这个类看作数据库的表中字段定义。可以用Berkeley DB保存AccountInfo对象的串行化二进制数据,以此来保存这个对象中的变量值。在操作中,先对Dbt的对象调用set_object (AccountInfo)方法,而后把这个Dbt对象作为一条纪录保存到表中。当然,我们也可以应用继承Dbt类的方法来完成对数据的保存。
下面这段简单代码演示如何将数据存入到数据库中,然后再用游标对象浏览全部数据。
//注意,下面的程序的忽略了对异常处理,写入数据初始化等等一些代码,请在适当修改后再编
//译运行它
Db dbFile = null;
//生成Db对象
dbFile = new Db(null, 0);
//用BTree方式打开数据库,库文件是在c:/temp下的mydata.db文件,表名是employee
//如果数据库不存在,则自动生成一个新的数据库。
dbFile.open(null,"c:tempmydata.db","employee",Db.DB_BTREE,
Db.DB_CREATE,0);
Dbt key = new Dbt();
Dbt data = new Dbt();
//向库文件中插入一条数据,如果已经存在,打印出错信息
if (dbFile.put(null,key, data, Db.DB_APPEND) == Db.DB_KEYEXIST) {
System.out.println("Key already exists.");
}
//关闭数据文件
dbFile.close(0);
//重新打开数据文件
dbFile = new Db(null, 0);
dbFile.open(null, "c:tempmydata.db", "employee", Db.DB_UNKNOWN,
0, 0644);
// 声明一个数据库游标Dbc对象iterator
Dbc iterator = dbFile.cursor(null, 0);
// 遍历整个表
Dbt key = new Dbt();
while (iterator.get(key, data, Db.DB_NEXT) == 0)
{
System.out.println("reading");
}
//关闭游标和数据文件
iterator.close();
dbFile.close(0);
在运行Berkeley DB的程序时勿必在系统环境变量PATH中设置libdb41.dll和 libdb_java41.dll所在的路径。
4. Berkeley DB的存储模式
Berkeley DB提供了四种存储数据的模式:Btree,Hash,Queue和Recno。在打开数据库的时候要指定一种存储模式,比如上例中open()方法中的参数Db.DB_BTREE就是指定以Btree模式打开数据库。
Btree模式是以排序的二叉树的方式存储,Hash是以线性哈希表的方式存储。Queue用逻辑记录号做为键值,以定长的数据为记录值。 Recno方式也以逻辑记录号做为键值,但可以保存定长或变长的记录值。这里提到的逻辑记录号有两种,可变的和固定的。可变逻辑记录号会根据数据记录的增加与删除做相应的变化。比如在数据库中共有100条记录,如果删除第80条记录,那么第81条记录的逻辑记录号会自动变成80,以此类推,第100条记录逻辑记录号会变成99。固定的逻辑记录号则无论数据库如何操作都不会有变化。Queue模式下,逻辑记录号只能是固定方式。Recno模式则可通过配置来选择是采用那种类型的记录号作为键值。Btree模式也可以通过设置,将可变的逻辑记录号做为键值。
这几种存储模式各有优缺点,要根据具体的需求来选择。当键值不想用逻辑记录号时Btree或Hash是必须的选择。Btree方式比较适合连续的顺序读取,比如,当键值是时间值,如果经常有从某一时间点开始连续读取后继的记录的操作,Btree是一种很好的选择。对随机的跳跃式读取,Hash模式则更为恰当。Queue和Recno都以记录号为键值,但前者适合先进先出的读取方式。Recno则通常是存取变长文本记录的理想存储模式。
5. Berkeley DB Environment的概念
Berkeley DB Environment为一组数据库同时提供参数设置。更为重要的是,如果要应用更高级的特性,必须要使用Environment功能,比如在想要对保存的数据进行加密存储时。
6. 更多特点
除了最基本的插入、查询、删除功能以外,Berkeley DB还提供了一些特性,比如Transaction,数据加密,同步加锁控制,错误日志等功能。下面的图片是Berkeley DB功能示意图。
二. SQLite
1. 简介
相信PHP的开发人员一定不会对SQLite感到陌生,因为在PHP5中已经集成了这个轻巧的内嵌式数据库产品。SQLite与Berkeley DB相比,在操作语句上更类似关系型数据库的产品。绝大多数标准的SQL92语句SQLite都能支持。
SQLite的版权允许无任何限制的应用,包括商业性的产品。在参考链接二上提供的SQLite官方主站上可以下载到编译后的SQLite程序。但推荐应用CVS工具下载最新版本的SQLite源代码。如果在*nux平台下,可直接用make编译。如果在Windows平台,常用的有两种方法,一是应用在Windows平台下的Linux仿真程序,如MingW或Cygwin提供的make来编译。二是应用MS VC平台编译。后者设置略有麻烦,但可仿照参考链接五上提供的MS VC6工程文件的样例。应用到这个样例的时候,要注意的是由于SQLite源代码在不断更新,如果直接应用样例所提供的VC6工程文件编译会出现一些问题,读者要根据具体的情况稍微调整一下编译的设置。
2. 编译第三方Java接口
SQLite源代码是C,而且官方网站上只提供了C和Tcl语言的接口。为了应用Java接口,要采用第三方的接口驱程,可在参考链接三中找到这个Java接口程序。这个接口提供了两种连接SQLite的方式:一是直接用JNI技术调用SQLite的C语言接口,这种方式要求开发人员要对 SQLite本身的API也有一定的了解。在第二种方式中,接口程序实现了Java标准规范的JDBC接口,这样开发人员只要对JDBC有了解就可以了。
下面介绍在Windows系统MS VC6环境中编译SQLite Java接口(同时包括JNI和JDBC两个接口)的过程。如果你对C语言编译的设置很熟悉,可以跳这这段介绍。
第一步先把SQLite源代码编译成Lib静态库文件。具体的步骤可以直接应用下面参考链接中提供的MS VC6工作区文件,其中有一个编译SQLite到静态库的设置。编译成功后得到SQLite.lib文件。第二步要建立一个新的VC DLL项目,然后和上面介绍的Berkeley DB在编译Java本地化接口的设置一样,在VC的Tools菜单Options选项中指定JNI.H等JNI编译所要的头文件位置。同时还要指定 sqlite.h头文件位置,这个文件是在生成SQLite静态库的时候自动生成的,可以在SQLite.lib文件所在的工作区目录下面找到它,例如加入的路径为C:sqlitemsvc6。然后在Project菜单的setting选项设置Link到SQLite.lib库文件,并再次在 Tools菜单中Options指定SQLite.lib的查找路径。注意有些情况下可能要设置予编译选项HAVE_SQLITE_COMPILE以便使用SQLite中VM的一些功能。编译成功后可得到Sqlite_jni.dll文件。
第三方接口库中的Java代码包含JNI接口和多个版本的JDBC接口程序,可根据你的JRE的版本选择相应的JDBC程序。编译这些Java代码的过程这里就不做叙述了。
编译后的Java类包加上前面得到的Sqlite_jni.dll文件,组成了SQLite的Java接口库,在应用Java语言调用JDBC或JNI接口时,都是通过应用Java的本地化技术调用Sqlite_jni.dll文件,完成对SQLite数据库的操作。
3. 应用JNI直接调用SQLite功能
下面这段代码演示如何应用JNI接口操作SQLite。可以看到Database类的exec()方法是执行SQL语句的关键:
Database db = new Database();
try {
//打开数据库
db.open("c:tempmydata.slt", 0666);
db.interrupt();
db.busy_timeout(1000);
db.busy_handler(null);
db.exec("create table account (name varchar(10),gale boolean)"
,result);
db.exec("insert into account values('steve','m')", result);
db.exec("select * from account",result);
//关闭数据库
db.close();
} catch (Exception e) {
e.printStackTrace();
}
4. 应用JDBC连接SQLite
用"SQLite.JDBCDriver"作为JDBC的驱动程序类名。连接JDBC的URL格式为jdbc:sqlite:/path。这里的path为指定到SQLite数据库文件的路径,例如:
jdbc:sqlite://dirA/dirB/dbfile
jdbc:sqlite://DRIVE:/dirA/dirB/dbfile
jdbc:sqlite://COMPUTERNAME/shareA/dirB/dbfile
参考下面的应用JDBC连接SQLite的例程:
//声明JDBC驱动程序
Class clz = Class.forName("SQLite.JDBCDriver");
//连接数据库
Connection conn = DriverManager.getConnection("jdbc:sqlite:/c:/temp/e2.db");
Statement stmt = conn.createStatement();
//生成person表,包含名子和年龄字段
stmt.execute("create table person (name varchar(100),age int)");
//插入数据
stmt.execute("insert into person values('steve',25)");
//用SQL语句读出数据
result = stmt.executeQuery("select * from person");
while(result.next()){
System.out.println(result.getString(1));
System.out.println(result.getInt(2));
}
运行程序时要在Java.exe命令行加入选项java.library.path指定到Sqlite_jni.dll所在的路径。例如,如果 Sqlite_jni.dll放在c:sqliteNative 路径下面,运行类com.e2one.MyClass 的命令行将会是这样:java -Djava.library.path=c:sqliteNative com.e2one.MyClass。
5. SQLite的特点
SQLite是无数据类型的数据库。虽然在生成表结构的时候,要声明每个域的数据类型,但SQLite并不做任何检查。开发人员要靠自己的程序控制输入与读出数据的类型是正确的。这里有一个例外,就是当主键为整型值时,如果要插入一个非整型值时会产生异常。另外,虽然在插入或读出数据的时候是不区分类型的,但在比较的时候,不同数据类型是有区别的。比如:
CREATE TABLE MyTable(a INTEGER, b TEXT);
INSERT INTO MyTable VALUES(0,0);
当执行下面的查询:
SELECT count(*) FROM MyTable WHERE a=='00';
会返回一条记录,因为字段a的类型是整型,而数字00与0是相等的。
而执行下面的查询则不会返回记录:
SELECT count(*) FROM MyTable WHERE b=='00';
因为字段b是字符类型,字符"00"与"0"是不相等的。
SQLite提供了对Transaction的支持。应用Transaction即保证了数据的完整性,也会提高运行速度,因为多条语句一起提交给数据库的速度会比一条一条的提交方式更快。
对二进制数据,SQLite不能直接保存。但可以先将二进制的数据转换成ASCII编码,然后再保存。Base64编码机制是最常见的把二进制数据转换成ASCII编码的手段。在SQLite的C语言代码encode.c中提供了Base64编码的功能。对Java而言,在参考链接六中提供的 Apache的XML RPC项目中可以找到一个Base64编码的例子。
上面介绍了两个比较常见的嵌入式数据库,Berkeley DB速度极快,可靠性高,但学习起来有一定难度。SQLite则简单易用,速度也很快,又可以应用标准的JDBC连接,但它功能却照Berkeley略有逊色,比如加密功能、二进制数据的处理等。
参考资料
http://www.sleepycat.com ,Berkeley DB的官方主页。
http://www.sqlite.org , SQLite的官方主页。
http://www.ch-werner.de/javasqlite , 提供SQLite JNI接口程序和一个JDBC接口。
http://www.iturls.com/TechHotspot/TH_embeddedDB.asp 提供了一个很详细的嵌入式数据库产品及一些相关介绍。
http://jrepp.com/src.shtml Jake Repp的个人网站,上面提供了一个在MS VC6下编译SQLite的工程文件。
http://ws.apache.org/xmlrpc/ 可以在这个工程上找到将二进制数据用Base64编码方式转换成ASCII编码。
SQLITE3 在VC/MFC 中使用的一点体会
SQLITE简介:
This is an extension for the SQLite Embeddable SQL Database Engine. SQLite is a C library that implements an embeddable SQL database engine. Programs that link with the SQLite library can have SQL database access without running a separate RDBMS process.
SQLite is not a client library used to connect to a big database server. SQLite is the server. The SQLite library reads and writes directly to and from the database files on disk.
在 vc工程目录下 设置 Link L/对象类模块 为 sqlite3.lib ,c/c++ 分类 precompiled header 选择不使用预补偿页眉
首先将SQLITE3的七个文件放在和vc工程文件同一目录下,在工程中加入 CppSQLite3DB.cpp和CppSQLite3DB.h文件
# include "CppSQLite3.h"
extern CppSQLite3DB db;/////数据库对象
remove("c:test.db");
db.open("c:test.db");///打开数据库文件
建立表格:
db.execDML("create table Customer(CustomerName char(50), RoomNumber int, CustomerId int, ComeTime int,Money int);");
db.execDML("create table Room(RoomNumber int, RoomPrice int, RoomState char[20]);");
db.execDML("create table Manager(ManagerName char[20],PassWord int);");
插入数据:
string szCmd;
string szName = m_data1;
szCmd = "insert into Customer values(" ;
szCmd +="'";
szCmd +=szName;
szCmd +="'";
szCmd +=",";
sprintf(a,"%d",m_data2);
szCmd += a;
szCmd += ",";
sprintf(b,"%d",m_data3);
szCmd +=b;
szCmd +=",";
sprintf(c,"%d",m_data4);
szCmd += c;
szCmd += ",";
sprintf(d,"%d",m_data5);
szCmd += d;
szCmd += ");";
db.execDML(szCmd.c_str());/////////////插入SQL语句
MessageBox("提交成功,请继续!");
更新数据:
char k[20];//提交后更新房间信息
string szCkd;
szCkd="update Room set RoomState = 'notnull' where RoomNumber=";
sprintf(k,"%d",m_data2);
szCkd +=k;
szCkd +=";";
db.execDML(szCkd.c_str()); //////////插入SQL语句
查询数据:
CppSQLite3Query q = db.execQuery("select * from Customer;");
while(!q.eof()){
UpdateData(true);
if(m_data1==q.getIntField(1))
{
s.m_data1=q.getStringField(0);
s.m_data2=q.getIntField(1);
s.m_data3=q.getIntField(2);
s.m_data4=q.getFloatField(3);
s.m_data5=q.getIntField(4);
s.DoModal();
UpdateData(false);
break;}
q.nextRow();
if(q.eof()==true)
MessageBox("本旅馆无该房间!");
}
删除数据:
char a[20];
sprintf(a,"%d",m_data2);
string szCmd;
szCmd="delete from Room where RoomNumber =" ;
szCmd+=a;
szCmd+=";";
int b=0;
b=db.execDML(szCmd.c_str());
if(b==0)
MessageBox("操作错误,没有该房间信息");
else
MessageBox("删除房间信息成功");
在 vc工程目录下 设置 Link L/对象类模块 为 sqlite3.lib ,c/c++ 分类 precompiled header 选择不使用预补偿页眉
首先将SQLITE3的七个文件放在和vc工程文件同一目录下,在工程中加入 CppSQLite3DB.cpp和CppSQLite3DB.h文件
# include "CppSQLite3.h"
extern CppSQLite3DB db;/////数据库对象
remove("c:test.db");
db.open("c:test.db");///打开数据库文件
建立表格:
db.execDML("create table Customer(CustomerName char(50), RoomNumber int, CustomerId int, ComeTime int,Money int);");
db.execDML("create table Room(RoomNumber int, RoomPrice int, RoomState char[20]);");
db.execDML("create table Manager(ManagerName char[20],PassWord int);");
插入数据:
string szCmd;
string szName = m_data1;
szCmd = "insert into Customer values(" ;
szCmd +="'";
szCmd +=szName;
szCmd +="'";
szCmd +=",";
sprintf(a,"%d",m_data2);
szCmd += a;
szCmd += ",";
sprintf(b,"%d",m_data3);
szCmd +=b;
szCmd +=",";
sprintf(c,"%d",m_data4);
szCmd += c;
szCmd += ",";
sprintf(d,"%d",m_data5);
szCmd += d;
szCmd += ");";
db.execDML(szCmd.c_str());/////////////插入SQL语句
MessageBox("提交成功,请继续!");
更新数据:
char k[20];//提交后更新房间信息
string szCkd;
szCkd="update Room set RoomState = 'notnull' where RoomNumber=";
sprintf(k,"%d",m_data2);
szCkd +=k;
szCkd +=";";
db.execDML(szCkd.c_str()); //////////插入SQL语句
查询数据:
CppSQLite3Query q = db.execQuery("select * from Customer;");
while(!q.eof()){
UpdateData(true);
if(m_data1==q.getIntField(1))
{
s.m_data1=q.getStringField(0);
s.m_data2=q.getIntField(1);
s.m_data3=q.getIntField(2);
s.m_data4=q.getFloatField(3);
s.m_data5=q.getIntField(4);
s.DoModal();
UpdateData(false);
break;}
q.nextRow();
if(q.eof()==true)
MessageBox("本旅馆无该房间!");
}
删除数据:
char a[20];
sprintf(a,"%d",m_data2);
string szCmd;
szCmd="delete from Room where RoomNumber =" ;
szCmd+=a;
szCmd+=";";
int b=0;
b=db.execDML(szCmd.c_str());
if(b==0)
MessageBox("操作错误,没有该房间信息");
else
MessageBox("删除房间信息成功");
在 vc工程目录下 设置 Link L/对象类模块 为 sqlite3.lib , c/c++ 分类 precompiled header 选择不使用预补偿页眉
首先将SQLITE3的七个文件放在和vc工程文件同一目录下,在工程中加入 CppSQLite3DB.cpp和CppSQLite3DB.h文件
extern CppSQLite3DB db;/////数据库对象
remove("c:test.db");
db.open("c:test.db");///打开数据库文件
建立表格:
db.execDML("create table Customer(CustomerName char(50), RoomNumber int, CustomerId int, ComeTime int,Money int);");
db.execDML("create table Room(RoomNumber int, RoomPrice int, RoomState char[20]);");
db.execDML("create table Manager(ManagerName char[20],PassWord int);");
插入数据:
string szCmd;
string szName = m_data1;
szCmd = "insert into Customer values(" ;
szCmd +="'";
szCmd +=szName;
szCmd +="'";
szCmd +=",";
sprintf(a,"%d",m_data2);
szCmd += a;
szCmd += ",";
sprintf(b,"%d",m_data3);
szCmd +=b;
szCmd +=",";
sprintf(c,"%d",m_data4);
szCmd += c;
szCmd += ",";
sprintf(d,"%d",m_data5);
szCmd += d;
szCmd += ");";
db.execDML(szCmd.c_str());/////////////插入SQL语句
MessageBox("提交成功,请继续!");
更新数据:
char k[20];//提交后更新房间信息
string szCkd;
szCkd="update Room set RoomState = 'notnull' where RoomNumber=";
sprintf(k,"%d",m_data2);
szCkd +=k;
szCkd +=";";
db.execDML(szCkd.c_str()); //////////插入SQL语句
查询数据:
CppSQLite3Query q = db.execQuery("select * from Customer;");
while(!q.eof()){
UpdateData(true);
if(m_data1==q.getIntField(1))
{
s.m_data1=q.getStringField(0);
s.m_data2=q.getIntField(1);
s.m_data3=q.getIntField(2);
s.m_data4=q.getFloatField(3);
s.m_data5=q.getIntField(4);
s.DoModal();
UpdateData(false);
break;}
q.nextRow();
if(q.eof()==true)
MessageBox("本旅馆无该房间!");
}
删除数据:
char a[20];
sprintf(a,"%d",m_data2);
string szCmd;
szCmd="delete from Room where RoomNumber =" ;
szCmd+=a;
szCmd+=";";
int b=0;
b=db.execDML(szCmd.c_str());
if(b==0)
MessageBox("操作错误,没有该房间信息");
else
MessageBox("删除房间信息成功");
C/C++中调用SQLITE3的基本步骤
Sqlite是一个面向嵌入式系统的数据库,编译完成只有200K,同时支持2T的数据记录。对于嵌入式设备是一个很好的数据库引擎。本文通过一个小例子说明如何在C与C++调用Sqlite API完成数据库的创建、插入数据与查询数据。本文的开发环境为(Redhat9.0 + Qtopia2.1.2 + Sqlite3)
安装Sqlite3:
从www.sqlite.org上下载Sqlite3.2.2运源代码,依照Readme中的步骤:
tar xzf sqlite3.2.2.tar.gz
mkdir bld
cd bld
../sqlite3.2.2/configure
make
make install
然后在shell下运行 sqlite3 test.db命令可以检验是否已经安装成功。
创建数据库:
sqlite3 *pDB = NULL;
char * errMsg = NULL;
//打开一个数据库,如果改数据库不存在,则创建一个名字为databaseName的数据库文件
int rc = sqlite3_open(databaseName, &pDB);
if(rc)
{
cout << " Open the database " << databaseName << " failed" << endl;
}
//如果创建成功,atECt2g*网c;添加表
else
{
cout << "create the database successful!" << endl;
//creat the table
int i;
for(i=1; i<nTableNum; i++)
{
}
//插入一个表,
;GhlNvn络@[N教P?网
返回值为SQLITE_OK为成功,否则输出出错信息
//函数参数:第一个为操作数据库的指针,第二句为SQL命令字符串
//第三个参数为callback函数,这里没有用,第四个参数为callback函数
//中的第一个参数,第五个为出错信息
rc = sqlite3_exec(pDB, "CREATE TABLE chn_to_eng(chinese QString, english QString)", 0, 0, &errMsg);
if(rc == SQLITE_OK)
cout << "create the chn_to_eng table successful!" << endl;
else
cout << errMsg << endl;
//同上,插入另一个表
rc = sqlite3_exec(pDB, "CREATE TABLE eng_to_chn(english QString, chinese QString)", 0, 0, &errMsg);
if(rc == SQLITE_OK)
cout << "create the eng_to_chn table successful!" << endl;
else
cout << errMsg << endl;
}
、、、、、、
//往表中添加数据
char chn[]="...";
char eng[]="...";
char value[500];
//定义一条参数SQL命令,其中chn,eng为需要插入的数据
sprintf(value, "INSERT INTO chn_to_eng(chinese, english) VALUES(''''%s'''', ''''%s'''')", chn, eng);
//use the SQLITE C/C++ API to create and adjust a database.
rc = sqlite3_exec(pDB,
value,
0, 0, &errMsg);
//查询一条记录
char value[500];
//定义一条查询语句,其中条件为当english为target时的中文记录
//print_result_cb为callback函数,在其中可以得到查询的结果,具体见下文
sprintf(value, "SELECT chinese FROM eng_to_chn where english=''''%s'''' ", target);
rc = sqlite3_exec(pDB,
value,
print_result_cb, 0, &errMsg);
if(rc == SQLITE_OK)
{
// #ifdef_debug
cout << "select the record successful!" << endl;
// #endif
}
else
{
// #ifdef_debug
cout << errMsg << endl;
// #endif
return false;
}
.......
}
//callback回调函数print_result_cb的编写,
理EG网`cc_@育:业提Ev
其中data为sqlite3_exec中的第四个参数,第二个参数是栏的数目,第三个是栏的名字,第四个为查询得到的值得。这两个函数输出所有查询到的结果
int print_result_cb(void* data, int n_columns, char** column_values,
char** column_names)
{
static int column_names_printed = 0;
int i;
if (!column_names_printed) {
print_row(n_columns, column_names);
column_names_printed = 1;
}
print_row(n_columns, column_values);
return 0;
}
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
char value[500];
//定义一条参数SQL命令,其中chn,eng为需要插入的数据
sprintf(value, "INSERT INTO chn_to_eng(chinese, english) VALUES(''''%s'''', ''''%s'''')", chn, eng);
//use the SQLITE C/C++ API to create and adjust a database.
rc = sqlite3_exec(pDB,
value,
0, 0, &errMsg);
//查询一条记录
char value[500];
//定义一条查询语句,
育11o]wdeZ国|_[zL中
其中条件为当english为target时的中文记录
//print_result_cb为callback函数,在其中可以得到查询的结果,具体见下文
sprintf(value, "SELECT chinese FROM eng_to_chn where english=''''%s'''' ", target);
rc = sqlite3_exec(pDB,
value,
print_result_cb, 0, &errMsg);
if(rc == SQLITE_OK)
{
// #ifdef_debug
cout << "select the record successful!" << endl;
// #endif
}
else
{
// #ifdef_debug
cout << errMsg << endl;
// #endif
return false;
}
.......
}
//callback回调函数print_result_cb的编写,
xbSe}育1(8R国%.h
其中data为sqlite3_exec中的第四个参数,第二个参数是栏的数目,第三个是栏的名字,第四个为查询得到的值得。这两个函数输出所有查询到的结果
int print_result_cb(void* data, int n_columns, char** column_values,
char** column_names)
{
static int column_names_printed = 0;
int i;
if (!column_names_printed) {
print_row(n_columns, column_names);
column_names_printed = 1;
}
print_row(n_columns, column_values);
return 0;
}
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
Sqlite是一个面向嵌入式系统的数据库,编译完成只有200K,同时支持2T的数据记录。对于嵌入式设备是一个很好的数据库引擎。本文通过一个小例子说明如何在C与C++调用Sqlite API完成数据库的创建、插入数据与查询数据。本文的开发环境为(Redhat9.0 + Qtopia2.1.2 + Sqlite3)
安装Sqlite3:
从www.sqlite.org上下载Sqlite3.2.2运源代码,依照Readme中的步骤:
tar xzf sqlite3.2.2.tar.gz
mkdir bld
cd bld
../sqlite3.2.2/configure
make
make install
然后在shell下运行 sqlite3 test.db命令可以检验是否已经安装成功。
创建数据库:
sqlite3 *pDB = NULL;
char * errMsg = NULL;
//打开一个数据库,如果改数据库不存在,则创建一个名字为databaseName的数据库文件
int rc = sqlite3_open(databaseName, &pDB);
if(rc)
{
cout << " Open the database " << databaseName << " failed" << endl;
}
//如果创建成功,添加表
else
{
cout << "create the database successful!" << endl;
//creat the table
int i;
for(i=1; i<nTableNum; i++)
{
}
//插入一个表,返回值为SQLITE_OK为成功,否则输出出错信息
//函数参数:第一个为操作数据库的指针,第二句为SQL命令字符串
//第三个参数为callback函数,这里没有用,第四个参数为callback函数
//中的第一个参数,第五个为出错信息
rc = sqlite3_exec(pDB, "CREATE TABLE chn_to_eng(chinese QString, english QString)", 0, 0, &errMsg);
if(rc == SQLITE_OK)
cout << "create the chn_to_eng table successful!" << endl;
else
cout << errMsg << endl;
//同上,插入另一个表
rc = sqlite3_exec(pDB, "CREATE TABLE eng_to_chn(english QString, chinese QString)", 0, 0, &errMsg);
if(rc == SQLITE_OK)
cout << "create the eng_to_chn table successful!" << endl;
else
cout << errMsg << endl;
}
、、、、、、
//往表中添加数据
char chn[]="...";
char eng[]="...";
char value[500];
//定义一条参数SQL命令,其中chn,eng为需要插入的数据
sprintf(value, "INSERT INTO chn_to_eng(chinese, english) VALUES('%s', '%s')", chn, eng);
//use the SQLITE C/C++ API to create and adjust a database.
rc = sqlite3_exec(pDB,
value,
0, 0, &errMsg);
//查询一条记录
char value[500];
//定义一条查询语句,其中条件为当english为target时的中文记录
//print_result_cb为callback函数,在其中可以得到查询的结果,具体见下文
sprintf(value, "SELECT chinese FROM eng_to_chn where english='%s' ", target);
rc = sqlite3_exec(pDB,
value,
print_result_cb, 0, &errMsg);
if(rc == SQLITE_OK)
{
// #ifdef_debug
cout << "select the record successful!" << endl;
// #endif
}
else
{
// #ifdef_debug
cout << errMsg << endl;
// #endif
return false;
}
.......
}
//callback回调函数print_result_cb的编写,其中data为sqlite3_exec中的第四个参数,第二个参数是栏的数目,第三个是栏的名字,第四个为查询得到的值得。这两个函数输出所有查询到的结果
int print_result_cb(void* data, int n_columns, char** column_values,
char** column_names)
{
static int column_names_printed = 0;
int i;
if (!column_names_printed) {
print_row(n_columns, column_names);
column_names_printed = 1;
}
print_row(n_columns, column_values);
return 0;
}
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
char value[500];
//定义一条参数SQL命令,其中chn,eng为需要插入的数据
sprintf(value, "INSERT INTO chn_to_eng(chinese, english) VALUES('%s', '%s')", chn, eng);
//use the SQLITE C/C++ API to create and adjust a database.
rc = sqlite3_exec(pDB,
value,
0, 0, &errMsg);
//查询一条记录
char value[500];
//定义一条查询语句,其中条件为当english为target时的中文记录
//print_result_cb为callback函数,在其中可以得到查询的结果,具体见下文
sprintf(value, "SELECT chinese FROM eng_to_chn where english='%s' ", target);
rc = sqlite3_exec(pDB,
value,
print_result_cb, 0, &errMsg);
if(rc == SQLITE_OK)
{
// #ifdef_debug
cout << "select the record successful!" << endl;
// #endif
}
else
{
// #ifdef_debug
cout << errMsg << endl;
// #endif
return false;
}
.......
}
//callback回调函数print_result_cb的编写,其中data为sqlite3_exec中的第四个参数,第二个参数是栏的数目,第三个是栏的名字,第四个为查询得到的值得。这两个函数输出所有查询到的结果
int print_result_cb(void* data, int n_columns, char** column_values,
char** column_names)
{
static int column_names_printed = 0;
int i;
if (!column_names_printed) {
print_row(n_columns, column_names);
column_names_printed = 1;
}
print_row(n_columns, column_values);
return 0;
}
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
char value[500];
//定义一条参数SQL命令,其中chn,eng为需要插入的数据
sprintf(value, "INSERT INTO chn_to_eng(chinese, english) VALUES('%s', '%s')", chn, eng);
//use the SQLITE C/C++ API to create and adjust a database.
rc = sqlite3_exec(pDB,
value,
0, 0, &errMsg);
//查询一条记录
char value[500];
//定义一条查询语句,其中条件为当english为target时的中文记录
//print_result_cb为callback函数,在其中可以得到查询的结果,具体见下文
sprintf(value, "SELECT chinese FROM eng_to_chn where english='%s' ", target);
rc = sqlite3_exec(pDB,
value,
print_result_cb, 0, &errMsg);
if(rc == SQLITE_OK)
{
// #ifdef_debug
cout << "select the record successful!" << endl;
// #endif
}
else
{
// #ifdef_debug
cout << errMsg << endl;
// #endif
return false;
}
.......
}
//callback回调函数print_result_cb的编写,其中data为sqlite3_exec中的第四个参数,第二个参数是栏的数目,第三个是栏的名字,第四个为查询得到的值得。这两个函数输出所有查询到的结果
int print_result_cb(void* data, int n_columns, char** column_values,
char** column_names)
{
static int column_names_printed = 0;
int i;
if (!column_names_printed) {
print_row(n_columns, column_names);
column_names_printed = 1;
}
print_row(n_columns, column_values);
return 0;
}
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
void print_row(int n_values, char** values)
{
int i;
for (i = 0; i < n_values; ++i) {
if (i > 0) {
printf("t");
}
printf("%s", values[i]);
}
printf("n");
}
大致过程就是如此,具体可以参考SQLITE的API函数说明,见www.sqlite.org
0. 引言
我们这篇文章主要讲述了如何在C/C++语言中调用 sqlite 的函数接口来实现对数据库的管理,
包括创建数据库、创建表格、插入数据、查询数据、删除数据等。
1. 说明
这里我们假设你已经编译好了sqlite的库文件 :
libsqlite3.a libsqlite3.la libsqlite3.so libsqlite3.so.0 libsqlite3.so.0.8.6 pkgconfig
和可执行文件 : sqlite3
我们再假设你的sqlite3的安装目录在 /usr/local/sqlite3 目录下。
如果不是,我们可以这样做,将你的安装文件复制到 /usr/local/sqlite3 这个目录,
这样我们好在下面的操作中更加统一,从而减少出错的概率
例如:[root@localhost home]# cp -rf sqlite-3.3.8-ix86/ /usr/local/sqlite3
这里假设 /home/sqlite-3.3.8-ix86/ 是你的安装目录,也就是说你的sqlite原来就是安装在这里
这样之后,我们的sqlite3的库文件目录是:/usr/local/sqlite3/lib
可执行文件 sqlite3 的目录是: /usr/local/sqlite3/bin
头文件 sqlite3.h 的目录是: /usr/local/sqlite3/include
可以用ls命令查看下:
[root@localhost sqlite]# ls /usr/local/sqlite3/lib
libsqlite3.a libsqlite3.la libsqlite3.so libsqlite3.so.0 libsqlite3.so.0.8.6 pkgconfig
好拉,现在开始我们的Linux下sqlite3编程之旅。
2. 开始
这里我们现在进行一个测试。
现在我们来写个C/C++程序,调用 sqlite 的 API 接口函数。
下面是一个C程序的例子,显示怎么使用 sqlite 的 C/C++ 接口. 数据库的名字由第一个参数取得且第二个参数或更多的参数是 SQL 执行语句. 这个函数调用sqlite3_open() 在 16 行打开数据库,并且sqlite3_close() 在 25 行关闭数据库连接。
[root@localhost temp]# vi opendbsqlite.c
按下 i 键切换到输入模式,输入下列代码:
// name: opendbsqlite.c
// This prog is used to test C/C++ API for sqlite3.It is very simple,ha!
// Author : zieckey All rights reserved.
// date : 2006/11/13
#include <stdio.h>
#include <sqlite3.h>
int main( void )
{
sqlite3 *db=NULL;
char *zErrMsg = 0;
int rc;
//打开指定的数据库文件,如果不存在将创建一个同名的数据库文件
rc = sqlite3_open("zieckey.db", &db);
if( rc )
{
fprintf(stderr, "Can't open database: %sn", sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
else printf("You have opened a sqlite3 database named zieckey.db successfully!nCongratulations! Have fun ! ^-^ n");
sqlite3_close(db); //关闭数据库
return 0;
}
退出,保存。(代码输入完成后,按下 Esc 键,然后输入: :wq ,回车就好拉)
好拉,现在编译:[root@localhost temp]# gcc opendbsqlite.c -o db.out
或者遇到这样的问题:
[root@localhost temp]# gcc opendbsqlite.c -o db.out
opendbsqlite.c:11:21: sqlite3.h: 没有那个文件或目录
opendbsqlite.c: In function `main':
opendbsqlite.c:19: `sqlite3' undeclared (first use in this function)
opendbsqlite.c:19: (Each undeclared identifier is reported only once
opendbsqlite.c:19: for each function it appears in.)
opendbsqlite.c:19: `db' undeclared (first use in this function)
这是由于没有找到头文件的原因。
也许会碰到类似这样的问题:
[root@localhost temp]# gcc opendbsqlite.c -o db.out
/tmp/ccTkItnN.o(.text+0x2b): In function `main':
: undefined reference to `sqlite3_open'
/tmp/ccTkItnN.o(.text+0x45): In function `main':
: undefined reference to `sqlite3_errmsg'
/tmp/ccTkItnN.o(.text+0x67): In function `main':
: undefined reference to `sqlite3_close'
/tmp/ccTkItnN.o(.text+0x8f): In function `main':
: undefined reference to `sqlite3_close'
collect2: ld returned 1 exit status
这是个没有找到库文件的问题。
下面我们着手解决这些问题。
由于用到了用户自己的库文件,所用应该指明所用到的库,我们可以这样编译:
[root@localhost temp]# gcc opendbsqlite.c -o db.out -lsqlite3
我用用 -lsqlite3 选项就可以了(前面我们生成的库文件是 libsqlite3.so.0.8.6 等,
去掉前面的lib和后面的版本标志,就剩下 sqlite3 了所以是 -lsqlite3 )。
如果我们在编译安装的时候,选择了安装路径,例如这样的话:
.......
# ../sqlite/configure --prefix=/usr/local/sqlite3
# make
.......
这样编译安装时,sqlite的库文件将会生成在 /usr/local/sqlite3/lib 目录下
sqlite的头文件将会生成在 /usr/local/sqlite3/include 目录下
这时编译还要指定库文件路径,因为系统默认的路径没有包含 /usr/local/sqlite3/lib
[root@localhost temp]# gcc opendbsqlite.c -o db.out -lsqlite3 -L/usr/local/sqlite3/lib
如果还不行的话,可能还需要指定头文件 sqlite3.h 的路径,如下:
[root@localhost temp]# gcc opendbsqlite.c -o db.out -lsqlite3 -L/usr/local/sqlite3/lib -I/usr/local/sqlite3/include
这样编译应该就可以了 ,运行:
[root@localhost temp]# ./db.out
./db.out: error while loading shared libraries: libsqlite3.so.0: cannot open shared object file: No such file or directory
运行是也许会出现类似上面的错误。
这个问题因为刚刚编译的时候没有选择静态编译,那么按照默认的编译就动态编译的。
动态编译后,由于可执行文件在运行时要调用系统库文件,
那么沿着系统默认的库文件搜索路径搜索,就可能找不到我们现在所需的库文件。
致使出现 "error while loading shared libraries" 等错误。
我们可以这样解决:
方法一:静态编译
在编译时加上 -static 参数,例如
[root@localhost temp]# gcc opendbsqlite.c -o db.out -lsqlite3 -L/usr/local/sqlite3/lib -I/usr/local/sqlite3/include -static
[root@localhost temp]# ll
总用量 1584
-rwxr-xr-x 1 root root 1596988 11月 13 10:50 db.out
-rw-r--r-- 1 root root 614 11月 13 10:31 opendbsqlite.c
可以看到输出文件 db.out ,其大小为: 1596988k
运行,好了,没有出现错误
[root@localhost temp]# ./db.out
You have opened a sqlite3 database named zieckey.db successfully!
Congratulations! Have fun ! ^-^
方法二:重新配置系统环境变量 LD_LIBRARY_PATH
这时需要指定 libsqlite3.so.0 库文件的路径,也就是配置系统环境变量 LD_LIBRARY_PATH ,
使系统能够找到 libsqlite3.so.0 。
去掉 -static ,再编译:
[root@localhost temp]# gcc opendbsqlite.c -o db.out -lsqlite3 -L/usr/local/sqlite3/lib -I/usr/local/sqlite3/include
[root@localhost temp]# ll
总用量 36
-rwxr-xr-x 1 root root 12716 11月 13 10:56 db.out
-rw-r--r-- 1 root root 614 11月 13 10:31 opendbsqlite.c
[root@localhost temp]#
可以看到输出文件 db.out ,其大小为: 12716k,比刚才的静态编译要小得多。
所以我们推荐使用动态编译的方法。
好了,现在我们来指定系统环境变量 LD_LIBRARY_PATH 的值
在shell下输入:
[root@localhost temp]# export LD_LIBRARY_PATH=/usr/local/sqlite3/lib:$LD_LIBRARY_PATH
看看现在系统环境设置:
[root@localhost temp]#env
SSH_AGENT_PID=3511
HOSTNAME=localhost.localdomain
DESKTOP_STARTUP_ID=
SHELL=/bin/bash
TERM=xterm
HISTSIZE=1000
GTK_RC_FILES=/etc/gtk/gtkrc:/root/.gtkrc-1.2-gnome2
WINDOWID=29388238
OLDPWD=/mnt/usb/wuruan/sqlite
QTDIR=/usr/lib/qt-3.1
USER=root
LD_LIBRARY_PATH=/usr/local/sqlite3/lib:
LS_COLORS=no=00:fi=00:di=00;34:ln=00;36:pi=40;33:so=00;35:bd=40;33;01:cd=40;33;01:or=01;05;37;41:mi=01;05;37;41:ex=00;32:*.cmd=00;32:*.exe=00;32:*.com=00;32:*.btm=00;32:*.bat=00;32:*.sh=00;32:*.csh=00;32:*.tar=00;31:*.tgz=00;31:*.arj=00;31:*.taz=00;31:*.lzh=00;31:*.zip=00;31:*.z=00;31:*.Z=00;31:*.gz=00;31:*.bz2=00;31:*.bz=00;31:*.tz=00;31:*.rpm=00;31:*.cpio=00;31:*.jpg=00;35:*.gif=00;35:*.bmp=00;35:*.xbm=00;35:*.xpm=00;35:*.png=00;35:*.tif=00;35:
SSH_AUTH_SOCK=/tmp/ssh-XXt14q3a/agent.3456
SESSION_MANAGER=local/localhost.localdomain:/tmp/.ICE-unix/3456
USERNAME=root
MAIL=/var/spool/mail/root
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin
INPUTRC=/etc/inputrc
PWD=/mnt/usb/wuruan
XMODIFIERS=@im=fcitx
LANG=zh_CN.GB18030
GDMSESSION=Default
SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass
HOME=/root
SHLVL=2
LANGUAGE=zh_CN.GB18030:zh_CN.GB2312:zh_CN
GNOME_DESKTOP_SESSION_ID=Default
BASH_ENV=/root/.bashrc
LOGNAME=root
LESSOPEN=|/usr/bin/lesspipe.sh %s
DISPLAY=:0.0
G_BROKEN_FILENAMES=1
COLORTERM=gnome-terminal
XAUTHORITY=/root/.Xauthority
_=/bin/env
看到了吧,LD_LIBRARY_PATH这一行开始就是sqlite3的库文件路径:
LD_LIBRARY_PATH=/usr/local/sqlite3/lib: ............
好拉,现在再运行
[root@localhost temp]# ./db.out
You have opened a sqlite3 database named zieckey.db successfully!
Congratulations! Have fun ! ^-^
是不是很有成就感阿 ,呵呵,这个上手还是很快的。
3. 插入:insert
刚刚我们知道了怎么调用 sqlite3 的C/C++的API函数接口,下面我们看看怎么在C语言中向数据库插入数据。
好的,我们现编辑一段c代码,取名为 insert.c
// name: insert.c
// This prog is used to test C/C++ API for sqlite3 .It is very simple,ha !
// Author : zieckey All rights reserved.
// date : 2006/11/18
#include <stdio.h>
#include <stdlib.h>
#include "sqlite3.h"
#define _DEBUG_
int main( void )
{
sqlite3 *db=NULL;
char *zErrMsg = 0;
int rc;
rc = sqlite3_open("zieckey.db", &db); //打开指定的数据库文件,如果不存在将创建一个同名的数据库文件
if( rc )
{
fprintf(stderr, "Can't open database: %sn", sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
else printf("You have opened a sqlite3 database named zieckey.db successfully!nCongratulations! Have fun ! ^-^ n");
//创建一个表,如果该表存在,则不创建,并给出提示信息,存储在 zErrMsg 中
char *sql = " CREATE TABLE SensorData(
ID INTEGER PRIMARY KEY,
SensorID INTEGER,
SiteNum INTEGER,
Time VARCHAR(12),
SensorParameter REAL
);" ;
sqlite3_exec( db , sql , 0 , 0 , &zErrMsg );
#ifdef _DEBUG_
printf("%sn",zErrMsg);
#endif
//插入数据
sql = "INSERT INTO "SensorData" VALUES( NULL , 1 , 1 , '200605011206', 18.9 );" ;
sqlite3_exec( db , sql , 0 , 0 , &zErrMsg );
sql = "INSERT INTO "SensorData" VALUES( NULL , 1 , 1 , '200605011306', 16.4 );" ;
sqlite3_exec( db , sql , 0 , 0 , &zErrMsg );
sqlite3_close(db); //关闭数据库
return 0;
}
好的,将上述代码写入一个文件,并将其命名为 insert.c 。
解释:
sqlite3_exec的函数原型说明如下:
int sqlite3_exec(
sqlite3*, /* An open database */
const char *sql, /* SQL to be executed */
sqlite_callback, /* Callback function */
void *, /* 1st argument to callback function */
char **errmsg /* Error msg written here */
);
编译:
[root@localhost temp]# gcc insert.c -lsqlite3 -L/usr/local/sqlite3/lib -I/usr/local/sqlite3/include
insert.c:28:21: warning: multi-line string literals are deprecated
[root@localhost temp]#
执行
[root@localhost temp]# ./a.out
./a.out: error while loading shared libraries: libsqlite3.so.0: cannot open shared object file: No such file or directory
[root@localhost temp]#
同样的情况,如上文处理方法:
[root@localhost temp]# export LD_LIBRARY_PATH=/usr/local/sqlite3/lib:$LD_LIBRARY_PATH
[root@localhost temp]# ./a.out
You have opened a sqlite3 database named zieckey.db successfully!
Congratulations! Have fun ! ^-^
(null)
(null)
(null)
[root@localhost temp]#
运行成功了,好了,现在我们来看看是否插入了数据
[root@localhost temp]# /usr/local/sqlite3/bin/sqlite3 zieckey.db
SQLite version 3.3.8
Enter ".help" for instructions
sqlite> select * from SensorData;
1|1|1|200605011206|18.9
2|1|1|200605011306|16.4
sqlite>
哈哈,已经插入进去了,不是吗?
很简单是不?
4. 查询: SELETE
好了,我们知道了怎么调用 sqlite3 的C/C++的API函数接口去创建数据库、创建表格、并插入数据,
下面我们看看怎么在C语言中查询数据库中的数据。
好的,我们现编辑一段c代码,取名为 query.c
// name: query.c
// This prog is used to test C/C++ API for sqlite3 .It is very simple,ha !
// Author : zieckey All rights reserved.
// date : 2006/11/18
#include <stdio.h>
#include <stdlib.h>
#include "sqlite3.h"
#define _DEBUG_
int main( void )
{
sqlite3 *db=NULL;
char *zErrMsg = 0;
int rc;
rc = sqlite3_open("zieckey.db", &db); //打开指定的数据库文件,如果不存在将创建一个同名的数据库文件
if( rc )
{
fprintf(stderr, "Can't open database: %sn", sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
else printf("You have opened a sqlite3 database named zieckey.db successfully!nCongratulations! Have fun ! ^-^ n");
//创建一个表,如果该表存在,则不创建,并给出提示信息,存储在 zErrMsg 中
char *sql = " CREATE TABLE SensorData(
ID INTEGER PRIMARY KEY,
SensorID INTEGER,
SiteNum INTEGER,
Time VARCHAR(12),
SensorParameter REAL
);" ;
sqlite3_exec( db , sql , 0 , 0 , &zErrMsg );
#ifdef _DEBUG_
printf("zErrMsg = %s n", zErrMsg);
#endif
//插入数据
sql = "INSERT INTO "SensorData" VALUES(NULL , 1 , 1 , '200605011206', 18.9 );" ;
sqlite3_exec( db , sql , 0 , 0 , &zErrMsg );
sql = "INSERT INTO "SensorData" VALUES(NULL , 1 , 1 , '200605011306', 16.4 );" ;
sqlite3_exec( db , sql , 0 , 0 , &zErrMsg );
int nrow = 0, ncolumn = 0;
char **azResult; //二维数组存放结果
//查询数据
/*
int sqlite3_get_table(sqlite3*, const char *sql,char***result , int *nrow , int *ncolumn ,char **errmsg );
result中是以数组的形式存放你所查询的数据,首先是表名,再是数据。
nrow ,ncolumn分别为查询语句返回的结果集的行数,列数,没有查到结果时返回0
*/
sql = "SELECT * FROM SensorData ";
sqlite3_get_table( db , sql , &azResult , &nrow , &ncolumn , &zErrMsg );
int i = 0 ;
printf( "row:%d column=%d n" , nrow , ncolumn );
printf( "nThe result of querying is : n" );
for( i=0 ; i<( nrow + 1 ) * ncolumn ; i++ )
printf( "azResult[%d] = %sn", i , azResult[i] );
//释放掉 azResult 的内存空间
sqlite3_free_table( azResult );
#ifdef _DEBUG_
printf("zErrMsg = %s n", zErrMsg);
#endif
sqlite3_close(db); //关闭数据库
return 0;
}
我们这里用到了一个查询的语句是 "SELECT * FROM SensorData " ,
在C语言中对应的函数接口是 sqlite3_get_table( db , sql , &azResult , &nrow , &ncolumn , &zErrMsg );
这个函数接口的解释在程序中已经注释。
下面我们编译运行下看看,
[root@localhost temp]# export LD_LIBRARY_PATH=/usr/local/sqlite3/lib:$LD_LIBRARY_PATH
[root@localhost temp]# gcc query.c -lsqlite3 -L/usr/local/sqlite3/lib -I/usr/local/sqlite3/include
query.c:29:21: warning: multi-line string literals are deprecated
[root@localhost temp]# ./a.out
You have opened a sqlite3 database named zieckey.db successfully!
Congratulations! Have fun ! ^-^
zErrMsg = (null)
row:2 column=5
The result of querying is :
azResult[0] = ID
azResult[1] = SensorID
azResult[2] = SiteNum
azResult[3] = Time
azResult[4] = SensorParameter
azResult[5] = 1
azResult[6] = 1
azResult[7] = 1
azResult[8] = 200605011206
azResult[9] = 18.9
azResult[10] = 2
azResult[11] = 1
azResult[12] = 1
azResult[13] = 200605011306
azResult[14] = 16.4
zErrMsg = (null)
这里我们可以看到,azResult 的前面 5 个数据正好是我们的表 SensorData 的列属性,
之后才是我们要查询的数据。所以我们的程序中才有 i<( nrow + 1 ) * ncolumn 的判断条件:
for( i=0 ; i<( nrow + 1 ) * ncolumn ; i++ )
printf( "azResult[%d] = %sn", i , azResult[i] );
输出中有 zErrMsg = (null) 这样的字句,这是 zErrMsg 保留的错误信息,
正如你所看到的,zErrMsg 为空,表明在执行过程中没有错误信息。
5. 删除:delete
下面我们看看怎么在C语言中删除数据库中的特定的数据。
好的,我们现编辑一段c代码,取名为 delete.c
// name: delete.c
// This prog is used to test C/C++ API for sqlite3 .It is very simple,ha !
// Author : zieckey All rights reserved.
// date : 2006/11/18
#include <stdio.h>
#include <stdlib.h>
#include "sqlite3.h"
#define _DEBUG_
int main( void )
{
sqlite3 *db=NULL;
char *zErrMsg = 0;
int rc;
rc = sqlite3_open("zieckey.db", &db); //打开指定的数据库文件,如果不存在将创建一个同名的数据库文件
if( rc )
{
fprintf(stderr, "Can't open database: %sn", sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
else printf("You have opened a sqlite3 database named zieckey.db successfully!nCongratulations! Have fun ! ^-^ n");
//创建一个表,如果该表存在,则不创建,并给出提示信息,存储在 zErrMsg 中
char *sql = " CREATE TABLE SensorData(
ID INTEGER PRIMARY KEY,
SensorID INTEGER,
SiteNum INTEGER,
Time VARCHAR(12),
SensorParameter REAL
);" ;
sqlite3_exec( db , sql , 0 , 0 , &zErrMsg );
#ifdef _DEBUG_
printf("zErrMsg = %s n", zErrMsg);
#endif
//插入数据
sql = "INSERT INTO "SensorData" VALUES(NULL , 1 , 1 , '200605011206', 18.9 );" ;
sqlite3_exec( db , sql , 0 , 0 , &zErrMsg );
sql = "INSERT INTO "SensorData" VALUES(NULL , 23 , 45 , '200605011306', 16.4 );" ;
sqlite3_exec( db , sql , 0 , 0 , &zErrMsg );
sql = "INSERT INTO "SensorData" VALUES(NULL , 34 , 45 , '200605011306', 15.4 );" ;
sqlite3_exec( db , sql , 0 , 0 , &zErrMsg );
int nrow = 0, ncolumn = 0;
char **azResult; //二维数组存放结果
//查询数据
sql = "SELECT * FROM SensorData ";
sqlite3_get_table( db , sql , &azResult , &nrow , &ncolumn , &zErrMsg );
int i = 0 ;
printf( "row:%d column=%d n" , nrow , ncolumn );
printf( "nThe result of querying is : n" );
for( i=0 ; i<( nrow + 1 ) * ncolumn ; i++ )
printf( "azResult[%d] = %sn", i , azResult[i] );
//删除数据
sql = "DELETE FROM SensorData WHERE SensorID = 1 ;" ;
sqlite3_exec( db , sql , 0 , 0 , &zErrMsg );
#ifdef _DEBUG_
printf("zErrMsg = %s n", zErrMsg);
#endif
sql = "SELECT * FROM SensorData ";
sqlite3_get_table( db , sql , &azResult , &nrow , &ncolumn , &zErrMsg );
printf( "nnnnrow:%d column=%d " , nrow , ncolumn );
printf( "nAfter deleting , the result of querying is : n" );
for( i=0 ; i<( nrow + 1 ) * ncolumn ; i++ )
printf( "azResult[%d] = %sn", i , azResult[i] );
//释放掉 azResult 的内存空间
sqlite3_free_table( azResult );
#ifdef _DEBUG_
printf("zErrMsg = %s n", zErrMsg);
#endif
sqlite3_close(db); //关闭数据库
return 0;
}
下面我们编译运行看看,效果如何
[root@localhost temp]# export LD_LIBRARY_PATH=/usr/local/sqlite3/lib:$LD_LIBRARY_PATH
[root@localhost temp]# gcc delete.c -lsqlite3 -L/usr/local/sqlite3/lib -I/usr/local/sqlite3/include
delete.c:29:21: warning: multi-line string literals are deprecated
[root@localhost temp]# ./a.out
You have opened a sqlite3 database named zieckey.db successfully!
Congratulations! Have fun ! ^-^
zErrMsg = (null)
row:3 column=5
The result of querying is :
azResult[0] = ID
azResult[1] = SensorID
azResult[2] = SiteNum
azResult[3] = Time
azResult[4] = SensorParameter
azResult[5] = 1
azResult[6] = 1
azResult[7] = 1
azResult[8] = 200605011206
azResult[9] = 18.9
azResult[10] = 2
azResult[11] = 23
azResult[12] = 45
azResult[13] = 200605011306
azResult[14] = 16.4
azResult[15] = 3
azResult[16] = 34
azResult[17] = 45
azResult[18] = 200605011306
azResult[19] = 15.4
zErrMsg = (null)
row:2 column=5
After deleting , the result of querying is :
azResult[0] = ID
azResult[1] = SensorID
azResult[2] = SiteNum
azResult[3] = Time
azResult[4] = SensorParameter
azResult[5] = 2
azResult[6] = 23
azResult[7] = 45
azResult[8] = 200605011306
azResult[9] = 16.4
azResult[10] = 3
azResult[11] = 34
azResult[12] = 45
azResult[13] = 200605011306
azResult[14] = 15.4
zErrMsg = (null)
从程序输出结果就可以看出,在删除数据前,我们有三条记录,
删除数据后我们发现,数据库内记录少了。从而实现了我们的删除数据目的。
总结:
在这篇文章里,我们主要讲述了如何在C/C++语言中调用 sqlite 的函数接口来实现对数据库的管理,
包括创建数据库、创建表格、插入数据、查询数据、删除数据等。而这些操作似乎都很简单不是吗?
嵌入式Linux系统的GDB远程调试的实现
远程调试环境由宿主机GDB和目标机调试stub共同构成,两者通过串口或TCP连接。使用GDB标准程串行协议协同工作,实现对目标机上的系统内核和上层应用的监控和调试功能。调试stub是嵌入式系统中的一段代码,作为宿主机GDB和目标机调试程序间的一个媒介而存在。
就目前而言,嵌入式Linux系统中,主要有三种远程调试方法,分别适用于不同场合的调试工作:用ROM Monitor调试目标机程序、用KGDB调试系统内核和用gdbserver调试用户空间程序。这三种调试方法的区别主要在于,目标机远程调试stub 的存在形式的不同,而其设计思路和实现方法则是大致相同的。
而我们最常用的是调试应用程序。就是采用gdb+gdbserver的方式进行调试。在很多情况下,用户需要对一个应用程序进行反复调试,特别是复杂的程序。采用GDB方法调试,由于嵌入式系统资源有限性,一般不能直接在目标系统上进行调试,通常采用gdb+gdbserver的方式进行调试。 gdbserver在目标系统中运行,GDB则在宿主机上运行。
行GDB调试,目标系统必须包括gdbserver程序,宿主机也必须安装GDB程序。一般Linux发行版中都有一个可以运行的GDB,但开发人员不能直接使用该发行版中的GDB来做远程调试,而要获取GDB的源代码包,针对arm平台作一个简单配置,重新编译得到相应GDB。GDB的源代码包可以从 http: //ftp.cs.pu.edu.tw/linux/sourceware/gdb/releases/下载,最新版本为gdb-6.4。下载到某个目录,笔者下载到自己的用户目录:/home/vicky。
下载完后,进入/home/vicky目录,配置编译步骤如下:
#tar jxvf gdb-6.4-tar-bz2
#cd gdb-6.4
#./configure --target=arm-linux --prefix=/usr/local/arm-gdb -v
#make
这一步的时候可能会有问题,提示一个函数中(具体函数名不记得了)parse error,就是unsigned前边多了一个”}”,你用vi进入那一行把它删掉就行了。
#make install
#export PATH=$PATH:/usr/local/arm-gdb
进入gdbserver目录:
#./configure --target=arm-linux –host=arm-linux
#make CC=/usr/local/arm/2.95.3/bin/arm-linux-gcc
(这一步要指定arm-linux-gcc的位置)
没有错误的话就在gdbserver目录下生成gdbserver可执行文件,把它烧写到flash的根文件系统分区,或通过nfs mount的方式都可以。只要保证gdbserver能在开发板上运行就行。
下面就可以用gdb+gdbserver调试我们开发板上的程序了。在目标板上运行gdbserver,其实就是在宿主机的minicom下,我的RedHat Linux装在vmware下的。我是在minicom下#mount 192.168.2.100:/ /tmp后做的(这里参数-o nolock可以不加,不加这一步执行得反而更快些),hello和gdbserver都是位于Linux根目录下,把主机根目录挂在到开发板的/tmp 目录下。
要进行gdb调试,首先要在目标系统上启动gdbserver服务。在gdbserver所在目录下输入命令:
(minicom下)
#cd /tmp
#./gdbserver 192.168.2.100:2345 hello
192.168.2.100为宿主机IP,在目标系统的2345端口开启了一个调试进程,hello为要调试的程序。
出现提示:
Process /tmp/hello created: pid=80
Listening on port 2345
(另一个终端下)
#cd /
#export PATH=$PATH:/usr/local/arm-gdb/bin
#arm-linux-gdb hello
(gdb) target remote 192.168.2.223:2345
(192.168.2.223为开发板IP)
出现提示:
Remote debugging using 192.168.2.223:2345
[New thread 80]
[Switching to thread 80]
0x40002a90 in ??()
同时在minicom下提示:
Remote debugging from host 192.168.2.100
(gdb)
连接成功,这时候就可以输入各种GDB命令如list、run、next、step、break等进行程序调试了。
以上针对通过nfs mount和tftp的方式,只能在主机上调试好后下载到开发板上运行,如果有错误要反复这个过程,繁琐不说,有些程序只能在开发板上调试。所以笔者采用了gdbserver的远程调试方式。希望对大家调试程序有用!
嵌入式Linux操作系统名词及资源大全
作为一名Linux开发人员,对Linux下的名词要有了解,最好是非常清晰的知道它是什么东西,拿来做什么的。每一个名词都代表着一个资源,也代表着一个Linux的潮流,如果听都没听过就去开发相关领域的应用,那只能是瞎摸,吃力又不讨好。举个例子,欲在Linux下支持一款软Modem或一种打印机,而Linux的发行版或是厂家都是没有驱动的,那么一般会出现三种开发人员:
一:习惯地要厂家想想办法,威胁说没有就换硬件,毕竟硬件厂商多的是。
二:职业性的到linmodem.org或http://www.linuxprinting.org/这类网站去找找看,有则拿来改改就用上了。没有则考虑换硬件,除非设备简单,参考下其它类似硬件改改就能用。
三:实在太牛了,什么事情都是靠自己的,用个一年半载的自己把驱动写出来。
从技术角度来看,第一种是低水平的开发人员,第二种是优秀的开发人员,第三种是天才。
但如果从一个项目(非本身就是做驱动的项目)的角度上来看,第一种是合格的开发人员,第二种仍是优秀的开发人员,而第三种,我们用一张图来形容请点击链接(仅为加深大家印象,搞笑一下,非攻击性),要项目组全是这种人,十个项目九个亏,要密切注意你自己或Team里面是否有这种冲动的人,要及时予以扼杀或纠正,否则时间浪费了钱也白花了。
先说一番道理,目的是要表明Linux名词及资源的重要性,所以,我们要好好了解Linux的名词,看看有什么样的资源可以供我们利用,不懂利用资源的程序员肯定不能成为优秀的开发人员,还是回windows下让比尔牵着鼻子走算了。如下是资源列表及相关介绍:
嵌入式Linux综合
Tomlinux 嵌入式Linux文档、资源及下载,提供免费版本的嵌入式Linux系统。
Linuxforum Linux技术交流与提问,有什么问题去那里发问,肯定会有所帮助。
Freshmeat 有许多Linux项目资源,内容丰富,版本最新还可发布自己的项目。
Sourceforge 最著的名源码下载网站,内容丰富,版本最新还可发布自己的项目。
GNU 查阅各种标准,下载GNU软件。
Kernel 下载最新内核及查阅Linux内核的最新动向。
TurboLinux 提供最佳中文支持,也是一个很好的开发平台,兼容性略差于Redhat。
Redhat Linux 最佳开发环境,兼容性最好,从V9.0起,对中文也开始提供了支持。
LinuxDevices 使用Linux操作系统的产品及硬件信息。
Linuxtoday Linux新闻及潮流,Linux最新动向。
嵌入式Linux系统
Lilo Boot loaer,Lilo的使用及配置方法是开发人员必须掌握的。
Grub 图形化操作Boot loader,界面美观,对桌面版系统比较适用。
LinuxBIOS 一种快度启动的BIOS。
uClibc 很小的基本库,要编译很小的静态应用程序非它不可,标准libc太大了。
SDL 非常成熟Linux多媒体库,许多ps/2游戏就是用它来开发的。
Busybox Linux命令集,最常用的上百个Linux命令集中成一个小程序,cool。
TinyX 支持任意vesa2.0标准显示卡,启动速度非常快且完成兼容于标准X。
MiniGUI 国产高速微型GUI,兼容性不够好,比Microwin好,适合小型应用系统。
Microwindows 高速微型GUI,兼容部分X功能,不成熟,适用于小型应用系统 。
Icewm 微型窗口管理器,windows风格,支持theme,又小又快。
M-system DOC硬件厂商,并提供DOC的驱动程序和在DOC上安装Linux的向导。
Linmodem 带有众多软modem(即winmodem)的驱动程序。
PPP Linux Modem拔号支持及应用程序。
PPPoE Linux xDSL(包括ADSL)宽带支持及应用程序。
Linuxprinting 带众多打印机驱动程序,如HP系统打印机基本上都有。
Rdesktop 对win2000/winxp/win2003的rdp5/6 支持及应用。
uClinux 一种无MMU的Linux,主要应用于微型控制器。
Mvista 可下载免费开发包,带有X86,StrongARM,PPC,MIPS等平台的支持。
BlueCat 著名嵌入式开发包,其对ARM系统的支持好。
RTLinux 嵌入式Linux硬实时操作系统。
Lineo Linux PDA支持的至强,已开发出很多成熟的PDA应用。
嵌入式Linux应用
GTK GTK工具集,可以开发出像GIMP一样界面功能的软件,非常成熟。
QT/E KDE所使用的工具体,能开发出类似windows界面的应用,有嵌入式版。
Mozilla 著名的Mozilla浏览器,支持与IE相差不远,而且有些功能是IE没用的。
Glade 用于编写GTK的界面,非常方便,用Glade画完图然后填入空函数即可。
Anjuta c/c++ IDE环境,可直接编译,跟踪程序。
SNavigator 项目管理、代码阅读工具,使得代码的阅读及修改变得非常方便。
Kdevelop c/c++ IDE开发环境,带有大量的开发文档,是Linux下的MSDN。
Apache 著名的web服务程序,历史悠久,使用者无数。
Mysql 一种中小型数据库,速度快具免费,是Linux下的首选数据库。
PHP web脚本语言,比ASP、CGI等快得多也好用得多,非常流行。
Proftpd 可提供Ftp文件服务,功能强且效率高。
Sendmail 邮件服务程序,支持巨大数量的用户,功能强大且稳定。
Linuxgames 众多Linux游戏及Linux游戏新闻。
Themes.org Linux桌面主题,KDE、GNOME、ICEWM,GTK等等什么的主题都有。
GDB 古代著名的代码调试跟踪工具,但IDE一般得调用它才能调试跟踪。
Java Linux对Java的支持,有桌面和嵌入式的版本。
Tomcat全攻略
随着java的流行,其在web上的应用也越来越广,tomcat作为一个开源的servlet容器,应用前景越来越广,本文将向你讲述tomcat的一些知识。
简介
tomcat是jakarta项目中的一个重要的子项目,其被JavaWorld杂志的编辑选为2001年度最具创新的java产品(Most Innovative Java Product),同时它又是sun公司官方推荐的servlet和jsp容器(具体可以见http://java.sun.com/products/jsp/tomcat/),因此其越来越多的受到软件公司和开发人员的喜爱。servlet和jsp的最新规范都可以在tomcat的新版本中得到实现。
安装及配置
tomcat最新版本为4.0.1,这个版本用了一个新的servlet容器Catalina,完整的实现了servlet2.3和jsp1.2规范。注意安装之前你的系统必须安装了jdk1.2以上版本。
(一):安装
1:windows平台
从tomcat网站下载 jakarta-tomcat-4.0.1.exe,按照一般的windows程序安装步骤即可安装好tomcat,安装时它会自动寻找你的jdk和jre的位置。
2:linux平台
下载 jakarta-tomcat-4.0.1.tar.gz,将其解压到一个目录。
(二):配置
运行tomcat需要设置JAVA_HOME变量
set JAVA_HOME=c:/jdk (win98,在msdos方式下使用,
或者放入autoexec.bat中)
export JAVA_HOME=/usr/local/jdk (linux下使用,
放到/etc/bashrc或者/etc/profile中)
(三):运行
设置完毕后就可以运行tomcat服务器了,进入tomcat的bin目录,win98下用startup启动tomcat,linux下用startup.sh,相应的关闭tomcat的命令为shutdown和shutdown.sh。
启动后可以在浏览器中输入http://localhost:8080/测试,由于tomcat本身具有web服务器的功能,因此我们不必安装apache,当然其也可以与apache集成到一起,下面会介绍。
下面你可以测试其自带的jsp和servlet示例。
应用
(一):目录结构
tomcat的目录结构如下:
目录名 简介
bin 存放启动和关闭tomcat脚本
conf 包含不同的配置文件,server.xml(Tomcat的主要配置文件)和web.xml
work 存放jsp编译后产生的class文件
webapp 存放应用程序示例,以后你要部署的应用程序也要放到此目录
logs 存放日志文件
lib/japser/common 这三个目录主要存放tomcat所需的jar文件
(二):server.xml配置简介
下面我们将讲述这个文件中的基本配置信息,更具体的配置信息见tomcat的文档
元素名 属性 解释
server port 指定一个端口,这个端口负责监听关闭tomcat的请求
shutdown 指定向端口发送的命令字符串
service name 指定service的名字
Connector (表示客户端和service之间的连接) port 指定服务器端要创建的端口号,并在这个断口监听来自客户端的请求
minProcessors 服务器启动时创建的处理请求的线程数
maxProcessors 最大可以创建的处理请求的线程数
enableLookups 如果为true,则可以通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其ip地址
redirectPort 指定服务器正在处理http请求时收到了一个SSL传输请求后重定向的端口号
acceptCount 指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理
connectionTimeout 指定超时的时间数(以毫秒为单位)
Engine (表示指定service中的请求处理机,接收和处理来自Connector的请求) defaultHost 指定缺省的处理请求的主机名,它至少与其中的一个host元素的name属性值是一样的
Context (表示一个web应用程序,通常为WAR文件,关于WAR的具体信息见servlet规范) docBase 应用程序的路径或者是WAR文件存放的路径
path 表示此web应用程序的url的前缀,这样请求的url为http://localhost:8080/path/****
reloadable 这个属性非常重要,如果为true,则tomcat会自动检测应用程序的/WEB-INF/lib 和/WEB-INF/classes目录的变化,自动装载新的应用程序,我们可以在不重起tomcat的情况下改变应用程序
host (表示一个虚拟主机) name 指定主机名
appBase 应用程序基本目录,即存放应用程序的目录
unpackWARs 如果为true,则tomcat会自动将WAR文件解压,否则不解压,直接从WAR文件中运行应用程序
Logger (表示日志,调试和错误信息) className 指定logger使用的类名,此类必须实现org.apache.catalina.Logger 接口
prefix 指定log文件的前缀
suffix 指定log文件的后缀
timestamp 如果为true,则log文件名中要加入时间,如下例:localhost_log.2001-10-04.txt
Realm (表示存放用户名,密码及role的数据库) className 指定Realm使用的类名,此类必须实现org.apache.catalina.Realm接口
Valve (功能与Logger差不多,其prefix和suffix属性解释和Logger 中的一样) className 指定Valve使用的类名,如用org.apache.catalina.valves.AccessLogValve类可以记录应用程序的访问信息
directory 指定log文件存放的位置
pattern 有两个值,common方式记录远程主机名或ip地址,用户名,日期,第一行请求的字符串,HTTP响应代码,发送的字节数。combined方式比common方式记录的值更多
注意:1:经过我测试,我设置Context 的path="",reloadable=true,然后放一个WAR文件到webapps目录,结果tomcat不能检测出此文件(重起tomcat可以),而把此文件解压,则tomcat会自动检测出这个新的应用程序。如果不能自动检测WAR文件,我们可以利用下面管理中讲的方法来部署应用程序。
2:默认的server.xml中,Realm元素只设置了一个className属性,但此文件中也包含几个通过JDBC连接到数据库进行验证的示例(被注释掉了),通过Realm元素我们可以实现容器安全管理(Container Managed Security)。
3:还有一些元素我们没有介绍,如Parameter,loader,你可以通过tomcat的文档获取这些元素的信息。
(三):管理
1:配置
在进行具体的管理之前,我们先给tomcat添加一个用户,使这个用户有权限来进行管理。
打开conf目录下的tomcat-users.xml文件,在相应的位置添加下面一行:
<user name="zf" password="zf" roles="standard,manager"/>
注意:这一行的最后部分一定是/>,tomcat的文档掉了/符号,如果没有/符号的话,tomcat重起时将无法访问应用程序。通过logs/catalina.out文件你可以看到这个错误的详细信息。
然后重起tomcat,在浏览器中输入http://localhost:8080/manager/,会弹出对话框,输入上面的用户名和密码即可。
2:应用程序列表
在浏览器中输入http://localhost:8080/manager/list,浏览器将会显示如下的信息:
OK - Listed applications for virtual host localhost
/ex:running:1
/examples:running:1
/webdav:running:0
/tomcat-docs:running:0
/manager:running:0
/:running:0
面的信息分别为应用程序的路径,当前状态(running 或者stopped),与这个程序相连的session数。
3:重新装载应用程序
在浏览器中输入 http://localhost:8080/manager/reload?path=/examples,浏览器显示如下:
OK - Reloaded application at context path /examples
表示example应用程序装载成功,如果我们将server.xml的Context元素的reloadable属性设为true(见上面表格),则没必要利用这种方式重新装载应用程序,因为tomcat会自动装载。
4:显示session信息
在浏览器中输入http://localhost:8080/manager/sessions?path=/examples,浏览器显示如下:
OK - Session information for application at context path
/examples Default maximum session inactive interval 30 minutes
5:启动和关闭应用程序
在浏览器中输入http://localhost:8080/manager/start?path=/examples和http://localhost:8080/manager/stop?path=/examples分别启动和关闭examples应用程序。
6:部署及撤销部署
WAR有两种组织方式,一种是按一定的目录结构组织文件,一种是一个后缀为WAR的压缩包,因此它的部署方式也有两种:
(1):在浏览器中输入: http://localhost:8080/manager/install?path=/examples&war=file:/c:examples
就会将按目录结构组织的WAR部署
(2):如果输入: http://localhost:8080/manager/install?path=/examples&war=jar:file:/c:examples.war!/
就会将按压缩包组织的WAR部署,注意此url后半部分一定要有!/号。
部署后就可以用 http://localhost:8080/examples访问了。
在浏览器中输入: http://localhost:8080/manager/remove?path=/examples
就会撤销刚才部署的应用程序。
(四):与apache集成
虽然tomcat也可以作web服务器,但其处理静态html的速度比不上apache,且其作为web服务器的功能远不如apache,因此我们想把apache和tomcat集成起来。
我们以linux系统为例介绍.
从apache网站下载 apache1.3.22源代码版本,然后使用如下命令配置安装apache:
mkdir /usr/local/apache
tar zxvf apache.1.32.tar.gz
cd apache.1.32
./configure --prefix=/usr/local/apache --enable-module=so
make
make install
注意configure命令指定目标安装目录,并且加入DSO(Dynamic Shared Object)支持,注意一定不要忘了这一个选项。
然后下载 webapp模块,将解压后mod_webapp.so文件放入apache的libexec目录,编辑apache的conf目录下的httpd.conf,在这个文件的最后加入下面三行:
LoadModule webapp_module libexec/mod_webapp.so
WebAppConnection warpConnection warp localhost:8008
WebAppDeploy examples warpConnection /examples/
第一行是加入webapp模块,如果编译apache时不增加DSO支持,则无法使用LoadModule指令,第二行指定tomcat与apache的连接,第三行指定部署那个应用,这两个指令使用格式如下:
WebAppConnection [connection name] [provider] [host:port]
WebAppDeploy [application name] [connection name] [url path]
其中connection name指定连接名,provider只能是warp,port端口与你的tomcat的配置文件server.xml最后几行指定的要保持一致。文件如下:
<Service name="Tomcat-Apache">
<Connector className="org.apache.catalina.connector.warp.WarpConnector"
port="8008" minProcessors="5" maxProcessors="75"
enableLookups="true"
acceptCount="10" debug="0"/>
******
</Service>
application name与你在tomcat中部署的应用名一致,url path指定访问这个应用的url。例如上面的例子可以通过http://localhost/examples/来访问tomcat中的examples应用。
(五):中文问题
一般jsp的乱码问题可以通过在jsp中加入<%@ page contentType="text/html;charset=GB2312" %>来解决,至于servlet的乱码在可以使用servlet2.3中提供的HttpServeletRequest.setCharacterEncoding函数。更详细的中文问题请见 JSP/Servlet 中的汉字编码问题。
综述
tomcat作为一个servlet(jsp也被编译为servlet执行)容器,其应用前景是非常好的,如果与 jboss结合起来,则可以实现sun的j2ee规范(用jboss作ejb服务器)。jboss的官方网站也提供集成了tomcat3.2*的jboss以供下载。另外一个开源的应用服务器( enhydra) 也是基于tomcat的,其提供了更友好的管理界面,部署应用程序也更简单,功能也更强大。
参考资料
tomcat文档
www.jguru.com的tomcat faq
apache的文档
SQLite-3.3.13在ARM2410s 开发板上的移植
Compile SQLite using the cross-compiler such as arm-linux-gcc
first, get sqlite-3.3.13.tar.gz from www.sqlite.org unzip it,
#tar -zxvf sqlite-3.3.13.tar.gz
change into the sqlite-3.3.13 directory
cd sqlite-3.3.13
make a new directory such as 'build' under sqlite-3.3.13 directory,
open the configure-script using your favorite text-editor ,such as:
#vi configure
I recomamnd that you make a copy of configure before editing the configure file
cp configure configure.old
and edit the configure.
Comment out the following commands by putting a '#' in front of them(looks like):
#if test "$cross_compiling" = "yes"; then
# { { echo "$as_me:$LINENO:: error: unable to find a compiler for building build tools" >&5
#echo "$as_me: error: unable to find a compiler for building build tools" >&2;}
# { (exit 1); exit 1; }; }
#fi
. . .
#else
# test "$cross_compiling" = yes &&
# { { echo "$as_me:$LINENO:: error: cannot check for file existence when cross compiling" >&5
#echo "$as_me: error: cannot check for file existence when cross compiling" >&2;}
# { (exit 1); exit 1; }; }
. . .
#else
# test "$cross_compiling" = yes &&
# { { echo "$as_me:$LINENO:: error: cannot check for file existence when cross compiling" >&5
#echo "$as_me: error: cannot check for file existence when cross compiling" >&2;}
# { (exit 1); exit 1; }; }
Save the configure script, change into the build directory you created and call the edited configure script from the sqlite
directory by using the following option:
../sqlite/configure --disable-tcl --host=arm-linux
After that configure should have created a Makefile and a libtool script in your build directory.
Open the Makefile using your favorite text editor and find the following lines:
BCC = arm-linux-gcc -g -O2
change to
BCC = gcc -g -O2
The reason for these changes is that the created files have to be executed on the PC during the compilation, so we have to
compile them with the standard gcc and not the arm-linux-gcc.
if you want compile static library version of sqlite3(only one execute file for distribution) on ARM, edit Makefile ,
find
sqlite3$(TEXE): $(TOP)/src/shell.c .libs/libsqlite3.la sqlite3.h
change to
sqlite3$(TEXE): $(TOP)/src/shell.c .libs/libsqlite3.a sqlite3.h
find
-o $@ $(TOP)/src/shell.c .libs/libsqlite3.la
change to
-o $@ $(TOP)/src/shell.c .libs/libsqlite3.a
save and quit editor
run 'make' command to create the sqlite3 execute file, after a successful compile,Now you should find a hiden “.libs”
directory in your build directory containing sqlite shared object files, like libsqlite.so or static libray files like
libsqlite3.a .
run 'arm-linux-strip sqlite3' to decrease the execute file size.
upload the sqlite3 to target ARM9 board by any FTP client and make it executive:
on ARM9 board with terminal or telnet ,run
chmod 775 sqlite3
and then run sqlite3 like this
sqlite3 ex2
,if you see the following messages:
SQLite version 3.3.13 Enter ".help" for instructions
sqlite>
C语言变长数组之剖析
我们知道,与C++等现代编程语言不同,传统上的C语言是不支持变长数组功能的,也就是说数组的长度是在编译期就确定下来的,不能在运行期改变。不过,在C99标准中,新增的一项功能就是允许在C语言中使用变长数组。然而,C99定义的这种变长数组的使用是有限制的,不能像在C++等语言中一样自由使用。
参考文献[1]中对变长数组的说明如下:
C99 gives C programmers the ability to use variable length arrays, which are arrays whose sizes are not known until run time. A variable length array declaration is like a fixed array declaration except that the array size is specified by a non-constant expression. When the declaration is encountered, the size expression is evaluated and the array is created with the indicated length, which must be a positive integer. Once created, variable length array cannot change in length. Elements in the array can be accessed up to the allocated length; accessing elements beyond that length results in undefined behavior. There is no check required for such out-of-range accesses. The array is destroyed when the block containing the declaration completes. Each time the block is started, a new array is allocated.
以上就是对变长数组的说明,此外,在文献[1]中作者还说明,变长数组有以下限制:
1、变长数组必须在程序块的范围内定义,不能在文件范围内定义变长数组;
2、变长数组不能用static或者extern修饰;
3、变长数组不能作为结构体或者联合的成员,只能以独立的数组形式存在;
4、变长数组的作用域为块的范围,对应地,变长数组的生存时间为当函数执行流退出变长数组所在块的时候;
上述限制是最常见的一些限制因素,此外,当通过typedef定义变长数组类型时,如何确定变长数组的长度,以及当变长数组作为函数参数时如何处理,作者也做了一一说明。详细的细节情况请参阅文献[1]。由于变长数组的长度在程序编译时未知,因此变长数组的内存空间实际上是在栈中分配的。
gcc虽然被认为是最遵守C语言标准的编译器之一,但是它并不是严格按照ISO C标准规定的方式来实现的。gcc的实现方式采取了这样的策略:最大限度地遵守标准的规定,同时从实用的角度做自己的扩展。当然,gcc提供了编译选项给使用者以决定是否使用这些扩展功能。gcc的功能扩展分为两种,一种是gnu自己定义的语言扩展;另外一种扩展是在C89模式中引入由C99标准定义的C语言特性。在参考文献[2]中,有关gcc的C语言扩展占据了将近120页的篇幅,扩展的语言功能多达几十个,由此可看出gcc的灵活程度。
在参考文献[2]中,对变长数组的描述如下:
Variable-length automatic arrays are allowed in ISO C99, and as an extension GCC accepts them in C89 mode and in C++. (However, GCC’s implementation of variable-length arrays does not yet conform in detail to the ISO C99 standard.) These arrays are declared like any other automatic arrays, but with a length that is not a constant expression. The storage is allocated at the point of declaration and deallocated when the brace-level is exited.
以上这段话并没有详细的说明gcc的变长数组实现和ISO C99的差异究竟体现在什么地方,但是从描述来看,基本上和文献[1]中的描述是一致的。文献[2]中没有说明而在文献[1]中给予了说明的几点是:变长数组是否能用static或者extern修饰;能否作为复合类型的成员;能否在文件域起作用。
另外,在文献[2]中提到,采用alloca()函数可以获得和变长数组相同的效果。在作者所用的Red Hat 9.0(Linux
#include <alloca.h>
void *alloca(size_t size);
这个函数在调用它的函数的栈空间中分配一个size字节大小的空间,当调用alloca()的函数返回或退出的时候,alloca()在栈中分配的空间被自动释放。当alloca()函数执行成功时,它将返回一个指向所分配的栈空间的起始地址的指针;然而,非常特别的一点是,当alloca()函数执行失败时,它不会像常见的库函数那样返回一个NULL指针,之所以会出现这样的状况,是由于alloca()函数中的栈调整通常是通过一条汇编指令来完成的,而这样一条汇编指令是无法判断是否发生溢出或者是否分配失败的。alloca()函数通常被实现为内联函数,因此它是与特定机器以及特定编译器相关联的,可移植性因此而大打折扣,实际上是不推荐使用的。
作者之所以会关注变长数组的问题是出于一次偶然的因素,在调试的时候发现gdb给出的变长数组的类型很怪异,由此引发作者对gcc中的变长数组进行了测试。本文中给出的就是对测试结果的说明和分析。
第一个测试所用的源代码很简单,如下所示:
1 int
2 main(int argc, char *argv[])
3 {
4 int i, n;
5
6 n = atoi(argv[1]);
7 char arr[n+1];
8 bzero(arr, (n+1) * sizeof(char));
9 for (i = 0; i < n; i++) {
10 arr[i] = (char)('A' + i);
11 }
12 arr[n] = '';
13 printf("%sn", arr);
14
15 return (0);
16 }
上述程序名为dynarray.c,其工作是把参数argv[1]的值n加上1作为变长数组arr的长度,变长数组arr的类型为char。然后向数组中写入一些字符,并将写入的字符串输出。
像下面这样编译这个程序:
[root@cyc test]# gcc -g -o dynarray dynarray.c
然后,用gdb观察dynarray的执行情况:
[root@cyc test]# gdb dynarray
(gdb) break main
Breakpoint 1 at 0x
(gdb) set args 6
(gdb) run
Starting program: /root/source/test/a.out 6
Breakpoint 1, main (argc=2, argv=0xbfffe224) at dynarray.c:6
6 n = atoi(argv[1]);
(gdb) next
7 char arr[n+1];
(gdb) next
8 bzero(arr, (n+1) * sizeof(char));
(gdb) print/x arr
$2 = {0xb0, 0xe5}
(gdb) ptype arr
type = char [2]
(gdb) print &arr
$3 = (char (*)[2]) 0xbfffe
这里,当程序执行流通过了为变长数组分配空间的第7行之后,用print/x命令打印出arr的值,结果居然是两个字节;而如果尝试用ptype打印出arr的类型,得到的结果居然是arr是一个长度为2的字符数组。很明显,在本例中,因为提供给main()函数的参数argv[1]是6,因此按常理可知arr应该是一个长度为7的字符数组,但很遗憾,gdb给出的却并不是这样的结果。用print &arr打印出arr的地址为0xbfffe
(gdb) x/4x &arr
0xbfffe
(gdb) x/8x $esp
0xbfffe5b0: 0xbffffad8 0x
0xbfffe
可以看到,在&arr(即地址0xbfffe
(gdb) next
9 for (i = 0; i < n; i++) {
(gdb) next
10 arr[i] = (char)('A' + i);
(gdb) next
9 for (i = 0; i < n; i++) {
(gdb) until
12 arr[n] = '';
(gdb) next
13 printf("%sn", arr);
(gdb) x/8x $esp
0xbfffe5b0: 0x44434241 0x42004645 0xbfffe
0xbfffe
注意上面表示为蓝色的部分,由于Intel平台采用的是小端字节序,因此蓝色的部分实际上就是’ABCDEF’的十六进制表示。而红色的32位字则暗示着arr就是指向栈顶的指针。为了确认我们的这一想法,下面通过修改arr的值来观察程序的执行情况(需要注意的是:每一次运行时堆栈的地址是变化的):
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/source/test/dynarray 6
Breakpoint 1, main (argc=2, argv=0xbfffde24) at dynarray.c:6
6 n = atoi(argv[1]);
(gdb) next
7 char arr[n+1];
(gdb) next
8 bzero(arr, (n+1) * sizeof(char));
(gdb) print/x &arr
$3 = 0xbfffddc8
(gdb) x/8x $esp
0xbfffddb0: 0xbffffad8 0x
0xbfffddc0: 0x
(gdb) set *(unsigned int*)&arr=0xbfffddc0
(gdb) x/8x $esp
0xbfffddb0: 0xbffffad8 0x
0xbfffddc0: 0x
(gdb) next
9 for (i = 0; i < n; i++) {
(gdb) next
10 arr[i] = (char)('A' + i);
(gdb) next
9 for (i = 0; i < n; i++) {
(gdb) until
12 arr[n] = '';
(gdb) next
13 printf("%sn", arr);
(gdb) x/8x $esp
0xbfffddb0: 0xbffffad8 0x
0xbfffddc0: 0x44434241 0x40004645 0xbfffddc0 0xbfffddc0
地址0xbfffddc8(也就是arr的地址)处的值本来为0xbfffddb0,我们把它改成了0xbfffddc0,于是,当程序运行到向变长数组输入数据完成之后,我们发现这次修改的地址的确是从0xbfffddc0开始的。这就表明arr的确像我们通常所理解的一样,数组名即指针。只不过这个指针指向的位置在它的下方(堆栈向下生长),而不是像大多数时候一样指向上方的某个位置。
上面的测试结果表明:变长数组的确是在栈空间中分配的;变长数组的数组名实际上就是一个地址指针,指向数组所在的栈顶位置;而GDB无法判断出变长数组的数组名实际上是一个地址指针。
GDB为什么无法准确判断出变长数组的类型的原因尚不清楚,但是作者猜测这和变长数组的动态特性有关,由于变长数组是在程序动态执行的过程生成的,GDB无法向对待常规数组一样从目标文件包含的.stabs节中获得长度信息,于是给出了错误的类型信息。
另外,作者对变长数组的作用域进行了测试,测试代码根据上例修改得到,如下所示:
1 int n;
2 char arr[n+1];
3
4 int
5 main(int argc, char *argv[])
6 {
7 int i;
8
9 n = atoi(argv[1]);
10 bzero(arr, (n+1) * sizeof(char));
11 for (i = 0; i < n; i++) {
12 arr[i] = (char)('A' + i);
13 }
14 arr[n] = '';
15 printf("%sn", arr);
16
17 return (0);
18 }
当如下编译的时候,gcc会提示出错:
[root@cyc test]# gcc -g dynarray.c
dynarray.c:2: variable-size type declared outside of any function
可见gcc不允许在文件域定义变长数组。
对于gcc中的变长数组能否用static修饰则使用如下代码进行测试:
1 int
2 main(int argc, char *argv[])
3 {
4 int i, n;
5
6 n = atoi(argv[1]);
7 static char arr[n+1];
8 bzero(arr, (n+1) * sizeof(char));
9 for (i = 0; i < n; i++) {
10 arr[i] = (char)('A' + i);
11 }
12 arr[n] = '';
13 printf("%sn", arr);
14
15 return (0);
16 }
当编译此源文件的时候,gcc给出如下错误提示:
[root@cyc test]# gcc -g dynarray.c
dynarray.c: In function `main':
dynarray.c:7: storage size of `arr' isn't constant
dynarray.c:7: size of variable `arr' is too large
根据提示,可知当数组用static修饰的时候,不能将其声明为变长数组。至于这里的提示说arr太大,作者猜测可能的原因是这样的:对于整数,gcc在编译期赋予了一个非常大的值,于是导致编译报错,不过这仅仅是猜测而已。
最后需要说明的是,作者是出于对gcc如何实现变长数组的方式感兴趣才进行上面的这些测试的。对于编程者来说,不用做这样的测试,也不需要知道变长数组是位于栈中还是其它地方,只要知道变长数组有上面这样一些限制就行了。另外,本文中有很多地方充斥着作者的推断和猜测。不过这并没有太大的关系,又不是写论文,谁在乎呢?
另外,上面的测试也说明了:尽管文献[2]没有像文献[1]中那样仔细说明变长数组的限制条件,但实际上它就是那样工作的。再一次体现出gcc的确很好地遵守了C标准的规定。
[1] Samuel P. Harbison III, Guy L. Steele Jr.; C: A Reference Manual Fifth Edition; Prentice Hall, Pearson Education, Inc.; 2002
[2] Richard M. Stallman and the GCC Developer Community; Using the GNU Compiler Collection; FSF; May 2004
在ARM-Linux平台上移植SQLite
摘要:本文首先对嵌入式数据库SQLite做了简单的介绍,对移植所采用的软硬件平台作了简单的说明。然后以SQLite3为蓝本对移植过程中的细节作了详细的说明,并对移植后的SQLite3数据库进行了测试。测试结果表明,本文所采取的移植方式是有效的。
关键字:ARM-Linux、嵌入式、SQLite
Port SQLite to ARM-Linux Platform
Abstract: This paper first give a brief introduction to SQLite database and the hardware and software platform to port. Then demonstrate SQLite3’s porting process to ARM-Linux in detail and test the ported SQLite3. The testing result states that the porting method this paper proposed is effective.
Keywords: ARM-Linux、embedded、SQLite
1、引言
本文将简要介绍如何在ARM-Linux平台上移植SQLite嵌入式数据库。SQLite是一个采用C语言开发的嵌入式数据库引擎。SQLite的最新版本是3.3.8,在不至于引起混淆的情况下,本文也将其简称为SQLite3。
数据库的目标是实现对数据的存储、检索等功能。传统的数据库产品除提供了基本的查询、添加、删除等功能外,也提供了很多高级特性,如触发器、存储过程、数据备份恢复等。但实际上用到这些高级功能的时候并不多,应用中频繁用到的还是数据库的基本功能。于是,在一些特殊的应用场合,传统的数据库就显得过于臃肿了。在这种情况下,嵌入式数据库开始崭露头角。嵌入式数据库是一种具备了基本数据库特性的数据文件,它与传统数据库的区别是:嵌入式数据库采用程序方式直接驱动,而传统数据库则采用引擎响应方式驱动。嵌入式数据库的体积通常都很小,这使得嵌入式数据库常常应用在移动设备上。由于性能卓越,所以在高性能的应用上也经常见到嵌入式数据库的身影。
SQLite是一种嵌入式数据库。SQLite的目标是尽量简单,因此它抛弃了传统企业级数据库的种种复杂特性,只实现那些对于数据库而言非常必要的功能。尽管简单性是SQLite追求的首要目标,但是其功能和性能都非常出色。它具有这样一些特点[1]:支持ACID事务(ACID是Atomic、Consistent、Isolated、Durable的缩写);零配置,不需要任何管理性的配置过程;实现了大部分SQL92标准;所有数据存放在一个单独的文件之中,支持的文件大小最高可达2TB;数据库可以在不同字节序的机器之间共享;体积小,在去掉可选功能的情况下,代码体积小于150KB,即使加入所有可选功能,代码大小也不超过250KB;系统开销小,检索效率高,执行常规数据库操作时速度比客户/服务器类型的数据库快;简单易用的API接口;可以和Tcl、Python、C/C++、Java、Ruby、Lua、Perl、PHP等多种语言绑定;自包含,不依赖于外部支持;良好注释的代码;代码测试覆盖率达95%以上;开放源码,可以用于任何合法用途。由于这样一些杰出的优点,SQLite获得了由Google与O’Reilly举办的2005 Open Source Award!
由于SQLite具有功能强大、接口简单、速度快、占用空间小这样一些特殊的优点,因此特别适合于应用在嵌入式环境中。SQLite在手机、PDA、机顶盒等设备上已获得了广泛应用。本文将说明如何在ARM-Linux内核的基础上移植SQLite3。
2、软硬件平台
本文中采用的硬件平台为Sitsang嵌入式评估板。Sitsang评估板的核心是PXA255嵌入式处理器,PXA255是一款基于Intel XScale微架构的高性能、低功耗嵌入式处理器。Sitsang评估板上配备了Flash存储器、LCD、触摸屏、USB接口、以太网接口、全功能串口(FFUART)、蓝牙串口(BTUART)、音频接口等诸多硬件资源。
底层软件系统是以ARM-Linux内核为基础的。Sitsang评估板使用的ARM-Linux是在linux-2.4.19内核上打了patch-2.4.19-sitsang2补丁后编译而成。
要将SQLite3移植到Sitsang评估板上,除了要有底层操作系统的支持外,还必须要有相应的交叉编译工具链。由于Sitsang评估板采用的是ARM-Linux作为底层操作系统,因此需要首先安装ARM-Linux工具链。关于ARM-Linux工具链的安装可以参阅文献[4]。ARM-Linux工具链通常安装在/usr/local/arm-linux/bin/目录下,通常以arm-linux-开头。本文中将会涉及到的主要是arm-linux-gcc、arm-linux-ar、arm-linux-ranlib这样三个工具。
3、移植过程
# tar zxvf sqlite-3.3.8.tar.gz
解压抽取完成之后将会在/root目录下生成一个sqlite-3.3.8/子目录,在该目录中包含了编译所需要的所有源文件和配置脚本。SQLite3的所有源代码文件都位于sqlite-3.3.8/src/目录下。
和在PC环境下编译SQLite3不同,不能通过sqlite-3.3.8/目录下的configure脚本来生成Makefile文件。取而代之的是必须手动修改Makefile文件。在sqlite-3.3.8/目录下有一个Makefile范例文件Makefile.linux-gcc。首先通过下面的命令拷贝此文件并重命名为Makefile:
# cp Makefile.linux-gcc Makefile
接下来,用vim打开Makefile文件并手动修改Makefile文件的内容。首先找到Makefile文件中的下面这样一行:
TOP = ../sqlite
将其修改为:
TOP = .
找到下面这样一行:
TCC = gcc -O6
将其修改为:
TCC = arm-linux-gcc -O6
找到下面这样一行:
AR = ar cr
将其修改为:
AR = arm-linux-ar cr
找到下面这样一行:
RANLIB = ranlib
将其修改为:
RANLIB = arm-linux-ranlib
找到下面这样一行:
MKSHLIB = gcc -shared
将其修改为:
MKSHLIB = arm-linux-gcc -shared
注释掉下面这一行:
TCL_FLAGS = -I/home/drh/tcltk/8.4linux
注释掉下面这一行:
LIBTCL = /home/drh/tcltk/8.4linux/libtcl8.4g.a -lm -ldl
原则上,对Makefile的修改主要包括两个方面:首先是将编译器、归档工具等换成交叉工具链中的对应工具,比如,gcc换成arm-linux-gcc,ar换成ar-linux-ar,ranlib换成arm-linux-ranlib等等;其次是去掉与TCL相关的编译选项,因为默认情况下,将会编译SQLite3的Tcl语言绑定,但是在移植到ARM-Linux的时候并不需要,因此将两个与TCL有关的行注释掉。对Makefile的修改总结如表1所示。
表1 Makefile修改情况
位置 | 原值 | 修改为 |
17行 | TOP = ../sqlite | TOP = . |
73行 | TCC = gcc -O6 | TCC = arm-linux-gcc -O6 |
81行 | AR = ar cr | AR = arm-linux-ar cr |
83行 | RANLIB = ranlib | RANLIB = arm-linux-ranlib |
86行 | MKSHLIB = gcc -shared | MKSHLIB = arm-linux-gcc -shared |
96行 | TCL_FLAGS = -I/home/drh/tcltk/8.4linux | #TCL_FLAGS = -I/home/drh/tcltk/8.4linux |
103行 | LIBTCL = /home/drh/tcltk/8.4linux/libtcl8.4g.a -lm -ldl | #LIBTCL = /home/drh/tcltk/8.4linux/libtcl8.4g.a -lm -ldl |
接下来,还需要修改的一个的文件是main.mk,因为Makefile包含了这个文件。找到这个文件中的下面一行:
select.o table.o tokenize.o trigger.o
把它替换成:
select.o table.o tclsqlite.o tokenize.o trigger.o
也就是把该行上的tclsqlite.o去掉。这样编译的时候将不会编译SQLite3的Tcl语言绑定。
自此,修改工作就完成了,接下来就可以开始编译SQLite3了,这通过make命令即可完成:
# make
编译完成之后,将在sqlite3.3.8/目录下生成库函数文件libsqlite3.a和头文件sqlite3.h,这就是所需要的两个文件了。
4、测试
这里以SQLite官方站点http://sqlite.org的quick start文档中的测试程序为例对移植到ARM-Linux上的SQLite3进行测试。该程序清单如下:
1 #include <stdio.h>
2 #include <sqlite3.h>
3
4 static int
5 callback(void *NotUsed, int argc, char **argv, char **azColName)
6 {
7 int i;
8
9 for (i = 0; i < argc; i++) {
10 printf("%s = %sn", azColName[i], argv[i] ? argv[i] : "NULL");
11 }
12 printf("n");
13 return 0;
14 }
15
16 int
17 main(int argc, char **argv)
18 {
19 sqlite3 *db;
20 char *zErrMsg = 0;
21 int rc;
22
23 if (argc != 3) {
24 fprintf(stderr, "Usage: %s DATABASE SQL-STATEMENTn", argv[0]);
25 exit(1);
26 }
27 rc = sqlite3_open(argv[1], &db);
28 if (rc) {
29 fprintf(stderr, "Can't open database: %sn", sqlite3_errmsg(db));
30 sqlite3_close(db);
31 exit(1);
32 }
33 rc = sqlite3_exec(db, argv[2], callback, 0, &zErrMsg);
34 if (rc != SQLITE_OK) {
35 fprintf(stderr, "SQL error: %sn", zErrMsg);
36 sqlite3_free(zErrMsg);
37 }
38 sqlite3_close(db);
39 return 0;
40 }
将此源程序保存为test.c,然后,通过如下命令编译该程序:
# arm-linux-gcc -I /root/sqlite-3.3.8/ -L /root/sqlite-3.3.8 -o test test.c -lsqlite3
上述编译命令中,-I /root/sqlite-3.3.8指明了头文件sqlite3.h所在的目录,-L /root/sqlite3.3.8指定了库函数文件libsqlite3.a所在的目录,-o test指定编译生成的文件名为test,test.c是源程序文件,-lsqlite3指明要链接静态库文件libsqlite3.a。编译完成后,可以通过NFS或者FTP将test下载到Sitsang评估板上,通过ls命令可以看到test的大小只有300K左右:
[root@Sitsang2 root]$ll -h test
-rwxr-xr-x 1 root root 323.5k Jan 1 00:07 test
接下来就可以测试test程序了。test程序接受两个参数:第一个参数为数据库文件名,第二个参数为要执行的SQL语句。程序中与SQLite3的API相关的地方主要有四个:第27行的sqlite3_open(),第33行的sqlite3_exec(),第30行和第38行的sqlite3_close(),第36行的sqlite3_free()。关于SQLite3的API接口请参阅文献[1]。
下面是测试test程序的完整过程,需要注意的是由于命令较长,因此每一个命令都分成了多行输入,这样看起来要清楚一些:
[root@Sitsang2 root]$./test xyz.db "create table
> tbl0(name varchar(10), number smallint);"
[root@Sitsang2 root]$./test xyz.db "insert into
> tbl0 values('cyc', 1);"
[root@Sitsang2 root]$./test xyz.db "insert into
> tbl0 values('dzy', 2);"
[root@Sitsang2 root]$./test xyz.db "select *
> from tbl0;"
name = cyc
number = 1
name = dzy
number = 2
解释一下上面所用的测试命令:第一条命令在xyz.db这个数据库文件中创建了一个tbl0表,表中包含两个字段,字段name是一个变长字符串,字段number的类型为smallint;第二条命令向数据库的tbl0表中插入了一条记录(‘cyc’,1);第三条命令向数据库的tbl0表中插入了一条记录(‘dzy’,2);第四条命令则是查询表tbl0中的所有内容,与预期的一样,这条命令打印除了数据库中的两条刚插入的记录。由此可以得出结论,这几条命令确实都已经按照预期的目标工作了。
同时,在向数据库中插入上面所示的数据之后,可以看到数据库文件xyz.db大小已经发生了变化:
[root@Sitsang2 root]$ll -h xyz.db
-rw-r--r-- 1 root root 2.0k Jan 1 00:18 xyz.db
此时数据库文件xyz.db的大小为2K。自此,SQLite3数据库在Sitsang评估板上移植完成。测试结果表明数据库能够正常工作。
5、结论
SQLite是一个优秀的嵌入式数据库。本文详细描述了如何将SQLite3移植到ARM-Linux平台上,并对移植后的SQLite3进行了简单的测试。SQLite功能强大、效率高、零配置、体积小等诸多优点使得它很适用于嵌入式移动设备环境中。因此本文给出的移植SQLite3的细节具有积极意义。由于SQLite3采用C语言开发,因此可移植性非常好。本文所讨论的方法稍加修改也可适用于其它操作系统平台。
参考文献
[1] The Definitive Guide to SQLite。[美]Michael Owens著。Apress,2006
[3] SQLite移植手记。Hily Jiang。www.sqlite.com.cn,2006年11月
[4] Sitsang/PXA255 Evaluation Platform Linux User’s Guide。Intel Ltd,Sep. 2003
查看全文
嵌入式Linux用Qt Designer快速开发
RAD(快速应用程序开发)是一种高效的软件开发形式,可以让用户在极短的时间里创建一个图形化的用户界面。通常情况下,在一张空白的表单上,开发人员可以通过拖拉或点击的方式,在窗口的适当位置上添加一些输入框和按钮等窗口组件。这时,RAD工具会自动编写和维护代码。而用户所要做的只是确定当点击按钮或选择选单选项时将要发生什么事件。
在Linux下,一个非常流行的RAD工具就是Qt Designer。它是嵌入式公司Trolltech的Qt软件包的一个组成部分。如果用户使用的是KDE桌面,那么Qt已经自动安装上了,Qt Designer也很有可能已经被安装好。如果用户的系统没有安装,那么针对不同的版本,可以很方便地找到KDE Development Tools,并安装之。以Red Hat 9.0为例,用户可以从主选单→系统设置→添加/删除应用程序中选择KDE软件开发,即可完成Qt Designer的安装。
创建
为了快速地向大家展示一下Qt Designer功能,先创建一个简单的摄氏温度和华氏温度的转换程序。本文里将设计一个简单的GUI,并且添加一些简单的代码来实现温度的转换。因为是为了展示一下快速的开发过程,而不是一个严谨的软件项目的开发,所以这里不会进行任何的错误检查,也不进行输入检验(也就是检查用户的输入是否为合法的温度形式)、缓冲溢出检查等在日常软件开发中一定要做的步骤。
如果用户使用的是KDE,那么选单上应该已经有Qt Designer的图标。不同的发行版,图标的位置会有所不同。如果用户的发行版没有Qt Designer图标,那么可以在命令行模式下输入“designer”命令来启动该开发工具。在Red Hat 9.0中,可以通过点击主选单→编程→更多编程工具→Qt Designer来启动。
Qt Designer首先呈现给用户的是一个New/Open对话框。因为这里要创建一个C++程序,所以在此选择C++ Project,点击“OK”继续。
选择一个想要保存文件的位置,并且给出一个文件名,在此使用的文件名是cfconv。注意这里文件名的扩展名一定要是.pro。点击“Save”后,返回到了Project Settings对话框。
现在就已经在Qt Designer主窗口上了,确保Property Editor可见。如果它是不可见的,用户可以通过Windows→Views→Property Editor/Signal Handlers选单选项来使其可见(缺省情况下是可见的)。
通过选择File→New选单,然后选择Dialog来创建一个新的对话框。这时Qt Designer会创建一个新的空白表单(见图6),用户可以在其上放置输入框和按钮。
打开Property Editor,把name的值改为“cfconvMainForm”,把caption的值改为“Celsius to Fahrenheit Converter”。
这里表单的name是被应用程序使用的内部名字,在用户编写代码时,有时需要使用的就是这个名字。Caption指的是要在标题栏上显示的名字。
从左边的工具箱中选择Common Widgets,并且双击“TextLabel”。在表单的左上角放置一个标签,在这个标签位置下方再放置一个同样的标签。选中上面的标签,并且将其text值改为“Celsius”,相应地把第二个标签的text值也改为“Fahrenheit”。在这两个标签的后面加上两个对应的输入框,用于输入需要转换的温度和输出转换后的温度。从Common Widgets中双击选择LineEdit,然后在两个标签后创建两个LineEdit。
把两个LineEdit框的name值分别改为“celsiusLineEdit”和“fahrenheitLineEdit”,再把fahrenheitLineEdit文本框的readOnly属性改为True。
从Common Widgets上选择PushButton,并且创建两个按钮,分别将其name和text属性改为quitPushButton和Quit、convertPushButton和Convert。
现在按“Ctrl+S”或从选单中选择File→Save,接下来要求输入文件名。缺省情况下,使用的是表单的name值,扩展名使用的是.ui。用户可以接受这个名字,然后点击“Save”。
如果想看一看效果,用户可以按“Ctrl+T”或从选单中选择Preview→Preview Form来预览应用程序。但是现在按钮还不能做任何事情,所以下一步要做的事情就是让按钮和某一特定的动作相关联。当点击“Quit”按钮时,要求应用程序会被关闭;而当点击“Convert”按钮时,要求输入的温度由摄氏温度转换为华氏温度。
在“Quit”按钮上点击右键,选择Connections,然后点击“New”。从Sender列表中选择quitPushButton,从Signal列表中选择clicked(),从Receiver列表中选择cfconvMainFrom,从Slot列表中选择close()。
现在,当用户点击“Quit”按钮时,会向表单发送一个鼠标点击的信号,这将使这个表单关闭(因为这个表单是主表单,所以它关闭时应用程序也就同时关闭了)。要进行测试,可以选择Preview→Preview Form。这时点击“Quit”按钮,预览窗口就会被关闭。
下面为“Convert”按钮创建连接。在“Convert”按钮上点击右键,然后选择Connections。这时用户会发现这是一个全局连接窗口,而不是某一窗口部件的连接。点击“New”来创建一个新的连接。从Sender列表中选择convertPushbButton,从Signal列表中选择clicked(),从Receiver列表中选择cfconvMainFrom。本想将该按钮与fahrenheitLineEdit窗口部件相关联,但列表中却没有一个可以满足这项要求的栏目。因此需要创建一个新的栏目来完成这个连接。
点击“Edit Slots”和“New Function”,把函数名改为convert(),其它的值可以保持不变,点击“OK”来关闭窗口。
要完成这个连接,从Slot列表中选择convert()。
现在来完成应用程序的代码部分:创建convert()函数。在Project Overview窗口点击“cfconvmainform.ui.h”来启动Code Editor。此时convert()函数实际上已经存在了,只不过是空的罢了。输入下面的C++代码来完成函数:
|
七款嵌入式Linux操作系统简介
除了智能数字终端领域以外,Linux在移动计算平台、智能工业控制、金融业终端系统,甚至军事领域都有着广泛的应用前景。这些Linux被统称为“嵌入式Linux”。下面就来看看都有哪些嵌入式Linux在以上领域纵横驰骋吧!
RT-Linux
这是由美国墨西哥理工学院开发的嵌入式Linux操作系统。到目前为止,RT-Linux已经成功地应用于航天飞机的空间数据采集、科学仪器测控和电影特技图像处理等广泛领域。RT-Linux开发者并没有针对实时操作系统的特性而重写Linux的内核,因为这样做的工作量非常大,而且要保证兼容性也非常困难。为此,RT-Linux提出了精巧的内核,并把标准的Linux核心作为实时核心的一个进程,同用户的实时进程一起调度。这样对Linux内核的改动非常小,并且充分利用了Linux下现有的丰富的软件资源。
uClinux
uCLinux是Lineo公司的主打产品,同时也是开放源码的嵌入式Linux的典范之作。uCLinux主要是针对目标处理器没有存储管理单元MMU(Memory Management Unit)的嵌入式系统而设计的。它已经被成功地移植到了很多平台上。由于没有MMU,其多任务的实现需要一定技巧。uCLinux是一种优秀的嵌入式Linux版本,是micro-Conrol-Linux的缩写。它秉承了标准Linux的优良特性, 经过各方面的小型化改造,形成了一个高度优化的、代码紧凑的嵌入式Linux。虽然它的体积很小,却仍然保留了Linux的大多数的优点:稳定、良好的移植性、优秀的网络功能、对各种文件系统完备的支持和标准丰富的API。它专为嵌入式系统做了许多小型化的工作,目前已支持多款CPU。 其编译后目标文件可控制在几百KB数量级,并已经被成功地移植到很多平台上。
Embedix
Embedix是由嵌入式Linux行业主要厂商之一Luneo推出的,是根据嵌入式应用系统的特点重新设计的Linux发行版本。Embedix提供了超过25种的Linux系统服务,包括Web服务器等。系统需要最小8MB内存,3MB ROM或快速闪存。Embedix基于Linux 2.2内核,并已经成功地移植到了Intel x86和PowerPC处理器系列上。像其它的Linux版本一样,Embedix可以免费获得。Luneo还发布了另一个重要的软件产品,它可以让在Windows CE上运行的程序能够在Embedix上运行。Luneo还将计划推出Embedix的开发调试工具包、基于图形界面的浏览器等。可以说,Embedix是一种完整的嵌入式Linux解决方案。
XLinux
XLinux是由美国网虎公司推出,主要开发者是陈盈豪。他在加盟网虎几个月后便开发出了基于XLinux的、号称是世界上最小的嵌入式Linux系统,内核只有143KB,而且还在不断减小。XLinux核心采用了“超字元集”专利技术,让Linux核心不仅可能与标准字符集相容,还含盖了12个国家和地区的字符集。因此,XLinux在推广Linux的国际应用方面有独特的优势。
PoketLinux
PoketLinux由Agenda公司采用、作为其新产品“VR3 PDA”的嵌入式Linux操作系统。它可以提供跨操作系统构造统一的、标准化的和开放的信息通信基础结构,在此结构上实现端到端方案的完整平台。PoketLinux资源框架开放,使普通的软件结构可以为所有用户提供一致的服务。PoketLinux平台使用户的视线从设备、平台和网络上移开,由此引发了信息技术新时代的产生。在PoketLinux中,称之为用户化信息交换(CIE),也就是提供和访问为每个用户需求而定制的“主题”信息的能力,而不管正在使用的设备是什么。
MidoriLinux
由Transmeta公司推出的MidoriLinux操作系统代码开放,在GUN普通公共许可(GPL)下发布,可以在http://midori.transmeta.com上立即获得。该公司有个名为“MidoriLinux计划”。“MidoriLinux”这个名字来源于日本的“绿色”——Midori,用来反映其Linux操作系统的环保外观。
红旗嵌入式Linux
由北京中科院红旗软件公司推出的嵌入式Linux是国内做得较好的一款嵌入式操作系统。目前,中科院计算所自行开发的开放源码的嵌入式操作系统——Easy Embedded OS(EEOS)也已经开始进入实用阶段了。该款嵌入式操作系统重点支持p-Java。系统目标一方面是小型化,另一方面能重用Linux的驱动和其它模块。由于有中科院计算所的强大科研力量做后盾,EEOS有望发展成为功能完善、稳定、可靠的国产嵌入式操作系统平台。
思考与展望
以上列举的众多嵌入式Linux操作系统中,国内对于uClinux和RT-Linux研究的较多,很多基于它们的产品已经面世,比如华恒公司已经把uClinux成功移植,并投放到市场。
正是由于Linux开放源代码的特点,所以全世界的开发厂商都站在同一个起跑线上。国内的研究机构和企业也正在积极投入人力、物力,力争在嵌入式操作系统上有所为。但应该清醒认识到,绝大多数的嵌入式系统的硬件平台还掌握在外国公司的手中。国产的嵌入式操作系统在技术含量、兼容性、市场运作模式等方面还有很多工作要做。国家对嵌入式领域的发展也极为重视。信息产业部《2003年度电子发展基金项目指南》在软件类重点产品项目中,第五小类就是关于嵌入式软件与系统开发的,并提出要重点进行如下重点项目的研制与开发:嵌入式实时操作系统、嵌入式软件集成开发平台和嵌入式数据库管理软件。由于嵌入式系统研发在国内起步比较晚,我国目前还基本处于实验室阶段。但是嵌入式操作系统的巨大的商业价值和Linux的开放性,为民族软件产业的发展提供了难得的机会。在跟踪国外嵌入式操作系统最新技术的同时,国内厂商要坚持自主产权,力争找到自己的突破点,探索出一条适合中国国情的嵌入式操作系统的发展道路。
在redhat linux 9上交叉编译sqlite-3.3.6 for arm-linux-gcc on S3C2410b ARM9 board
嵌入式数据库简介----SQLite
1
嵌入式数据库简介----SQLite
2
纲要
SQLite介绍
SQLite的发展
SQLite的优势
SQLite的缺憾
SQLite的内部结构
SQLite数据库的使用
SQLite的命令行接口
SQLite命令行使用
SQLite数据库的编程接口
C/C++接口
简单应用
自定义简单函数
自定义聚合函数
自定义排序函数
PERL接口
SQLite数据库在ASTRAL中的应用
SQLite在多级关联中的应用
SQLite在IPIS中的应用
SQLite在web中的应用
3
SQLite介绍
SQLite的发展
2000年由D.Richard Hipp开始开发
2001年发布2.0v
2004年发布3.0v(采用了不同的数据文件格式以及编程接口)
目标
易于管理、操作、维护、自定义以及提供易用的编程接口
4
SQLite介绍
SQLite的优势
内存占用量小
比MySQL(2倍), PostgreSQL(20倍)快
ACID兼容(原子性,一致性,独立性,可持久性),支持视图,子查询,触发器
单个库文件中包含数据库引擎与接口,且其运行不依赖其它库
可以将数据放进单个文件
为C/C++, Perl,PHP等应用提供了接口
免费
允许为SQL命令集动态添加自定义函数(简单函数及聚集函数),而无需重编SQLite库
5
SQLite介绍
SQLite的 缺点
事务处理并发性
SQLite通过数据库级上的独占性和共享锁来实现独立事务处理,这意味着当多个进程或线程在同一时间可以从数据库读取数据,但是只能有一个可以同时写入,在写入之前,必须获得独占锁,其它的读操作不允许发生。
性能
在创建索引( CREATE INDEX)和删除表( DELTE TABLE)时明显比其它数据库慢
用户管理/安全
数据库的访问是基于操作系统对文件的控制来控制的,不能通过用户来区分数据库中的不同数据库.
举例,将数据库文件去写权限,然后向其中插入或删除数据条目,将提示写失败。但是不能通过数据库本身的来对权限进行设置。
在网上已经有一些SQLite的安全问题的解决方案,但大多数是商业化的,有些提供在整个数据库上的加密,有些提供在数据级别的加密。比如secure SQLite之类。
6
SQLite的内部结构
在内部,SQLite 由以下几个组件组成:SQL 编译器、内核、后端以及附件。SQLite 通过利用虚拟机和虚拟数据库引擎(VDBE),使调试、修改和扩展 SQLite 的内核变得更加方便。所有 SQL 语句都被编译成易读的、可以在 SQLite 虚拟机中执行的程序集。
7
SQLite数据库的使用
SQLite命令行接口
SQLite除库本身外,还包含命令行接口,可以在$SQLITE_HOME/bin下发现sqlite/sqlite3,
命令行功能介绍
运行方式:sqlite DBFile
得到提示符sqlite>
运行.help(注意sqlite命令行提供的命令都以”.”开头,可以看到sqlite命令行接口提供下面的功能.
8
SQLite命令行功能简介
SQLite命令行功能简介
DML/DDL语句的使用和以前一致,不做介绍
.databases 列出数据库文件名
.tables ?PATTERN? 列出?PATTERN?匹配的表名
.import FILE TABLE 将文件中的数据导入的文件中
.dump ?TABLE? 生成形成数据库表的SQL脚本
.output FILENAME 将输出导入到指定的文件中
.output stdout 将输出打印到屏幕
.mode MODE ?TABLE? 设置数据输出模式(csv,html,tcl…
.nullvalue STRING 用指定的串代替输出的NULL串
.read FILENAME 执行指定文件中的SQL语句
.schema ?TABLE? 打印创建数据库表的SQL语句
.separator STRING 用指定的字符串代替字段分隔符
.show 打印所有SQLite环境变量的设置
.quit 退出命令行接口
9
SQLite命令行功能使用
下面举例说明SQLITE命令行的常规使用:
SQLite数据导入
创建数据文件
这个文件可能来自其它的其它程序的输出之类,现只我们手功创建下面的数据文件data.txt(用逗号分隔):
id, name,gender, age
1,dq,male,24
2,jz,female,27
3.pp,male,26
4,cj,male,28
5,zc,male,25
创建数据库表
五种数据类型
TEXT,NUMERIC,INTEGER,REAL,NONE
数据类型的转换
向保存的目标类型转换,如将text保存到integer,则试着将文件转为数字(int或float),如果转换失败,则做为文件保存.
数据库表创建
shell> sqlite3 test.db
sqlite> create table employee( id integer primary key, name text, gender text, age integer );
10
SQLite命令行功能使用
数据导入
sqlite>.import data.txt employee
sqlite提示:data.txt line1:expected 4 coloumns of data but found 1;
从经验应该能看出是字符分隔符有问题,先来看看系统用什么样的提示符:
.show之后可以看到 separator: “|”,也就是说系统默认的分隔符为”|”面不是”,”,下面修改分隔字符:
sqlite>.separator “,”
sqlite>.import data.txt employee
sqlite> select * from employee where id > 2;
sqlite> select * from employee where name > 9999999999999;
上面这句用来说明text>integer(这个和比较字符的内码得到的结果是相同的)
数据比较
NULL数字之间用数学比较方法比较
TEXT/BLOB用memcpy()进行比较
比较方法是可以自已定义或者重载的(我们将在后面提及中文字串的比较)
Ex: sqlite> select id >2, name > ‘dong’, gender=‘male’ from employee;
0,1,1
0,1,0
1,1,1
1,0,1
1,1,1
SQLite3 使用教学
OS X自从10.4后把SQLite这套相当出名的数据库软件,放进了作业系统工具集里。OS X包装的是第三版的SQLite,又称SQLite3。这套软件有几个特色:
目前在OS X 10.4里,SQLite是以/usr/bin/sqlite3的形式包装,也就说这是一个命令列工具,必须先从终端机(Terminal.app或其他程序)进入shell之后才能使用。网络上有一些息协助使用SQLite的视觉化工具,但似乎都没有像CocoaMySQL(配合MySQL数据库使用)那般好用。或许随时有惊喜也未可知,以下仅介绍命令列的操作方式。
SQLite顾名思议是以SQL为基础的数据库软件,SQL是一套强大的数据库语言,主要概念是由「数据库」、「资料表」(table)、「查询指令」(queries)等单元组成的「关联性数据库」(进一步的概念可参考网络上各种关于SQL及关联性数据库的文件)。因为SQL的查询功能强大,语法一致而入门容易,因此成为现今主流数据库的标准语言(微软、Oracle等大厂的数据库软件都提供SQL语法的查询及操作)。
以下我们就建立数据库、建立资料表及索引、新增资料、查询资料、更改资料、移除资料、sqlite3命令列选项等几个项目做简单的介绍。
目录
|
用sqlite3建立数据库的方法很简单,只要在shell下键入(以下$符号为shell提示号,请勿键入):
$ sqlite3 foo.db
如果目录下没有foo.db,sqlite3就会建立这个数据库。sqlite3并没有强制数据库档名要怎么取,因此如果你喜欢,也可以取个例如foo.icannameitwhateverilike的档名。
进入了sqlite3之后,会看到以下文字:
SQLite version 3.1.3 Enter ".help" for instructions sqlite>
这时如果使用.help可以取得求助,.quit则是离开(请注意:不是quit)
所以的SQL指令都是以分号(;)结尾的。如果遇到两个减号(--)则代表注解,sqlite3会略过去。
假设我们要建一个名叫film的资料表,只要键入以下指令就可以了:
create table film(title, length, year, starring);
这样我们就建立了一个名叫film的资料表,里面有name、length、year、starring四个字段。
这个create table指令的语法为:
create table table_name(field1, field2, field3, ...);
table_name是资料表的名称,fieldx则是字段的名字。sqlite3与许多SQL数据库软件不同的是,它不在乎字段属于哪一种资料型态:sqlite3的字段可以储存任何东西:文字、数字、大量文字(blub),它会在适时自动转换。
如果资料表有相当多的资料,我们便会建立索引来加快速度。好比说:
create index film_title_index on film(title);
意思是针对film资料表的name字段,建立一个名叫film_name_index的索引。这个指令的语法为
create index index_name on table_name(field_to_be_indexed);
一旦建立了索引,sqlite3会在针对该字段作查询时,自动使用该索引。这一切的操作都是在幕后自动发生的,无须使用者特别指令。
接下来我们要加入资料了,加入的方法为使用insert into指令,语法为:
insert into table_name values(data1, data2, data3, ...);
例如我们可以加入
insert into film values ('Silence of the Lambs, The', 118, 1991, 'Jodie Foster');
insert into film values ('Contact', 153, 1997, 'Jodie Foster');
insert into film values ('Crouching Tiger, Hidden Dragon', 120, 2000, 'Yun-Fat Chow');
insert into film values ('Hours, The', 114, 2002, 'Nicole Kidman');
如果该字段没有资料,我们可以填NULL。
讲到这里,我们终于要开始介绍SQL最强大的select指令了。我们首先简单介绍select的基本句型:
select columns from table_name where expression;
最常见的用法,当然是倒出所有数据库的内容:
select * from film;
如果资料太多了,我们或许会想限制笔数:
select * from film limit 10;
或是照着电影年份来排列:
select * from film order by year limit 10;
或是年份比较近的电影先列出来:
select * from film order by year desc limit 10;
或是我们只想看电影名称跟年份:
select title, year from film order by year desc limit 10;
查所有茱蒂佛斯特演过的电影:
select * from film where starring='Jodie Foster';
查所有演员名字开头叫茱蒂的电影('%' 符号便是 SQL 的万用字符):
select * from film where starring like 'Jodie%';
查所有演员名字以茱蒂开头、年份晚于1985年、年份晚的优先列出、最多十笔,只列出电影名称和年份:
select title, year from film where starring like 'Jodie%' and year >= 1985 order by year desc limit 10;
有时候我们只想知道数据库一共有多少笔资料:
select count(*) from film;
有时候我们只想知道1985年以后的电影有几部:
select count(*) from film where year >= 1985;
(进一步的各种组合,要去看SQL专书,不过你大概已经知道SQL为什么这么流行了:这种语言允许你将各种查询条件组合在一起──而我们还没提到「跨数据库的联合查询」呢!)
了解select的用法非常重要,因为要在sqlite更改或删除一笔资料,也是靠同样的语法。
例如有一笔资料的名字打错了:
update film set starring='Jodie Foster' where starring='Jodee Foster';
就会把主角字段里,被打成'Jodee Foster'的那笔(或多笔)资料,改回成Jodie Foster。
delete from film where year < 1970;
就会删除所有年代早于1970年(不含)的电影了。
sqlite可以在shell底下直接执行命令:
sqlite3 film.db "select * from film;"
输出 HTML 表格:
sqlite3 -html film.db "select * from film;"
将数据库「倒出来」:
sqlite3 film.db ".dump" > output.sql
利用输出的资料,建立一个一模一样的数据库(加上以上指令,就是标准的SQL数据库备份了):
sqlite3 film.db < output.sql
在大量插入资料时,你可能会需要先打这个指令:
begin;
插入完资料后要记得打这个指令,资料才会写进数据库中:
commit;
以上我们介绍了SQLite这套数据库系统的用法。事实上OS X也有诸于SQLiteManagerX这类的图形接口程序,可以便利数据库的操作。不过万变不离其宗,了解SQL指令操作,SQLite与其各家变种就很容易上手了。
至于为什么要写这篇教学呢?除了因为OS X Tiger大量使用SQLite之外(例如:Safari的RSS reader,就是把文章存在SQLite数据库里!你可以开开看~/Library/Syndication/Database3这个档案,看看里面有什么料),OpenVanilla从0.7.2开始,也引进了以SQLite为基础的词汇管理工具,以及全字库的注音输入法。因为使用SQLite,这两个模块不管数据库内有多少笔资料,都可以做到「瞬间启动」以及相当快速的查询回应。
将一套方便好用的数据库软件包进OS X中,当然也算是Apple相当相当聪明的选择。再勤劳一点的朋友也许已经开始想拿SQLite来记录各种东西(像我们其中就有一人写了个程序,自动记录电池状态,写进SQLite数据库中再做统计......)了。想像空间可说相当宽广。
目前支援SQLite的程序语言,你能想到的大概都有了。这套数据库2005年还赢得了美国O'Reilly Open Source Conference的最佳开放源代码软件奖,奖评是「有什么东西能让Perl, Python, PHP, Ruby语言团结一致地支援的?就是SQLite」。由此可见SQLite的地位了。而SQLite程序非常小,更是少数打 "gcc -o sqlite3 *",不需任何特殊设定就能跨平台编译的程序。小而省,小而美,SQLite连网站都不多赘言,直指SQL语法精要及API使用方法,原作者大概也可以算是某种程序设计之道(Tao of Programming)里所说的至人了。
开放源码嵌入式数据库 SQLite 简介
自几十年前出现的商业应用程序以来,数据库就成为软件应用程序的主要组成部分。正与数据库管理系统非常关键一样,它们也变得非常庞大,并占用了相当多的系统资源,增加了管理的复杂性。随着软件应用程序逐渐模块模块化,一种新型数据库会比大型复杂的传统数据库管理系统更适应。嵌入式数据库直接在应用程序进程中运行,提供了零配置(zero-configuration)运行模式,并且资源占用非常少。本文将介绍流行的 SQLite 数据库引擎,并描述如何在应用程序开发中使用它。
SQLite 是 D. Richard Hipp 用 C 语言编写的开源嵌入式数据库引擎。它是完全独立的,不具有外部依赖性。它是作为 PHP V4.3 中的一个选项引入的,构建在 PHP V5 中。SQLite 支持多数 SQL92 标准,可以在所有主要的操作系统上运行,并且支持大多数计算机语言。SQLite 还非常健壮。其创建者保守地估计 SQLite 可以处理每天负担多达 100,00 次点击率的 Web 站点,并且 SQLite 有时候可以处理 10 倍于上述数字的负载。
SQLite 对 SQL92 标准的支持包括索引、限制、触发和查看。SQLite 不支持外键限制,但支持原子的、一致的、独立和持久 (ACID) 的事务(后面会提供有关 ACID 的更多信息)。
这意味着事务是原子的,因为它们要么完全执行,要么根本不执行。事务也是一致的,因为在不一致的状态中,该数据库从未被保留。事务还是独立的,所以,如果在同一时间在同一数据库上有两个执行操作的事务,那么这两个事务是互不干扰的。而且事务是持久性的,所以,该数据库能够在崩溃和断电时幸免于难,不会丢失数据或损坏。
SQLite 通过数据库级上的独占性和共享锁定来实现独立事务处理。这意味着当多个进程和线程可以在同一时间从同一数据库读取数据,但只有一个可以写入数据。在某个进程或线程向数据库执行写入操作之前,必须获得独占锁定。在发出独占锁定后,其他的读或写操作将不会再发生。
SQLite 网站上记录了完整的 SQLite locking semantics。
在内部,SQLite 由以下几个组件组成:SQL 编译器、内核、后端以及附件。SQLite 通过利用虚拟机和虚拟数据库引擎(VDBE),使调试、修改和扩展 SQLite 的内核变得更加方便。所有 SQL 语句都被编译成易读的、可以在 SQLite 虚拟机中执行的程序集。
SQLite 支持大小高达 2 TB 的数据库,每个数据库完全存储在单个磁盘文件中。这些磁盘文件可以在不同字节顺序的计算机之间移动。这些数据以 B+树(B+tree)数据结构的形式存储在磁盘上。SQLite 根据该文件系统获得其数据库权限。
SQLite 不支持静态数据类型,而是使用列关系。这意味着它的数据类型不具有表列属性,而具有数据本身的属性。当某个值插入数据库时,SQLite 将检查它的类型。如果该类型与关联的列不匹配,则 SQLite 会尝试将该值转换成列类型。如果不能转换,则该值将作为其本身具有的类型存储。
SQLite 支持 NULL、INTEGER、REAL、TEXT 和 BLOB 数据类型。
SQLite 附带一个可下载的 command-line interface for database administration。通过数据库名称可以调用此命令行程序,并且可以按照下面的方式创建新的数据库和表:
C:minblogg>sqlite3 c:minbloggwwwdbalf.db
SQLite version 3.2.1
Enter ".help" for instructions
sqlite> create table mytable(name varchar(40), age smallint);
sqlite> insert into mytable values('Nils-Erik',23);
sqlite> select * from mytable;
Nils-Erik|23
sqlite> |
然后,可以再次打开该数据库,列出它的表和架构,并继续进行插入和删除值的操作。
C:minblogg>sqlite3 c:minbloggwwwdbalf.db SQLite version 3.2.1 Enter ".help" for instructions sqlite> .tables mytable sqlite> select * from mytable; Nils-Erik|23 sqlite> .schema CREATE TABLE mytable(name varchar(40), age smallint); sqlite> |
SQLite 还附带命令行数据库分析器,该分析器允许您显示关于任何 SQLite 数据库当前状态的详细信息。
C:minblogg>sqlite3_analyzer wwwdbalf.db Analyzing table mytable... Analyzing table sqlite_master... /** Disk-Space Utilization Report For wwwdbalf.db *** As of 2005-Apr-24 18:56:40 Page size in bytes.................... 1024 Pages in the whole file (measured).... 2 Pages in the whole file (calculated).. 2 Pages that store data................. 2 100.0% Pages on the freelist (per header).... 0 0.0% Pages on the freelist (calculated).... 0 0.0% Pages of auto-vacuum overhead......... 0 0.0% Number of tables in the database...... 2 Number of indices..................... 0 Number of named indices............... 0 Automatically generated indices....... 0 Size of the file in bytes............. 2048 Bytes of user payload stored.......... 13 0.63% *** Page counts for all tables with their indices ******************** MYTABLE............................... 1 50.0% SQLITE_MASTER......................... 1 50.0% *** All tables ******************************************************* Percentage of total database.......... 100.0% Number of entries..................... 2 Bytes of storage consumed............. 2048 Bytes of payload...................... 91 4.4% Average payload per entry............. 45.50 Average unused bytes per entry........ 916.50 Maximum payload per entry............. 78 Entries that use overflow............. 0 0.0% Primary pages used.................... 2 Overflow pages used................... 0 Total pages used...................... 2 Unused bytes on primary pages......... 1833 89.5% Unused bytes on overflow pages........ 0 Unused bytes on all pages............. 1833 89.5% *** Table MYTABLE **************************************************** Percentage of total database.......... 50.0% Number of entries..................... 1 Bytes of storage consumed............. 1024 Bytes of payload...................... 13 1.3% Average payload per entry............. 13.00 Average unused bytes per entry........ 999.00 Maximum payload per entry............. 13 Entries that use overflow............. 0 0.0% Primary pages used.................... 1 Overflow pages used................... 0 Total pages used...................... 1 Unused bytes on primary pages......... 999 97.6% Unused bytes on overflow pages........ 0 Unused bytes on all pages............. 999 97.6% |
由于完全能够使用命令行界面来管理数据库,因此它可以为数据库管理员带来很大的方便。目前有许多优秀的基于 Web 的 SQLite 数据库管理系统。其中有一个是基于 PHP 的 SQLiteManager。
备份 SQLite 数据库有两种方法。如果数据库正在使用中,则应从命令行界面使用 .dump 命令。这样可以创建一个包含必要命令和数据的文件,从而重新创建数据库。.dump 命令也可以用于备份数据库表。
sqlite> .dump
BEGIN TRANSACTION;
CREATE TABLE mytable(name varchar(40), age smallint);
INSERT INTO "mytable" VALUES('Nils-Erik', 23);
COMMIT;
sqlite>
|
如果数据库没有处于使用状态,则可以直接将数据库文件复制到安全位置。
一个好的做法是将 SQLite 数据库与 PHP 代码分开。完成此操作的一个简便方法是创建一个 www 目录。在此目录中,创建一个用于存放 SQLite 数据库的 db 目录、一个用于存放数据库和表创建脚本的 dbscripts 目录和一个用于存放数据库备份的 backups 目录。
2004-12-06 15:43 DIR . 2004-12-06 15:43 DIR .. 2005-04-23 19:55 DIR db 2005-01-02 11:46 DIR dbscripts 2005-01-02 11:46 DIR backups |
在 PHP V5 中创建 SQLite 数据库与在命令行界面中创建该数据库非常相似。如果该数据库不存在,则创建一个空数据库。
$db = sqlite_open('../db/ac.db'); |
创建一个表也非常容易:
$db = sqlite_open('../db/ac.db');
sqlite_query($db, 'DROP TABLE post');
sqlite_query($db, 'CREATE TABLE post (id INTEGER PRIMARY KEY, kategori VARCHAR(20) NOT NULL,
titel VARCHAR(75) NOT NULL, referens VARCHAR(75), status VARCHAR(20) not null,
date varchar(10)not null, synopsis VARCHAR(120), inlaegg varchar(8192))');
|
插入一条记录:
$sqldb = sqlite_open("../db/ac.db");
sqlite_query($sqldb, "INSERT INTO isvs VALUES ('$isvurl' , '$isvname', '$comment')");
|
并选择数据:
$sqldb = sqlite_open("www/db/ac.db");
$results = sqlite_query($sqldb, "SELECT * FROM isvs order by isvurl asc ");
while (list($isvurl, $isvname) = sqlite_fetch_array($results)) {
sqlite_close($sqldb);
|
|

VC++使用事务来写SQLite3数据库
如果使用VC++使用事务来写SQLite3数据库,首先要先能写库,这篇文章分为两部分,第一部分是怎么来写SQLite3数据库,第二部分怎么使用事务来写库。
第一部分写SQLite3数据库
SQLite官方下载只给了我们一个SQLite3.Dll跟一个SQLite3.def文件,并没有提供用于VC++6使用的Lib文件,因此大家可能要麻烦一点,有两种选择,一种使用Dll动态加载来使用里面的函数,但是这很麻烦,而且如果在程序执行的时候,理论上是有些慢的(因为它每次都要LoadLibrary跟GetProcAddress),如果把所有的函数在程序加载的时候,然后执行的时候就很快了,我推荐大家使用这种方式,但是没有LIB文件怎么办?那咱们就自己做呗~~ ^_^ (我会介绍大部分流行编程语言跟SQLite的接口方法)
<1> VC++ : 启动一个命令行,别说你不会,进入VC的安装目录,我的目录是D:Microsoft Visual StudioVC98Bin 在这个目录下面有一个LIB.exe文件,对,使用它咱们就能制作出咱们需要的SQLite3.lib文件,将咱们在SQLite官方下载的SQLite3.def文件放到相同目录,或者绝对路径也可以, 然后在命令行输入如下命令。
D:Microsoft Visual StudioVC98Bin>LIB /MACHINE:IX86 /DEF:sqlite.def
这样我们就得到了一个SQLite3.lib文件,赶快放到工程链接里面。Project->Settings 在Link选项卡找到Object/library modules : 在最后填入SQLite3.lib 。如果原来就有链接,请使用空格分隔。
将sqlite3.h sqlite3.lib sqlite3.dll文件复制到我们的工程目录,在我们需要写库的CPP文件顶部,填入:#include “sqlite3.h” 然后咱们就可以调用sqlite3.dll里面的所有函数了,比每次都要定义函数原型方便吧?^_^
//Open Sqlite3 database
void OpenSqlite()
{
sqlite3 *sdb;
char buffer[MAX_PATH];
::GetCurrentDirectory(MAX_PATH,buffer);
strcat(buffer,"sqlite.db");
if(SQLITE_OK!=sqlite3_open(buffer,&sdb))
{
::MessageBox(NULL,sqlite3_errmsg(sdb),NULL,MB_OK | MB_ICONERROR);
sqlite3_close(sdb);
exit(1);
return;
}
db=sdb;
}
//Execute SQL statement
void execSQL(char* sql)
{
char* zErrMsg = 0;
if(SQLITE_OK!=sqlite3_exec(db,sql,0,0,&zErrMsg))
{
::MessageBox(NULL,sqlite3_errmsg(db),NULL,MB_OK | MB_ICONERROR);
sqlite3_close(db);
return;
}
}
程序N简单,不多赘述。之后,我们每次调用这两个函数即可写库了。
<2> Borland C++ Builder 6使用Lib文件跟VC++6完全不同,大家千万别拿来乱用,咱们也可以使用它系统本身提供的一个命令行工具生成一个SQLite3.lib文件,具体的方法如下:
在BCB (Borland C++ Builder 6) 的安装目录找到implib.exe,复制出来或者在它本目录都无所谓,但是必须将SQLite3.dll复制到相同目录下,然后启动一个命令行,(我把他放到了D:/下面)
D:>implib.exe sqlite3.dll sqlite3.lib
这样就生成了我们需要的专用于BCB的sqlite3.lib文件,怎么使用在这里省略,大家自己研究吧。^_^
<3>
我这里使用的是sqlitesimpledelphi 这是一个完全免费的包装类,大家可以到它的官方网站下载,http://www.itwriting.com/sqlitesimple.php
下来以后是一个压缩包里面是一个demo。咱们需要里面的两个文件。SQLite3.pas 跟 SQLiteTable3.pas ,把他们复制到工程目录下面,然后在需要操作库的单元文件里面 uses SQLiteTable3 ,然后大家请查看官方的事例代码:
procedure TForm1.btnTestClick(Sender: TObject);
var
slDBpath: string;
sldb: TSQLiteDatabase;
sltb: TSQLIteTable;
sSQL: String;
Notes: String;
begin
slDBPath := ExtractFilepath(application.exename)
+ 'test.db';
sldb := TSQLiteDatabase.Create(slDBPath);
try
if sldb.TableExists('testTable') then begin
sSQL := 'DROP TABLE testtable';
sldb.execsql(sSQL);
end;
sSQL := 'CREATE TABLE testtable ([ID] INTEGER PRIMARY KEY,[OtherID] INTEGER NULL,';
sSQL := sSQL + '[Name] VARCHAR (255),[Number] FLOAT, [notes] BLOB, [picture] BLOB COLLATE NOCASE);';
sldb.execsql(sSQL);
sldb.execsql('CREATE INDEX TestTableName ON [testtable]([Name]);');
//begin a transaction
sldb.BeginTransaction;
sSQL := 'INSERT INTO testtable(Name,OtherID,Number,Notes) VALUES ("Some Name",4,587.6594,"Here are some notes");';
//do the insert
sldb.ExecSQL(sSQL);
sSQL := 'INSERT INTO testtable(Name,OtherID,Number,Notes) VALUES ("Another Name",12,4758.3265,"More notes");';
//do the insert
sldb.ExecSQL(sSQL);
//end the transaction
sldb.Commit;
//query the data
sltb := slDb.GetTable('SELECT * FROM testtable');
try
if sltb.Count > 0 then
begin
//display first row
ebName.Text := sltb.FieldAsString(sltb.FieldIndex['Name']);
ebID.Text := inttostr(sltb.FieldAsInteger(sltb.FieldIndex['ID']));
ebNumber.Text := floattostr( sltb.FieldAsDouble(sltb.FieldIndex['Number']));
Notes := sltb.FieldAsBlobText(sltb.FieldIndex['Notes']);
memNotes.Text := notes;
end;
finally
sltb.Free;
end;
finally
sldb.Free;
end;
end;
这样操作起来是不是很简单,^_^
<4> PHP调用SQLite3 ,这个不用我多废话,请参看我写的《通过Apache + PHP5 + PDO 连接 SQLite3 数据库》一文。
<5>Visual Basic , 因为我没有仔细研究,我无发言权,请参看SQLite官方的资料
http://www.sqlite.org/cvstrac/wiki?p=SqliteWrappers
COM Wrappers / Visual Basic DLLs部分
<6>JAVA 请参看http://www.loveunix.net/bbs/index.php?showtopic=35051 帖子,讲述的比较详细。
第二部分 使用事务写库
事务的具体理论概念我不想多说了,如果不明白的请大家到GOOGLE搜索关键字“数据库事务”,能找到N多文章资料。我们使用VC++来做事例。还有使用到上面的两个函数
void WriteSqlite()
{
try
{
OpenSqlite();
execSQL("begin transaction;"); 开始一个事务,记录操作
char strSQL[MAX_PATH];
sprintf(strSQL,"INSERT INTO Table_Name (nAction1, nAction2, nAction3) VALUES(%d,%d,%d)", szVar1,szVar2, szVar3,);
execSQL(strSQL);
execSQL("commit transaction;"); 执行提交之前数据库自动保存用户对数据库操作序列。
return;
}
catch(...)
{
execSQL("rollback transaction;");
}
//Add custom code
}
本文至此而完,上述代码均在 Windows2000 Pro + VC++6 英文企业版本 + Delphi7 英文企业版 编译通过.
SQLite3.3.6在ARM2410上的移植
要将SQLite3.3.6移植到ARM2410开发板上,除了要有底层操作系统的支持外,还必须要有相应的交叉编译工具链。由于ARM2410开发板采用的是ARM-Linux作为底层操作系统,因此需要首先安装ARM-Linux工具链。
1.交叉编译环境建立:
拷贝cross-2.95.3.tar.bz2到/usr/local目录下并解压缩。
cp cross-2.95.3.tar.bz2 /usr/local/arm
tar –jxvf cross-2.95.3.tar.bz2
2.编译SQLite-3.3.6
(1)在/root下建立目录sqlite,拷贝sqlite-3.3.6.tar.gz到该目录同时解压缩。
tar –zxvf sqlite-3.3.6.tar.gz
(2)新建目录:
cd /sqlite-3.3.6
mkdir build
(3)修改配置文件
vi configure
修改以下几个部分:
# if
test "$cross_compiling" = "yes"; then
# { { echo "$as_me:$LINENO: error: unable to find a compiler for building build tools" >&5
#echo "$as_me: error: unable to find a compiler for building build tools" >&2;}
# { (exit 1); exit 1; }; }
# fi
#else
# test "$cross_compiling" = yes &&
# { { echo "$as_me:$LINENO: error: cannot check for file existence when cross compiling" >&5
#echo "$as_me: error: cannot check for file existence when cross compiling" >&2;}
# { (exit 1); exit 1; }; }
#else
# test "$cross_compiling" = yes &&
# { { echo "$as_me:$LINENO: error: cannot check for file existence when cross compiling" >&5
#echo "$as_me: error: cannot check for file existence when cross compiling" >&2;}
# { (exit 1); exit 1; }; }
进入目录build:
cd build
../ configure --disable-tcl --host=arm-linux
这样在build目录中就将生成Makefile和一个libtool脚本
(4)修改Makefile文件:
cd build
vi Makefile
将下面的这行
BCC = arm-linux-gcc -g -O2
改成:
BCC = gcc -g -O2
将下面的这行:
sqlite3$(TEXE): $(TOP)/src/shell.c .libs/libsqlite3.la sqlite3.h
改成:
sqlite3$(TEXE): $(TOP)/src/shell.c .libs/libsqlite3.a sqlite3.h
我们都是将sqlite放到arm-linux的硬件板子上运行,所以我们一般将其编译成静态链接的形式。
保存Makefile文件后退出。
(5)编译:
执行make命令即可完成编译。
编译完成后,在build目录下生成许多.o和.lo文件。但最重要的时文件sqlite3。
file sqlite3
sqlite3: ELF 32-bit LSB executable, ARM, version 1 (ARM), for GNU/Linux 2.0.0, dynamically linked (uses shared libs), not stripped
由此可知,此时生成的sqlite文件是还未strip过的。执行命令arm-linux-strip, 去掉其中的调试信息,这样文件将减少很多。
arm-linux-strip sqlite3
再次用file命令查看sqlite3的信息:
file sqlite3
sqlite3: ELF 32-bit LSB executable, ARM, version 1 (ARM), for GNU/Linux 2.0.0, dynamically linked (uses shared libs), stripped
这就是在开发板上可以直接运行的可执行文件。
通过nfs将这些文件下载到开发板上。
需要注意:
拷贝是需要加上 –arf选项,因为libsqlite3.so,libsqlite3.so.0是链接到libsqlite3.so.0.8.6的。
cp –arf libsqlite3.so libsqlite3.so.0. libsqlite3.so.0.8.6 /usr/lib
cp sqlite3 /mnt/nfs
(6)测试结果:
chmod 777 sqlite3
编辑测试程序:test.c
编译:arm-linux-gcc test.c -L.libs -lsqlite3 –static
arm-linux-strip a.out
将其下载到开发板上:
执行:a.out ex "select * from tbl"
轻松制作优盘linux
科技进步到今天,软驱开始慢慢退出市场,取而代之的是优盘。优盘和软盘相比,不但在容量上要大很多,而且在速度上也快了许多。以往建在软盘上的Linux由于受限于1.4MB的容量,所以多数被用来做小型路由器、防火墙之类的应用。而今天有了几十MB、几百MB甚至上GB的优盘后,就完全可以提供小型的HTTP、邮件等需要更大存储空间的服务了。想想看用户只要把这个特制的优盘插入电脑的USB接口,就可以启动一个小型的Linux系统。这个系统已经有了路由、HTTP和邮件等功能,只需快速设置一下IP等参数就可以开始工作了。这就相当于用户随身带着一台Linux服务器,方便极了。
要制作优盘Linux,首先在硬件上要满足以下条件:
1.优盘带有启动功能;
2.主板支持USB设备启动(如USBZip启动)。
有了合适的硬件条件后,用户就可以开始动手制作优盘Linux了。以下所有工作都在Linux环境下完成,笔者的软硬件环境如下:
软件环境:Federa Linux 1.0,系统采用GRUB做引导程序;
硬件环境:P42.0GHz、主板Intel 845、内存512MB、两个80GB硬盘(hda、hdb)和带启动功能的128MB优盘。
下面就开始介绍优盘Linux的制作过程。
分区和格式化
首先,将已经设置为带启动功能的优盘插入USB接口,使用下面的命令对其进行分区和格式化:
# fdisk /dev/sda
其中/dev/sda设备指用户的优盘。如果用户的机器中还有其它的SCSI设备,那么可能是/dev/sdb等。用户使用“fdisk”删除优盘原来的所有分区,并将128MB的空间都分成一个Linux基本分区。接下来用户要将其格式化为Ext2文件系统,可以使用以下命令:
# mkfs.ext2 /dev/sda1
安装GRUB
有了已经格式化好的Ext2的文件系统,接下来用户就可以在这个文件系统上安装Linux的引导程序GRUB了。
首先,要将格式化好的优盘上的文件系统挂载到当前的Linux系统中。命令如下:
# mount /dev/sda1 /usb
接着,要建立GRUB所需要的目录,并将当前使用的Linux系统中的GRUB相关文件(/boot/grub/目录下的stage1和stage2)复制到优盘的/usb/boot/grub下。命令如下:
# mkdir /usb # mkdir /usb/boot # mkdir /usb/boot/grub # cp /boot/grub/stage* /usb/boot/grub/
然后,使用“grub”命令将GRUB引导程序安装在优盘上。具体可以参考如下:
grub> root (hd2,0) Filesystem type is ext2fs, partition type 0x83 grub> setup (hd2) Checking if “/boot/grub/stage1” exists... yes Checking if“/boot/grub/stage2”exists... yes Checking if“/boot/grub/e2fs_stage1_5”exists... no Running“install /boot/grub/stage1 (hd2) /boot/ grub/stage2 p /boot/grub/grub.conf”... succeeded Done. grub>quit
上面操作中的像“(hd2)”这样的参数可能随用户机器的硬盘数量和分布情况的差异会有所不同。在安装完GRUB后,还要对其进行配置。用户在优盘的/usr/boot/grub目录下创建grub.conf文件,命令如下:
# vi /usb/boot/grub/grub.conf
grub.conf文件的内容如下:
default=0 timeout=10 title My USB Linux root (hd0,0) kernel /vmlinuz
保存退出,GRUB配置完成。此时可以测试用优盘启动机器,会看到GRUB的启动画面和选项“My USB Linux”了。
注意在测试优盘启动时,将两个硬盘的电源线都要拔掉,因为在上面的grub.conf中设定的根分区是hd0。
编译内核
首先用户要根据用途考虑使用哪一个版本的内核。比如要使用Iptables来建立防火墙,那就一定要选择2.4以上的版本。但要记住,版本并不是越高越好。选定内核的版本后,从kernel.org上下载对应的源码。在动手配置内核之前,还要对内核的源码做一个小小的修改。从内核的源码树中找到init目录下的do_mounts.c文件,请根据自己的源码版本做类似下面的修改:
* Allow the user to distinguish between failed open * and bad superblock on root device. */ - printk (“VFS: Cannot open root device“%s”or %s”, + printk (“VFS: Cannot open root device“%s”or %s”, retrying in 1 second.", root_device_name, kdevname (ROOT_DEV)); - printk (“Please append a correct “root=”boot option”); - panic(“VFS: Unable to mount root fs on %s”, - kdevname(ROOT_DEV)); + printk (“You may need to append a correct“root=”boot option”); + printk (“or wait for the root device to become ready.”); + + /* wait 1 second and try again, + * allowing time for hubs/devices to become ready */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ); + goto retry; ) panic(“VFS: Unable to mount root fs on %s”, kdevname(ROOT_DEV));
做这一步修改是考虑到,通常在Linux启动过程中,内核解压运行完成后,会加载root分区,然后执行root分区上的init脚本。当用户使用硬盘时,硬盘的初始化速度很快。在内核运行完成之前,因为硬盘已经准备就绪了,所以内核可以顺利加载硬盘上的root分区。然而优盘初始化速度要比硬盘慢多了。这样就会出现内核启动完成后,因优盘还没有完成初始化而导致root分区无法被加载的现象。所以要想使用优盘启动Linux就需要延长内核加载root分区的等待时间,办法就是对系统内核初始化代码do_mounts.c作类似上面的修改。
当然还有其它的解决办法,比如在内核启动时,先将一个临时的root分区设在RAM disk上,然后等待几秒钟,在优盘差不多初始化完成后再继续启动。这种方法就要选上对“RAM disk”的支持,而且还要修改initrd.img文件等。所以除非用户想同时用RAM disk做一些其它的用途(如运行特定程序或加载其它的驱动),否则就不建议使用这种方法。
在完成源码的修改后,就可以开始配置内核了。在配置过程中将所有需要的功能和驱动都编译进内核中去。注意要将一些不必要的模块去掉,比如声卡和显卡驱动等。还要特别注意对网络方面的支持要广泛一些,要尽量将可能遇到的网卡的驱动编译进内核。
常见的USB芯片类型有EHCI、OHCI和UHCI三种。因为对于常见的PC机主板来说一般使用的是UHCI芯片的USB控制器,所以在USB设备的配置中,要对应的选上对“UHCI Alternate Driver (JE) support”的支持。另外,还要选上“Preliminary USB device filesystem”。因为在Linux中,优盘等类似设备都是被模拟成SCSI硬盘而进行访问。以下是执行“make menuconfig”命令配置内核时关于USB的选项实例,可供参考:
<*> Support for USB [ ] USB verbose debug messages --- Miscellaneous USB options [*] Preliminary USB device filesystem --- USB Host Controller Drivers <*> UHCI Alternate Driver (JE) support < > OHCI (Compaq, iMacs, OPTi, SiS, ALi, ...) support --- USB Device Class drivers <*> USB Mass Storage support [ ] USB Mass Storage verbose debug [ ] Freecom USB/ATAPI Bridge support [ ] ISD-200 USB/ATA Bridge support [ ] Microtech CompactFlash/SmartMedia support
细心完成内核配置后,最后就是编译内核了。内核的编译方法大家应该很熟悉了,通常是使用“make mrproper”、“make menuconfig”、“make dep”、“make clean”和“make bzImage”等指令来编译。
编译完成后,新的内核就是arch/i386/boot/目录中的bzImage文件,将其复制到优盘上,命名如下:
# cp arch/i386/boot/bzImage /usb/vmlinuz
编译成功的内核通常不会超过1MB。在实际制作优盘Linux的过程中,定制内核是非常关键的。很多人失败在这个地方。通常要多次尝试,才能生成适合的内核。
目录和文件
内核启动后,要加载root文件系统。在Linux中,root文件系统中包含了系统必须的目录、设备文件、可执行文件和配置文件等。
首先创建系统必需的目录,示意如下:
# mkdir bin etc etc/rc.d proc tmp var dev mnt # chmod 755 bin etc etc/rc.d tmp var dev mnt # chmod 555 proc
接着要用“mknod”命令在dev目录下建立一些基本的设备文件,不可从原系统/dev目录中复制。具体请参考下列实例命令:
# cd dev # mknod tty c 5 0 # mknod console c 5 1 # chmod 666 tty console # mknod tty0 c 4 0 # chmod 666 tty0 # mknod ram0 b 1 0 # chmod 600 ram0
然后生成一些常用命令和工具,比如sh、ls、cd和ifconfig等。用户可以使用原来系统中的这些命令,不过要对应地将其所用到的动态连接库也复制到优盘上,或者是重新进行静态编译,然后将二进制文件复制到优盘上。这些过程都不轻松,如果想研究Linux,或者是想在优盘Linux上使用Glibc的话,可以自己动手来做一下。对于大多数人,可以使用BusyBox工具箱来做这件事。BusyBox提供了一个很小的可执行程序busybox。用户通过链接它可以为系统提供ls、rm、cp和init等多种命令。busybox的安装过程很简单。在Fedora Linux中已经自带,用户也可以自行下载源码来编译安装。安装完后,可以将busybox产生的文件复制到优盘中,具体可以参考其官方网站http:// www.busybox.net上的说明。
最后,还要建立一些配置文件,比如/etc/rc.d/inittab、/etc/rc.d/rc.sysinit和/etc/fstab等。下面是这几个文件的简单实例,仅供参考:
1./etc/rc.d/inittab ::sysinit:/etc/rc.d/rc.sysinit ::askfirst:/bin/sh 2./etc/rc.d/rc.sysinit #!/bin/sh mount -a 3./etc/fstab proc /proc proc defaults 0 0
完成上面这些工作之后,就可以测试这个小型的优盘Linux了。
在启动时,先是GRUB引导,接下来就是内核解压和初始化,然后调用/sbin/init,由init来调用/etc/inittab中的各种程序运行(比如加挂各种分区等),最后在console上启动一个Shell程序,这样就完成了启动。如果在启动中遇到问题,还要反复进行修改和测试,这中间的过程就要靠自己去摸索了。
优盘Linux的应用
创建和定制完Linux系统所需的目录结构和文件之后,就已经完成了一个小型的Linux系统了。这个小型的Linux系统主要可以有以下几个方面的应用。
做为应急盘
当Linux系统故障时,可以用这个优盘启动系统,然后将原系统的硬盘挂进文件系统,进行诊断和修复。盘中可以放入一些常用的诊断工具和软件。
提供应用服务
此时这个小型的Linux系统本身已经可以提供路由和防火墙等功能了。不过用户还可以加入其它的应用服务,这要视用户自己的需要而定。比如一个Web开发人员,可以装上MySQL、Apache、PHP和CVS等服务。这些服务可以先在其它的Linux上静态编译好,然后将其复制到优盘上。
其它
优盘还可以有其它的用途,比如作为密匙盘,为Linux的启动或应用程序提供访问控制认证等。
嵌入式工具Qt的安装与使用
Qt是Trolltech公司的一个产品。Trolltech是挪威的一家软件公司,主要开发两种产品:一种是跨平台应用程序界面框架;另外一种就是提供给做嵌入式Linux开发的应用程序平台,能够应用到PDA和各种移动设备上。Qt和Qtopia分别是其中具有代表性的两个。
Qt是一个多平台的C++图形用户界面应用程序框架,它能给用户提供精美的图形用户界面所需要的所有元素,而且它是基于一种面向对象的思想,所以用户对其对象的扩展是相当容易的,并且它还支持真正的组件编程。
Qt是Linux桌面环境KDE的基础。笔者认为,可以说Qt与Windows下的Mfc的实质是一样的,所以Qt最大的优点在于其跨平台性,可以支持现有的多种操作系统平台,主要有:
◆ MS/Windows 95、Windows 98、WindowsNT 4.0、Windows 2000、Windows XP;
◆ Unix/X11 Linux、Sun Solaris、HP-UX、Compaq True64Unix、IBM AIX、SGI IRIX和很多其它X11平台;
◆ Macintoshi Mac OSX;
◆ Embedded—带FramBuffer的Linux平台。
下面简单介绍一下Qt/Embedded和Qtopia在Linux上的安装和使用,还有在开发过程中可能碰到的一些问题。
Qt 和Qtopia的安装
如果需要安装一个带FramBuffer的Qtopia平台,需要有以下软件(所列举软件以笔者使用的为例):
◆ Qtopia 1.6.0;
◆ Tmake 1.11;
◆ Qt/Embedded 2.3.4(Qtopia 1.6.0是基于该开发平台上开发的);
◆ Qt/Embedded 2.3.2 for X11;
◆ Qt 3.1.2 for X11。
在Trolltech公司的网站上可以下载该公司所提供的Qt/Embedded的免费版本。
Qtopia平台安装分为以下几个步骤:
1. 解包Qtopia
在Linux命令模式下运行以下命令:
tar xfz qtopia-source-1.6.0 (解包)
cd qtopia-source-1.6.0
export QPEDIR=$PWD (设置环境变量)
cd..
2. 安装Tmake
在Linux命令模式下运行以下命令:
tar xfz tmake-1.11.tar.gz
export TMAKEDIR=$PWD/tmake-1.11
export TMAKEPATH=$TMAKEDIR/lib/qws/linux-x86-g++
export PATH=$TMAKEDIR/bin:$PATH
tar xfz qt-embedded-2.3.4-commercial.tar.gz cd qt-2.3.4 export QTDIR=$PWD export QTEDIR=$QTDIR export PATH=$QTDIR/bin:$PATH export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH cp $QPEDIR/src/qt/qconfig-qpe.h src/tools/ . /configure -qconfig qpe -qvfb -depths 4,8,16,32 make sub-src cd .. |
tar xfz qt-x11-2.3.2-commercial.tar.gz cd qt-2.3.2 export QTDIR=$PWD export PATH=$QTDIR/bin:$PATH export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH . /configure -no-opengl make make -C tools/qvfb mv tools/qvfb/qvfb bin cp bin/uic $QTEDIR/bin cd .. |
tar xfz qt-x11-commercial-3.1.x.tar.gz cd qt-x11-commercial-3.1.x export QTDIR=$PWD export QT3DIR=$QTDIR export PATH=$QTDIR/bin:$PATH export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH ./configure -thread make cd .. |
cd qtopia-source-1.6.x export QTDIR=$QTEDIR export QPEDIR=$PWD export PATH=$QPEDIR/bin:$PATH cd src ./configure make cd ../.. |
cd qtopia-source-1.6.x/src export QTDIR=$QT3DIR ./configure -qtopiadesktop make mv qtopiadesktop/bin/qtopiadesktop ../bin cd .. |
export QTDIR=$QTEDIR, qpe &; |
cd qt-2.3.2/bin ./designer |

./uic -o test.h test.ui ./uic -o test.h -i test.cpp test.ui |
TEMPLATE = app CONFIG = qtopia warn_on release MOC_DIR =tmp OBJECTS_DIR =tmp HEADERS =fcrs.h structs.h globalfunc.h globalvars.h testimpl.h SOURCES = main.cpp globalfunc.cpp globalvars.cpp testimpl.cpp INTERFACES = test.ui TARGET = fcrs |
tmake -o Makefile test.pro |
qtopia for arm and x86 编译过程描述
交叉编译所用到的文件:
qt-embedded-2.3.10-free.tar.gz
qt-x11-2.3.2.tar.gz
qtopia-free-source-2.1.1.tar.gz
tmake-1.13.tar.gz
e2fsprogs-1.35.tar.gz
qtopia for x86的编译步骤:
tar xfz qt-embedded-2.3.7.tar.gz
export QTEDIR=$PWD
tar xfz qt-x11-2.3.2.tar.gz
export QT2DIR=$PWD
tar xfz qtopia-free-1.7.0.tar.gz
export QPEDIR=$PWD
tar xfz tmake-1.13.tar.gz
export TMAKEDIR=$PWD
export TMAKEPATH=$PWD/tmake-1.13/lib/qws/linux-x86-g++
export PATH=$TMAKEDIR/bin:$PATH
cd qt-2.3.2
export QTDIR=$QT2DIR
export PATH=$QTDIR/bin:$PATH
export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
./configure -no-xft
make
make -C tools/qvfb
cd ..
cd qt-2.3.7
export QTDIR=$PWD
export PATH=$QTDIR/bin:$PATH
export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
cp $QT2DIR/bin/uic bin
cp $QT2DIR/tools/qvfb/qvfb bin
cp $QPEDIR/src/qt/qconfig-qpe.h src/tools/
./configure -qconfig qpe -qvfb -thread -system-jpeg -gif -depths 4,8,16,32
make
cd ..
tar xzf e2fsprogs-1.35.tar.gz
cd e2fsprogs-1.35
./configure -enable-elf-shlibs
make install
注:这是编译x86的libuuid库
cd ..
cd qtopia-free-1.7.0
export PATH=$QPEDIR/bin:$PATH
export LD_LIBRARY_PATH=$QPEDIR/lib:$LD_LIBRARY_PATH
cd src
./configure
make
以上步骤按照qtopia的doc里步骤就可以了。
安装结束后,可以打开一个qvfb,看看qpe的效果。
qtopia for arm的编译步骤:
tar xfz qt-embedded-2.3.7.tar.gz
export QTEDIR=$PWD
tar xfz qt-x11-2.3.2.tar.gz
export QT2DIR=$PWD
tar xfz qtopia-free1.7.0.tar.gz
export QPEDIR=$PWD
tar xfz tmake-1.13.tar.gz
export TMAKEDIR=$PWD
export TMAKEPATH=$PWD/tmake-1.13/lib/qws/linux-arm-g++
export PATH=$TMAKEDIR/bin:$PATH
cd qt-2.3.2
export QTDIR=$QT2DIR
export PATH=$QTDIR/bin:$PATH
export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
./configure -no-xft
make
make -C tools/qvfb
cd ..
cd qt-2.3.7
export QTDIR=$PWD
export PATH=$QTDIR/bin:$PATH
export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
cp $QT2DIR/bin/uic bin
cp $QT2DIR/tools/qvfb/qvfb bin
cp $QPEDIR/src/qt/qconfig-qpe.h src/tools/
./configure -xplatform linux-arm-g++ -qconfig qpe -qvfb -thread -system-jpeg -gif -depths 4,8,16,32
make
cd ..
注:这里交叉编译arm版本的Qt/E,需要arm版本的libjpeg.so.62
tar xzf e2fsprogs-1.35.tar.gz
cd e2fsprogs-1.35
./configure -host=arm-linux -with-cc=arm-linux-gcc -with-linker=arm-linux-ld -enable-elf-shlibs -prefix=/usr/local/arm/2.95.3/arm-linux
make install
//这步安装到/usr/local/arm/2.95.3/arm-linux/lib上的libuuid.so.1.2版本不对,要执行
cp lib/libuuid.so.1.2 ../arm-linux/lib
cd ..
cd qtopia-free-1.7.0
export PATH=$QPEDIR/bin:$PATH
export LD_LIBRARY_PATH=$QPEDIR/lib:$LD_LIBRARY_PATH
cd src
./configure -xplatform linux-arm-g++
make
基于LSB的信息隐藏实现
LSB就是最不重要位信息隐藏算法,读取出图片中每个点的象素值,然后把信息的bit序列填充到这些象素的最低上.然后在分析检测的时候提取出最低位,就可以恢复原始信息.因为是在最低位上进行隐藏,所以图像的失真是比较小的,肉眼无法察觉,但是它会破坏图像的统计特性,这样通过统计的方法可以检测到有信息的嵌入.此外在图片有损压缩时,隐藏的信息容易丢失.如果嵌入高位,会带来失真,但是嵌入强度高,可以抵御有损压缩.
/*
位操作的类,自己写的.
*
// BitOperate.cpp: implementation of the BitOperate class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "BitOperate.h"
#include "iostream.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
BitOperate::BitOperate()
{
}
BitOperate::~BitOperate()
{
}
/*
功能:把ch从右数的第position位设置为value;
*/
byte BitOperate::bitSet(byte ch, short position, bool value)
{
byte temp = ch;
if(position>8||position<1)
{
cout<<"Out of Bound! Position must be a number between 1 --- 8 "<<endl;
return ch;
}
bool bit = bitAt(temp,position);
if(bit^value)//如果不相同的话
{
if(bit == 0)
{
byte m = (byte)value;
for(int i = 1;i<position;i++)
m = m<<1;
ch+=m;
return ch;
}
else
{
byte m = (byte)bit;
for(int i = 1;i<position;i++)
m = m<<1;
ch-=m;
return ch;
}
}
else return ch;
}
/*
功能:返回ch从右数第i位的值;
*/
bool BitOperate::bitAt(byte ch, short position)
{
byte temp = ch;
if(position>8||position<1)
{
cout<<"Out of Bound! Position must be a number between 1 --- 8 "<<endl;
return false;
}
for(int j = 1;j<position;j++)
temp = temp>>1;
if(temp%2==0)return 0;
else return 1;
}
// HideInFo.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "iostream.h"
#include "stdio.h"
#include "BitOperate.h"
#include "stdlib.h"
#include "ctype.h"
#include"conio.h"
#include <string.h>
#define MAX 1000
typedef unsigned char byte;
/*1. BMP文件头
BMP文件头数据结构含有BMP文件的类型、
文件大小和位图起始位置等信息。其结构定义如下:
*/
typedef struct tagBitMapFileHeader
{
byte bfType[2]; // 位图文件的类型,必须为"BM"
byte bfSize[4]; // 位图文件的大小,以字节为单位
byte bfReserved1[2]; // 位图文件保留字,必须为0
byte bfReserved2[2]; // 位图文件保留字,必须为0
byte bfOffBits[4]; // 位图数据的起始位置,以相对于位图文件头的偏移量表示,以字节为单位
} *BitMap;//该结构占据14个字节。
/*功能:返回图像的长度*/
int getValue(byte*A,int num)
{
int result = 0;
for(int i = num-1;i>0;i--)
{
result+=A[i];
result = result<<8;
}
result+=A[0];
return result;
}
/*功能:将信息隐藏到图片中*/
void hideInFo()
{
FILE* fp;
char data[MAX];
BitOperate operate;
if((fp = fopen("H:tao.bmp", "r+"))==NULL)
{
cout<<"打开文件的时候出现错误!"<<endl;
return;
}
BitMap map = //存放文件头信息;
(BitMap)malloc(sizeof(struct tagBitMapFileHeader ));
fread(map,sizeof(struct tagBitMapFileHeader ),1,fp);
printf("Input the data you want to hide:n");
gets(data);
int datalength = strlen(data);
int maplength = getValue(map->bfSize,4);
int databegin = getValue(map->bfOffBits,4);
if((datalength*8)>(maplength-databegin))
{
printf("The data is too long!Choose a bigger picture or hide it in pieces!n");
return;
}
printf("#######Datalength = %dn",datalength);
printf("#######Malength = %dn",maplength);
printf("DataBegin = %dn",databegin);
//首先将文件的长度隐藏起来;
byte temp[32];
fseek(fp,databegin,SEEK_SET);//定位到数据块的起点;
fread(temp,sizeof(char)*32,1,fp);
int copy = datalength;
for(int i = 31;i>=0;i--)
{
bool v = (bool)(copy%2);
temp[i] = operate.bitSet(temp[i],1,v);
copy = copy/2;
}
fseek(fp,databegin,SEEK_SET);//定位到数据块的起点;
fwrite(temp,sizeof(byte)*32,1,fp);
//以下将数据信息隐藏到图像中;
int times = 0;
byte ch[8];
while(times<datalength)//加上隐藏的文件长度信息;
{
fseek(fp,databegin+32+times*8,SEEK_SET);//定位到数据块的起点;
fread(ch,sizeof(byte),8,fp);//读出8个字节;
for(int i = 7;i>=0;i--)
ch[i] = operate.bitSet(ch[i],1,operate.bitAt(data[times],8-i));
fseek(fp,databegin+32+times*8,SEEK_SET);//定位到数据块的起点;
fwrite(ch,sizeof(byte),8,fp);//写入8个字节;
times++;
}
fclose(fp);
}
/*将图片中隐藏的信息提取出来*/
void getInFo()
{
FILE* fp;
byte data[MAX];
BitOperate operate;
if((fp = fopen("H:tao.bmp", "r+"))==NULL)
{
cout<<"打开文件的时候出现错误!"<<endl;
return;
}
BitMap map = //存放文件头信息;
(BitMap)malloc(sizeof(struct tagBitMapFileHeader ));
fread(map,sizeof(struct tagBitMapFileHeader ),1,fp);
int databegin = getValue(map->bfOffBits,4);//得到数据块开始的偏移地址;
//获取文件的长度;
int datalength = 0;
//首先将隐藏文件的长度读出来;
byte temp[32];
fseek(fp,databegin,SEEK_SET);//定位到数据块的起点;
fread(temp,sizeof(char)*32,1,fp);
for(int i = 0;i<31;i++)
{
datalength += (int)operate.bitAt(temp[i],1);
datalength = datalength<<1;
}
datalength += (int)operate.bitAt(temp[31],1);
printf("nnDatalength = %dn",datalength);
int times = 0;
byte ch[8];
while(times<datalength)//加上隐藏的文件长度信息;
{
fseek(fp,databegin+32+times*8,SEEK_SET);
fread(ch,sizeof(byte),8,fp);//读出8个字节;
//printf("nch = %sn",ch);
data[times] = 0;
for(int i = 0;i<7;i++)
{
data[times]+= (int)operate.bitAt(ch[i],1);
data[times] = data[times]*2;
}
data[times]+= (int)operate.bitAt(ch[7],1);
printf("%c",data[times]);
times++;
}
fclose(fp);
}
void main()
{
//hideInFo();
getInFo();
getch();
}
系统引导管理器GRUB
摘要: GRUB是多系统引导管理器,简单的说既能引导Linux,同时也能引导Windows;从LinuxSir.Org 讨论区近四年的观察来看,大多初学者并不能在短时间内掌握GRUB的用法,为了解决初学者在最短时间内掌握GRUB,重写GRUB入门文档还是有必要的;
本文重点介绍了GRUB的menu.lst的写法,另外通过GRUB命令行引导系统也做了详述;这些无论是对Windows版本的WINGRUB还是Linux版本的GRUB都是适用的;
目录索引
三、在Linux中,GRUB的配置中的安装和写入硬盘的MBR;
八、关于GRUB的未尽事宜;
九、关于本文;
十、参考文档;
十一、相关文档;
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
正文
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
一、什么是多重操作系统引导管理器,什么是GRUB;
1、什么是多重操作系统引导管理器及工作原理;
系统启动引导管理器,是在计算机启动后运行的第一个程序,他是用来负责加载、传输控制到操作系统的内核,一旦把内核挂载,系统引导管理器的任务就算完成退出,系统引导的其它部份,比如系统的初始化及启动过程则完全由内核来控制完成;
Briefly, boot loader is the first software program that runs when a computer starts. It is responsible for loading and transferring control to the operating system kernel software (such as the Hurd or the Linux). The kernel, in turn, initializes the rest of the operating system (e.g. GNU).
在X86 架构的机器中,Linux、BSD 或其它Unix类的操作系统中GRUB、LILO 是大家最为常用,应该说是主流;
Windows也有类似的工具NTLOADER;比如我们在机器中安装了Windows 98后,我们再安装一个Windows XP ,在机器启动的会有一个菜单让我们选择进入是进入Windows 98 还是进入Windows XP。NTLOADER就是一个多系统启动引导管理器,NTLOADER 同样也能引导Linux,只是极为麻烦罢了;
在Powerpc 架构的机器中,如果安装了Linux的Powerpc 版本,大多是用yaboot 多重引导管理器,比如Apple机目前用的是IBM Powerpc处理器,所以在如果想在Apple机上,安装Macos 和Linux Powerpc 版本,大多是用yaboot来引导多个操作系统;
因为目前X86架构的机器仍是主流, 所以目前GRUB和LILO 仍然是我们最常用的多重操作系统引导管理器;
2、什么是GRUB;为什么我要选择GRUB;
1)什么是GRUB;
GNU GRUB 是一个多重操作系统启动管理器。GNU GRUB 是由GRUB(GRand Unified Bootloader) 派生而来。GRUB 最初由Erich Stefan Boleyn 设计和应用;
GNU GRUB is a Multiboot boot loader. It was derived from GRUB, GRand Unified Bootloader, which was originally designed and implemented by Erich Stefan Boleyn.
2)“GRUB太不好用”──对GRUB的认识的误区;
GRUB真的不好用吗?不是的,通过LinuxSir.Org 社区近四年来的运行,我发现了大多新手弟兄还是不太了解GRUB;当然这也有中文Linux社区的责任,虽然也有GRUB的中文译本,初学Linux的弟兄可能有点看不懂;
我们欣喜的看到LinuxSir.Org 社区的好多弟兄都曾经或正在写GRUB实践文档,也有的弟兄也总结了GRUB的一些基础知识,比如 probing兄弟的 《GRUB 学习笔记》;由于每个人的写文档时风格不同,可能同一份文档不同的人来写就有不同的风格;所以今天也抖胆也一篇入门级的教程,由于北南不会写高级教程,所以还得请高手弟兄指教,先谢过;
3)为什么要选择GRUB;
基于在X86架构的CPU而开发操作系统,系统引导管理器不仅仅有GRUB ,而且也有LILO,但对于多重系统引导管理器,你只能选择其一而用;不能两个同时使用;
目前这两个多重系统引导管理器是大家最常用的,也是主流Linux发行版而采用的;有的弟兄喜欢GRUB,比如我个人,有的弟兄喜欢LILO ,比如etony兄(谁是etony,请参见 http://debian.linuxsir.org/ );
主流发行版 Fedora、Redhat、Centos等基于RPM包的系统,在最新版本中都默认GRUB引导;Slackware 目前仍采用LILO;而Debian发行版目前最新的版本也是采用GRUB;
从目前看来,GRUB有逐渐取代LILO之势,GRUB 2.0正在开发之中;所以我们有理由用GRUB,我也有理由写GRUB使用教程;
二、GRUB软件包版本选择和安装;
1、GRUB的版本选择,Linux版本的GRUB及Windows版本的GRUB的说明;
GRUB不但有Linux版本,也有Windows版本;现我们一一介绍;
如前面所说,目前在在Unix类的操作系统中,大多是都有GRUB;GRUB几乎能引导所有X86架构的操作系统;功能之强,使用简单是GRUB最大的卖点;由于Windows 操作系统的先入为主的优势,使得大家对Windows的NTLOADER了解的比较多,而对开源社区的GRUB显得有点寞生,由此而带来使用上的“心理恐惧”;究其初学者对GRUB“恐惧”的主要原因还是对GRUB没有太多的了解和深入;无论是WINGRUB还是Linux版本的GRUB,最方便的还是对GRUB命令行的操作;一谈到命令行(Command)的操作,可能初学者对此恐惧;其实没有什么难的,象北南这样低级的写手,还能操作得起来,您也应该能行;
2、GRUB的Windows版本WINGRUB;
请参考:《以WINGRUB 引导安装Fedora 4.0 为例,详述用WINGRUB来引导Linux的安装》
3、GRUB的Linux版本软件包的安装;
其实对于Linux的GRUB,几乎所有的Linux主流发行版都有打包,如果您安装了Linux,并且在开机后出现GRUB字样的,证明您已经安装了GRUB;而无需再次安装;Linux的GRUB软件包安装部份并不是本文的重点;
如果您的Linux系统没有安装GRUB,或者采用的是LILO,而您想用GRUB,可以用系统安装盘自带GRUB软件包来安装,或者到相关发行版本的软件仓库下载后安装;
GRUB 的Linux版本目前在各大发行版中都有打包;比如Fedora/Redhat/Centos/Mandrive/Mandriva/SuSE等以RPM包管理机制的系统,可以通过如下的命令来安装;
[root@localhost ~]# rpm -ivh grub*.rpm如果是Slackware 您可以用如下的办法来安装;
[root@localhost ~]# installpkg grub*.tgz其它的发行版本请用其自己特色的软件包管理工具来安装;
当然您也可以通过源码包,在任何Linux的发行版上安装;至于源码包的安装方法;
请参考:《如何编译安装源码包软件》
[root@localhost ~]#tar zxvf grub*.tar.gz
[root@localhost ~]#cd grub-xxx
[root@localhost ~]#./configure;make;make install确认您是否成功安装了GRUB,您可以测试是否有如下两个命令;
[root@localhost ~]# grub
[root@localhost ~]# grub-install如果您不能找到这两个命令,可能您的可执行程序的路径没有设置;
请参考:《设置可执行程序路径》,当然您可以用绝对路径;比如下面的;
[root@localhost ~]# /usr/sbin/grub
[root@localhost ~]# /usr/sbin/grub-install如果您还是找不到GRUB软件包安装在哪了;您可以用下面的命令来解决和查找;
[root@localhost ~]# updatedb 注:这个要花很长时间;是索引slocate 的库,然后再通过locate来查找;
[root@localhost ~]# locate grub比如找到的是有类似如下的;
[root@localhost ~]# locate grub
/sbin/grub-md5-crypt
/sbin/grub
/sbin/grub-install
/sbin/grub-terminfo在一般情况下,在路径中带有bin或sbin中字样的,这些路径下都是可执行程序;sbin 是超级权限用户才能使用的管理命令;要使用这些命令一般的情况下得切换到root用户下才能使用;比如
[beinan@localhost ~]$ su - 注:切换到root用户,并且切换到其家目录;
Password:
[root@localhost ~]#/sbin/grub 注:用绝对路径来运行grub命令;
三、在Linux中,GRUB的配置中的安装和写入硬盘的MBR;
1、在Linux中,GRUB配置过程中的安装grub-install;
grub-install 命令有何用呢?其实就是把我们前面已经安装的软件包中的一些文件复制到 /boot/grub中;对于新安装GRUB软件包后,也是一个必经的过程;我们前面所说的GRUB软件包的安装;而现在我们说的是GRUB配置的过程中的安装;虽然在洋文中都是install ,但表达的意思是不一样的;
我们首先要运行 fdisk -l 来确认到底是硬盘的标识;
这个过程主要是确认硬盘的标识是哪个调备,到底是/dev/hda还是/dev/hdb 还是其它的;
[root@localhost ~]# fdisk -l
Disk /dev/hda: 80.0 GB, 80026361856 bytes
255 heads, 63 sectors/track, 9729 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Device Boot Start End Blocks Id System
/dev/hda1 * 1 970 7791493+ 7 HPFS/NTFS
/dev/hda2 971 9729 70356667+ 5 Extended
/dev/hda5 971 2915 15623181 b W95 FAT32
/dev/hda6 2916 4131 9767488+ 83 Linux
/dev/hda7 4132 5590 11719386 83 Linux
/dev/hda8 5591 6806 9767488+ 83 Linux
/dev/hda9 6807 9657 22900626 83 Linux
/dev/hda10 9658 9729 578308+ 82 Linux swap / Solaris如果通过fdisk -l 出现有/dev/hda字样的,我们就要用下面的命令来安装;
[root@localhost ~]# grub-install /dev/hda
Installation finished. No error reported.
This is the contents of the device map /boot/grub/device.map.
Check if this is correct or not. If any of the lines is incorrect,
fix it and re-run the script `grub-install'.
(fd0) /dev/fd0
(hd0) /dev/hda如果是您fdisk -l 出现的有/dev/hdb呢,那就如下运行;
[root@localhost ~]# grub-install /dev/hdb如果既有/dev/hda和/dev/hdb 就安装到/dev/hda中;
[root@localhost ~]# grub-install /dev/hda值得注意的是如果您有一个/boot分区,应该用如下的办法来安装;
[root@localhost ~]#grub-install --root-directory=/boot /dev/hda
[root@localhost ~]#grub-install --root-directory=/boot /dev/hdb注解:具体是/dev/hda还是/dev/hdb,请以fdisk -l 为准;如果两个都有,就看您把/boot分区是放在第一块硬盘还是第二块硬盘上了,以实际情况为准;
2、设定GRUB的/boot分区并写入MBR;;
在Linux中,GRUB软件包的安装,及在配置过程中安装grub到 /boot中还是不够的, 还要把GRUB,写入MBR才行;有时我们重新安装了Windows,Windows会把MBR 重写,这样GRUB就消失了;如果您出现这样的情况,就要进行这个过程;
[root@localhost ~]# grub会出现grub>提示符,这是grub命令行模式 ,如果能在开机中出现提示符,没有引导不起来的系统,除非您的系统破坏的极为严重。如果仅仅是GRUB被破坏了,GRUB命令行是能让操作系统引导起来的;
接着看例子,我们要找到 /boot/grub/stage1的,在grub>后面输入;
grub> find /boot/grub/stage1
(hd0,6)
(fd0) 注:这个是软驱;现在很少用软驱了,如果您有这方面的需要,自己看GRUB的DOC吧;注解:
(hd0,6) 这是/boot所在的分区;不要误解为是Linux 的/所在的分区,这是值得注意的;
(fd0) 注:这个是软驱;现在很少用软驱了,如果您有这方面的需要,自己看GRUB的DOC吧;
grub>root (hd0,6) 注:这是/boot所在的分区;
grub>setup (hd0) 注:把GRUB写到MBR上;注解:
上面这步骤是根据 find /boot/stage1而来的,仔细看一下就明白了;现在我们一般安装很少会把/boot分区列为一个单独的分区;不过有的弟兄可能也喜欢这么做;所以还是有必要说一下为好;
四、GRUB的配置文件的menu.lst的写法;
对于GRUB来说,如果没有配置menu.lst,无论是Linux版本的GRUB,还是WINGRUB,都会有命令行可用,通过命令行是一样能把操作系统引导起来的;有些弟兄总以为menu.lst 配置错了, 或者在机器启动后出现grub>命令行模式就要重新安装系统,其实根本没有这个必要;只要学会GRUB的命令行的用法,根本没有必要重装系统;
menu.lst 位于/boot/grub目录中,也就是/boot/grub/menu.lst 文件;您可以用vi或您喜欢的编辑器来编辑他;如果您不会用vi,还是去学习一下吧;简单的用法怎么也得会,对不对?毕竟这个文档不是讲vi的用法的;
有的弟兄会说,我没有menu.lst怎么办?那就创建一个;用下面的命令;
[root@localhost ~]# touch /boot/grub/menu.lst
然后我们再做一个/boot/grub/menu.lst 的链接 /boot/grub/grub.conf
[root@localhost ~]# cd /boot/grub
[root@localhost ~]# ln -s menu.lst grub.conf现在我们来写GRUB的menu.lst了,因为/boot/grub/grub.conf是 /boot/grub/menu.lst的链接文件,改哪个都行。链接文件相当于Windows的快捷方式,这样可能能更好的理解;
1、menu.lst的写法之一;
首先我们看一下我的Fedora 4.0 中的/boot/grub/menu.lst 的内容;
default=0
timeout=5
#splashimage=(hd0,6)/boot/grub/splash.xpm.gz
hiddenmenu
title Fedora Core (2.6.11-1.1369_FC4)
root (hd0,6)
kernel /boot/vmlinuz-2.6.11-1.1369_FC4 ro root=LABEL=/
initrd /boot/initrd-2.6.11-1.1369_FC4.img
title WinXp
rootnoverify (hd0,0)
chainloader +1注解:
default=0
default=0 是默认启动哪个系统,从0开始;每个操作系统的启动的定义都从title开始的,第一个title 在GRUB的启动菜单上显示为0,第二个启动为1,以此类推;
timeout=5
注:表示在开机后,GRUB画面出现几秒后开始以默认启动;如果在启动时,移动上下键,则解除这一规则;
#splashimage=(hd0,6)/boot/grub/splash.xpm.gz 注:GRUB的背景画面,这个是可选项;我不喜欢GRUB的背景画面,所以加#号注掉,也可以删除;
hiddenmenu注解:隐藏GRUB的启动菜单,这项也是可选的,也可以用#号注掉;
一般的情况下对Linux操作系统的启动,一般要包括四行;title 行;root行;kernel 行;initrd 行;
1)在menu.lst中 ,通过 root (hd[0-n],y)来指定/boot 所在的分区;
title XXXXX 注:title 后面加一个空格,title 是小写的,后面可以自己定义;比如FC4,自己定义一个名字就行;
root (hd[0-n],y) ,在本例中,我们看到的是root (hd0,6) ,root (hd[0-n],y)表示的是/boot所在的分区;有时我们安装Linux的时候,大多是不设置/boot的,这时/boot和/所在的同一个分区; 这个root (hd[0-n],y)很重要,因为/boot目录中虽然有grub目录,最为重要的是还有kernel 和initrd文件,这是Linux能启动起来最为重要东西;
有的弟兄会问,root (hd[0-n],y)是怎么来的?
请参考:《在Linux系统中存储设备的两种表示方法》
2)在menu.lst中,kernel 命令行的写法;
kernel 一行,是通指定内核及Linux的/分区所在位置;
比如例子中是;
kernel /boot/vmlinuz-2.6.11-1.1369_FC4 ro root=LABEL=/
在这里以kernel 起始,指定Linux的内核的文件所处的绝对路径;因为内核是处在/boot目录中的, 如果/boot是独立的一个分区,则需要把boot省略;如果/boot是独立的分区,这行要写成:
kernel /vmlinuz-2.6.11-1.1369_FC4 ro root=LABEL=/ 因为/boot所处的分区已经在title 下一行root (hd[0-n],y)中指定了,所以就无需要再指明内核处在哪个分区了;另外Linux系统的硬盘分区的挂载配置文件在/etc/fstab ,原理是通过 mount /dev/hd[a-z]X /boot 来进行的;您可以对照着来理解;
ro 表示只读; root=LABEL=/ 来表示Linux的根所处的分区。LABEL=/ 这是硬盘分区格式化为相应文件系统后所加的标签;如果您不了解什么是标签,也可以直接以/dev/hd[a-z]X 或者/dev/sd[a-z]X来表示;就看您的Linux是根分区是在哪个分区了。比如我的是在/dev/hda7 , 那这里就可以写成root=/dev/hda7;
如果查看系统运行所挂载的分区,请用 df -lh 来查看,就能明白是不是/boot是独立的分区,或者查看/etc/fstab也能知道;
[root@localhost ~]# df -lh
Filesystem 容量 已用 可用 已用% 挂载点
/dev/hda7 11G 9.2G 1.2G 90% /
/dev/shm 236M 0 236M 0% /dev/shm在这个例子中,我们可以发现 /boot并没有出现只有/dev/hda7,这表示/boot并不是独立的一个分区;所有的东西都包含在/中;于是我们在/boot中查看内核版本;
[root@localhost ~]# ls /boot/vmlinuz*
/boot/vmlinuz-2.6.11-1.1369_FC4 注:看到内核vmlinuz所处的目录;于是我们就可以这样kernel 这行了;
kernel /boot/vmlinuz-2.6.11-1.1369_FC4 ro root=/dev/hda7
3)initrd 命令行的写法;
如果是/boot独立一个分区,initrd 一行要把/boot中省略;如果/boot不是处于一个分区,而是和Linux的/分区处于同一分区,不应该省略;
比如我们在2)中用的例子;现在拿到这里,我们应该首先查看 /boot中的initrd的文件名到底是什么;
[root@localhost ~]# ls /boot/initrd*
/boot/initrd-2.6.11-1.1369_FC4.img如果是通过df -lh 得知或查看/etc/fstab 也行, 得知/boot是独立的分区;这时initrd 应该写成;
initrd /initrd-2.6.11-1.1369_FC4.img如果是 /boot不是独处一个分区,而是在/同一处一个分区, 则要写成;
initrd /boot/initrd-2.6.11-1.1369_FC4.img
4)menu.lst第一种写法的总结和实践;
在这里,我们只说重要的,不重要的就一带而过了;
1]用fdisk -l ;df -lh ;more /etc/fstab来确认分区情况;
我们过fdisk -l ;df -lh ; more /etc/fstab 来确认/boot所在的分区,及Linux的根分区所在位置;
比如我们确认/boot和Linux的/分区同处一个分区;
[root@localhost ~]# df -lh
Filesystem 容量 已用 可用 已用% 挂载点
/dev/hda7 11G 9.2G 1.2G 90% /
/dev/shm 236M 0 236M 0% /dev/shm然后我们/etc/fstab 中,查看/分所在的分区或分区标签是什么;
[root@localhost ~]# more /etc/fstab
# This file is edited by fstab-sync - see 'man fstab-sync' for details
LABEL=/ / ext3 defaults 1 1
/dev/devpts /dev/pts devpts gid=5,mode=620 0 0
/dev/shm /dev/shm tmpfs defaults 0 0
/dev/proc /proc proc defaults 0 0
/dev/sys /sys sysfs defaults 0 0
LABEL=SWAP-hda1 swap swap defaults 0 0
/dev/hdc /media/cdrecorder auto pamconsole,exec,noauto,managed 0 0经过上面的df -lh 和more /etc/fstab 的对照中得知,/boot并是独处一个分区,而是和/在同一个分区;这个Linux系统安装在/dev/hda7上,文件系统(此分区)的标签为LABEL=/ ,/boot也是处于/dev/hda7 ,/dev/hda7也可以说是 root (hd0,6);
2]查看内核vmlinuz的和initrd文件名的全称;
[root@localhost ~]# ls -lh /boot/vmlinuz*
-rw-r--r-- 1 root root 1.6M 2005-06-03 /boot/vmlinuz-2.6.11-1.1369_FC4
[root@localhost ~]# ls -lh /boot/initrd*
-rw-r--r-- 1 root root 1.1M 11月 26 22:30 /boot/initrd-2.6.11-1.1369_FC4.img
3]开始写menu.lst ;
我们根据上面所提到的,可以写成如下的样子;
default=0
timeout=5
title FC4
root (hd0,6)
kernel /boot/vmlinuz-2.6.11-1.1369_FC4 ro root=LABEL=/
initrd /boot/initrd-2.6.11-1.1369_FC4.img也可以写成;
default=0
timeout=5
title FC4
root (hd0,6)
kernel /boot/vmlinuz-2.6.11-1.1369_FC4 ro root=/dev/hda7
initrd /boot/initrd-2.6.11-1.1369_FC4.img注解:上面两个不同之处在于一指定Linux的根/所在的分区时,一个是用了文件系统的标签,另一个没有用标签;
2、menu.lst的写法之二,精简型;
本写法主要是把指定/boot所位于的所分区直接写入kernel 指令行;这样就省略了通过root (hd[0-n],y)来指定/boot所位于的分区;
1)第一种情况:/boot和Linux的/根分区在同一个分区;
有前面的那么多的讲解,menu.lst写法之二就好理解多了;也得分两种情况,咱们先把/boot并不是独处一个分区,而是和Linux的根分区处于同一个分区;我们以 4)menu.lst第一种方法的写法总结 的实例为例子;
default=0
timeout=5
title FC4x
kernel (hd0,6)/boot/vmlinuz-2.6.11-1.1369_FC4 ro root=/dev/hda7
initrd (hd0,6)/boot/initrd-2.6.11-1.1369_FC4.img注解:
title FC4x 注:自己为这个Linux 起个简单的名,以title开头,然后一个空格,后面就自己发挥吧,FC4或FC4x都行;
kernel 空格 (hd0,6)/boot/vmlinuz-2.6.11-1.1369_FC4 空格 ro 空格 root=/dev/hda7
kernel 这行这样理解 kernel (boot所在的分区)/boot/内核文件件全称 ro root=Linux根所位于的分区或标签
initrd 空格 (hd0,6)/boot/initrd-2.6.11-1.1369_FC4.img
initrd 这行可以这样理解 initrd (/boot所在的分区)/boot/内核文件名全称
2)第二种情况:/boot独立一个分区,和Linux的根分区不是同一个分区;
比如我们查看到df -lh 得到的是
[root@localhost ~]# df -lh
Filesystem 容量 已用 可用 已用% 挂载点
/dev/hda6 200M 120M 80M 60% /boot
/dev/hda7 11G 9.2G 1.2G 90% /我们再进一行查看/etc/fstab 得知;
LABEL=/ / ext3 defaults 1 1
LABEL=/boot /boot ext3 defaults 1 2所以我们应该写成如下的;
title FC4x
kernel (hd0,5)/vmlinuz-2.6.11-1.1369_FC4 ro root=LABEL=/
initrd (hd0,5)/initrd-2.6.11-1.1369_FC4.img因为Linux的根分区是/dev/hda7,通过/etc/fstab和df -h的内容得知标签为 LABEL=/的分区就是/dev/hda7 ,所以有;
title FC4x
kernel (hd0,5)/vmlinuz-2.6.11-1.1369_FC4 ro root=/dev/hda7
initrd (hd0,5)/initrd-2.6.11-1.1369_FC4.img
五、通过GRUB命令行来启动Linux操作系统;
GRUB的命令行才是王道,如果知道怎么用命令行来启动操作系统,那理解menu.lst的写法也不难;也就是说在开机的时候,不用GRUB的菜单,通过GRUB的命令也是一样能把操作系统引导起来。
因为menu.lst的内容就是GRUB的一个一个的指令集合;是不是Linux这玩意很神奇?
1、为什么需要学习GRUB的命令行;
当我们把GRUB的menu.lst写错的时候,或者丢掉了menu.lst的时,比如在开机的时候,GRUB会出现grub>类似的命令提示符,这时需要我们用命令行启动系统;当然您可以不用定义GRUB的菜单 ,直接用命令行来启动系统,比如我现在就是,为了写GRUB的文档,就把menu.lst 删除了,直接用命令来启动系统;
2、用命令行来引导Linux操作系统的步骤;
通过命令行来引导操作系统的流程,也没有什么难的;无非是把指令手工输入到grub>提示符的后面;在这个过程中,tab键的命令补齐功能就显得很重要了。如果您不知道有哪些命令,可以输入help;
1)进入GRUB的命令行模式 grub>
如果开机时,GRUB出现的是grub>,说明你没有/etc/grub/menu.lst ,您需要自己写一个才会GRUB的菜单,让我们来选择进入哪个系统。如果有GRUB的菜单,您可以按Ctrl+c组合键进入GRUB的命令行模式,会出现grub> 提示符;
grub>
2)获取帮助GRUB的 help
只要您在grub>提示符的后面输入help 就能得到GRUB所有的命令提示;
grub> help
blocklist FILE boot
cat FILE chainloader [--force] FILE
clear color NORMAL [HIGHLIGHT]
configfile FILE device DRIVE DEVICE
displayapm displaymem
find FILENAME geometry DRIVE [CYLINDER HEAD SECTOR [
halt [--no-apm] help [--all] [PATTERN ...]
hide PARTITION initrd FILE [ARG ...]
kernel [--no-mem-option] [--type=TYPE] makeactive
map TO_DRIVE FROM_DRIVE md5crypt
module FILE [ARG ...] modulenounzip FILE [ARG ...]
pager [FLAG] partnew PART TYPE START LEN
parttype PART TYPE quit
reboot root [DEVICE [HDBIAS]]
rootnoverify [DEVICE [HDBIAS]] serial [--unit=UNIT] [--port=PORT] [--
setkey [TO_KEY FROM_KEY] setup [--prefix=DIR] [--stage2=STAGE2_
terminal [--dumb] [--no-echo] [--no-ed terminfo [--name=NAME --cursor-address
testvbe MODE unhide PARTITION
uppermem KBYTES vbeprobe [MODE]如果需要得到某个指令的帮助,就在 help 后面空一格,然后输入指令,比如;
grub>help kernel
3)cat的用法;
cat指令是用来查看文件内容的,有时我们不知道Linux的/boot分区,以及/根分区所在的位置,要查看/etc/fstab的内容来得知,这时,我们就要用到cat (hd[0-n],y)/etc/fstab 来获得这些内容;注意要学会用tab键命令补齐的功能;
grub> cat ( 按tab 键会出来hd0或hd1之类的;
grub> cat (hd0, 注:输入hd0,然后再按tab键;会出来分区之类的;
grub> cat (hd0,6)
Possible partitions are:
Partition num: 0, Filesystem type unknown, partition type 0x7
Partition num: 4, Filesystem type is fat, partition type 0xb
Partition num: 5, Filesystem type is reiserfs, partition type 0x83
Partition num: 6, Filesystem type is ext2fs, partition type 0x83
Partition num: 7, Filesystem type unknown, partition type 0x83
Partition num: 8, Filesystem type is reiserfs, partition type 0x83
Partition num: 9, Filesystem type unknown, partition type 0x82
grub> cat (hd0,6)/etc/fstab 注:比如我想查看一下 (hd0,6)/etc/fstab的内容就这样输入;
LABEL=/ / ext3 defaults 1 1
/dev/devpts /dev/pts devpts gid=5,mode=620 0 0
/dev/shm /dev/shm tmpfs defaults 0 0
/dev/proc /proc proc defaults 0 0
/dev/sys /sys sysfs defaults 0 0
LABEL=SWAP-hda1 swap swap defaults 0 0
/dev/hdc /media/cdrecorder auto pamconsole,exec,noauto,
managed 0 0有的弟兄可能会说,我不知道Linux安装在了哪个分区。那根据文件系统来判断一个一个的尝试总可以吧我;只要能cat出/etc/fstab就能为以后引导带来方便;
主要查看/etc/fstab中的内容,主要是Linux的/分区及/boot是否是独立的分区;如果没有/boot类似的行,证明/boot和Linux的/处于同一个硬盘分区;比如上面的例子中LABEL=/ 这行是极为重要的;说明Linux系统就安在标签为LABEL=/的分区中;
如果您的Linux系统/boot和/没有位于同一个分区,可能cat (hd[a-n],y) 查到的是类似下面的;
LABEL=/ / ext3 defaults 1 1
LABEL=/boot /boot ext3 defaults 1 2
4) root (hd[0-n,y) 指令来指定/boot所在的分区;
其实这个root (hd[0,n],y)是可以省略的,如果省略了,我们要在kerenl 命令中指定;我们前面已经说过 (hd[0-n],y) 硬盘分区的表示方法的用途;主要是用来指定 /boot所在的分区;
比如我们确认/boot和 (hd0,6),所以就可以这样来输入root (hd0,6)
grub> root (hd0,6)如果发现不对,可以重新来过;没有什么大不了的;
5) kernel 指令,用来指定Linux的内核,及/所在的分区;
kernel 这个指令可能初学者有点怕,不知道内核在哪个分区,及内核文件名的全称是什么。不要忘记tab键的命令补齐的应用;
如果我们已经通过root (hd[0-n],y) 指定了/boot所在的分区,语法有两个:
如果/boot和Linux的/位于同一个分区,应该是下面的一种格式;
kernel /boot/vmlinuz在这里按tab键来补齐,就看到内核全称了 ro root=/dev/hd[a-z]X
如果/boot有自己独立的分区,应该是;
kernel /vmlinuz在这里按tab键来补齐,就看到内核全称了 ro root=/dev/hd[a-z]X
在这里 root=/dev/hd[a-z]X 是Linux 的/根所位于的分区,如果不知道是哪个分区,就用tab出来的来计算,一个一个的尝试;或用cat (hd[0-n],y)/etc/fstab 中得到Linux的/所在的分区或分区的标签;
grub> kernel /boot/在这里按tab键;这样就列出/boot中的文件了;
Possible files are: grub initrd-2.6.11-1.1369_FC4.img System.map-2.6.11-1.1369
_FC4 config-2.6.11-1.1369_FC4 vmlinuz-2.6.11-1.1369_FC4 grubBAK memtest86+-1.55
.1 xen-syms xen.gz
grub> kernel /boot/vmlinuz-2.6.11-1.1369_FC4 ro root=LABEL=/
[Linux-bzImage, setup=0x1e00, size=0x18e473]注解: root=LABEL=/ 是Linux的/所在的分区的文件系统的标签;如果您知道Linux的/在哪个具体的分区,用root=/dev/hd[a-z]X来指定也行。比如下面的一行也是可以的;
grub> kernel /boot/vmlinuz-2.6.11-1.1369_FC4 ro root=/dev/hda7也可以把/boot所在的分区的指定 root (hd[0-n],y)这行省掉,直接在kernel 中指定/boot所在的分区;所以就在下面的语法;
如果是/boot和Linux的根同处一个分区;
kernel (hd[0-n],y)/boot/vmlinuz ro root=/dev/hd[a-z]X比如:
grub>kernel如果是/boot和Linux所在的根不在一个分区;则是;
kernel (hd[0-n],y)/vmlinuz ro root=/dev/hd[a-z]Xgrub> kernel (hd0,6)/boot/vmlinuz-2.6.11-1.1369_FC4 ro root=/dev/hda7
[Linux-bzImage, setup=0x1e00, size=0x18e473]或下面的输入,以cat 出/etc/fstab内容为准;
grub> kernel (hd0,6)/boot/vmlinuz-2.6.11-1.1369_FC4 ro root=LABEL=/
[Linux-bzImage, setup=0x1e00, size=0x18e473]
6)initrd 命令行来指定initrd文件;
grub> initrd /boot/initrd在这里tab 来补齐;
grub> initrd /boot/initrd-2.6.11-1.1369_FC4.img
[Linux-initrd @ 0x2e1000, 0x10e685 bytes]如果/boot是独立的一个分区,应该是如下样子的语法;比如下面的;
grub> initrd /initrd在这里tab 来补齐;
grub> initrd /initrd-2.6.11-1.1369_FC4.img
[Linux-initrd @ 0x2e1000, 0x10e685 bytes]
7)boot 引导系统;
grub>boot前面的几个步骤都弄好 。就进入引导;尝试一下就知道了。。
8)引导Linux系统实例全程回放;
实例:/boot和Linux的/处于同一个硬盘分区;
grub> cat (hd0,6)/etc/fstab
# This file is edited by fstab-sync - see 'man fstab-sync' for details
LABEL=/ / ext3 defaults 1 1
/dev/devpts /dev/pts devpts gid=5,mode=620 0 0
/dev/shm /dev/shm tmpfs defaults 0 0
/dev/proc /proc proc defaults 0 0
/dev/sys /sys sysfs defaults 0 0
LABEL=SWAP-hda1 swap swap defaults 0 0
/dev/hdc /media/cdrecorder auto pamconsole,exec,noauto,managed 0 0
grub> root (hd0,6)
Filesystem type is ext2fs, partition type 0x83
grub> kernel /boot/在这里按tab补齐,全列出/boot所有的文件;
Possible files are: grub initrd-2.6.11-1.1369_FC4.img System.map-2.6.11-1.1369_FC4 config-2.6.11-1.1369_FC4 vmlinuz-2.6.11-1.1369_FC4
memtest86+-1.55.1 xen-syms xen.gz
grub> kernel /boot/vmlinuz-2.6.11-1.1369_FC4 ro root=/dev/hda7 注:输入
[Linux-bzImage, setup=0x1e00, size=0x18e473]
grub> initrd /boot/在这里按tab补齐
Possible files are: grub initrd-2.6.11-1.1369_FC4.img System.map-2.6.11-1.1369_FC4 config-2.6.11-1.1369_FC4 vmlinuz-2.6.11-1.1369_FC4
memtest86+-1.55.1 xen-syms xen.gz
grub> initrd /boot/initrd-2.6.11-1.1369_FC4.img 注;输入intrd文件名的全名;
[Linux-initrd @ 0x2e1000, 0x10e685 bytes]
grub> boot我们指定Linux的根时,可以用cat出来的fstab的内容中Linux的/分区文件系统标签来替代;也就是kernel 那行中 root=/dev/hd[a-z]X;
grub> cat (hd0,6)/etc/fstab
# This file is edited by fstab-sync - see 'man fstab-sync' for details
LABEL=/ / ext3 defaults 1 1
/dev/devpts /dev/pts devpts gid=5,mode=620 0 0
/dev/shm /dev/shm tmpfs defaults 0 0
/dev/proc /proc proc defaults 0 0
/dev/sys /sys sysfs defaults 0 0
LABEL=SWAP-hda1 swap swap defaults 0 0
/dev/hdc /media/cdrecorder auto pamconsole,exec,noauto,managed 0 0
grub> root (hd0,6)
Filesystem type is ext2fs, partition type 0x83
grub> kernel /boot/在这里按tab补齐,全列出/boot所有的文件;
Possible files are: grub initrd-2.6.11-1.1369_FC4.img System.map-2.6.11-1.1369_FC4 config-2.6.11-1.1369_FC4 vmlinuz-2.6.11-1.1369_FC4
memtest86+-1.55.1 xen-syms xen.gz
grub> kernel /boot/vmlinuz-2.6.11-1.1369_FC4 ro root=LABEL=/
[Linux-bzImage, setup=0x1e00, size=0x18e473]
grub> initrd /boot/在这里按tab补齐
Possible files are: grub initrd-2.6.11-1.1369_FC4.img System.map-2.6.11-1.1369_FC4 config-2.6.11-1.1369_FC4 vmlinuz-2.6.11-1.1369_FC4 grubBAK
memtest86+-1.55.1 xen-syms xen.gz
grub> initrd /boot/initrd-2.6.11-1.1369_FC4.img 注;输入intrd文件名的全名;
[Linux-initrd @ 0x2e1000, 0x10e685 bytes]
grub> boot如果是/boot和Linux的根分区不在同一个分区,要把kernel和initrd 指令中的/boot去掉,也就是/vmlinuzMMMMMM 或 /initrdNNNN
也可以不用root (hd[0-n]来指定/boot所在分区,要在kernel 和initrd 中指定;比如Linux的/根所位于的分区和/boot所位于的分区都是(hd0,6),并且我们cat出来的/etc/fstab是Linux的/根分区的文件系统的标签为LABEL=/,引导操作系统的例子如下;
grub>kernel (hd0,6)/boot/vmlinuz-2.6.11-1.1369_FC4 ro root=LABEL=/
grub>initrd (hd0,6)/boot/initrd-2.6.11-1.1369_FC4.img
grub>boot或
grub>kernel (hd0,6)/boot/vmlinuz-2.6.11-1.1369_FC4 ro root=/dev/hda7
grub>initrd (hd0,6)/boot/initrd-2.6.11-1.1369_FC4.img
grub>boot如果/boot位于 /dev/hda6,也就是(hd0,5),Linux的根/位于分区/dev/hda7,并且我们cat 出来的/etc/fstab 中/分区的标签为 LABEL=/。下面的两种方法都可以引导;
grub>kernel (hd0,5)/vmlinuz-2.6.11-1.1369_FC4 ro root=LABEL=/
grub>initrd (hd0,5)/initrd-2.6.11-1.1369_FC4.img
grub>boot或
grub>kernel (hd0,5)/vmlinuz-2.6.11-1.1369_FC4 ro root=/dev/hda7
grub>initrd (hd0,5)/initrd-2.6.11-1.1369_FC4.img
grub>boot
六、通过GRUB引导Windows操作系统;
1、通过编辑 menu.lst 来引导Windows 系统;
如果您的Windows所处于的分区在(hd0,0),可以在menu.lst 加如下的一段就能引导起来了;
title WinXp
rootnoverify (hd0,0)
chainloader +1如果您的机器有两块硬盘,而Windows 位于第二个硬盘的第一个分区,也就是(hd1,0)
您可以用grub的map来指令来操作把两块硬盘的序列对调,这样就不用在BIOS中设置了;在menu.lst中加如下的内容,比如下面的;
title WinXp
map (hd0) (hd1)
map (hd1) (hd0)
rootnoverify (hd0,0)
chainloader +1
makeactive如果Windows的分区不位于硬盘的第一个分区怎么办呢?比如在(hd0,2);
这个也好办吧,把rootnoverify 这行的(hd0,0)改为 (hd0,2)
title WinXp
rootnoverify (hd0,2)
chainloader +1
makeactive如果Windows的在第二个硬盘的某个分区,比如说是位于(hd1,2),则要用到map指令;
title WinXp
map (hd0) (hd1)
map (hd1) (hd0)
rootnoverify (hd1,2)
chainloader +1
makeactive如果有多个Windows 系统,怎么才能引导出来呢?应该用hide 和unhide指令操作;比如我们安装了两个Windows ,一个是位于(hd0,0)的windows 98 ,另一个是安装的是位于(hd0,1)的WindowsXP;这时我们就要用到hide指令了;
title Win98
unhide (hd0,0)
hide (hd0,1)
rootnoverify (hd0,0)
chainloader +1
makeactive
title WinXP
unhide (hd0,1)
hide (hd0,0)
rootnoverify (hd0,1)
chainloader +1
makeactive
2、通过GRUB指令来引导Windows ;
其实我们会写menu.lst了,在menu.lst中的除了title外,都是一条条指令;如果我们启动Windows ,只是输入指令就行了;
比如 Windows的分区在 (hd0,0),我们在开机后,按ctrl+c ,进入GRUB的命令模式;就可以用下面的
grub> rootnoverify (hd0,0)
grub> chainloader +1
grub> boot其它同理... ...
七、GRUB丢失或损坏的应对策略;
如果GRUB是Linux版本才出会这样的问题;WINGRUB可以不写在MBR上;所以不会出现这样的问题。WINGRUB用起来比较简单。menu.lst 和命令行的用法和Linux版本的GRUB是一样的;
1、由于重新安装Windows或其它未知原因而导致GRUB的丢失;
您可以通过系统安装盘、livecd进入修复模式;
请参考:《Linux 系统的单用户模式、修复模式、跨控制台登录在系统修复中的运用》
首先:您根据前面所说grub-install来安装GRUB到/boot所在的分区;要仔细看文档,/boot是不是处于一个独立的分区是重要的,执行的命令也不同;
其次:要执行grub ,然后通过 root (hd[0-n],y)来指定/boot所位于的分区,然后接着执行 setup (hd0),这样就写入MBR了,比如下面的例子;
grub>root (hd0,6)
grub>setup (hd0)
grub>quit
重新引导就会再次出现MBR的菜单了或命令行的提示符了;
2、如果出现GRUB提示符,而不出现GRUB的菜单,如何引导系统;
存在的问题可能是/boot/grub/menu.lst丢失,要自己写一个才行;您可以用命令行来启动系统,进入系统后写一写menu.lst就OK了。前面已经谈过了;
写好后还要建一个grub.conf的链接,如下:
[root@localhost ~]# cd /boot/grub
[root@localhost grub]# ln -s menu.lst grub.conf
八、关于GRUB的未尽事宜;
GRUB有很多内容,比如对BSD的引导,还有一些其它指令的用法,我并没有在本文提到;主要我目前还未用到,如果您需要了解更多,请查看 《GNU GRUB 手册和FAQ》
九、关于本文;
本文前后写了三四天,中间发现并不能把Linux设备的两种表现形式说的清楚,于是被迫写了《在Linux系统中存储设备的两种表示方法》;由于没有BSD系统,所以没有写关于BSD的引导;如果正在用BSD的弟兄如果有时间不妨写一写;写的时候注意文档的结构,这样方便大家的阅读;
GRUB有很多内容,需要大家慢慢的学习和研究;有的弟兄抑制GRUB,说不如NTLOADER,其实这是错误的;如果您想学习和使用Linux就得学习和适应Linux的操作;习惯成自然,如果您抵制学习Linux,那可能您永远会说“Linux不如Windows”;
十、参考文档;
十一、相关文档;
《以WINGRUB 引导安装Fedora 4.0 为例,详述用WINGRUB来引导Linux的安装》
《系统引导管理器GRUB学习笔记》
《如何为GRUB系统引导管理器加上密码》
《在Linux系统中存储设备的两种表示方法》
《合理规划您的硬盘分区》
《系统引导过程及硬盘分区结构论述》
《Linux 查看磁盘分区、文件系统、使用情况的命令和相关工具介绍》
《实例解说 fdisk 使用方法》
基于Video4Linux的USB摄像头图像采集实现
做了一段时间的摄像头图像采集,有了一些心得.在论坛上开的2410摄像头问题专贴(http://www.hhcn.com/cgi-bin/topic.cgiforum=1&topic=247&show=0)
也得到了大家的关注.在此,我将这一阶段遇到的问题,解决方法等做个总结,
希望对您有所帮助.
Linux本身自带了采用ov511芯片的摄像头,而市场上应用最广泛的是采用中
芯微公司生产的zc301芯片的摄像头,下面我将针对这两大系列的摄像头分别做
介绍.(注:所有的开发都是在华恒HHARM-2410-EDU上完成,ov511摄像头采
用的是网眼webeye3000,zc301摄像头采用的是ANC奥尼S888).
一 驱动加载
1.1 ov511驱动
1.静态加载
(1)在arm linux的kernel目录下make menuconfig.
(2)首先(*)选择Multimedia device->下的Video for linux.加载video4linux模块,
为视频采集设备提供了编程接口;
(3)然后在usb support->目录下(*)选择support for usb和usb camera ov511
support.这使得在内核中加入了对采用OV511接口芯片的USB数字摄像头的驱动
支持.
(4)保存配置退出.
(5)make dep;make zImage
此时在/tftpboot下就生成了带有ov511驱动的内核.
2.动态加载
(1)在arm linux的kernel目录下make menuconfig.
(2)首先选择Multimedia device->下的Video for linux.
(3)然后在usb support->目录下选择support for usb和选择usb camera
ov511 support.
(4)保存退出.
(5)Make dep;make zImage;make modules然后就在/driver/usb下生成ov511.o,同
时生成的zImage自动放在/tftpboot下.
(6)然后用新内核启动板子后insmod ov511.o就可以成功加载.
动态方式与静态方式相比,测试时要简单的多.不需要下载整个内核,只需
通过nfs,加载驱动即可测试.在测试成功后就可以编译进内核.
模块加载中出现的问题:
1.insmod和modprobe间的一个区别试后者不会在当前目录中查找模块,它只
在/lib/modules下的缺省目录下查找,这是因为该程序只是一个系统实用例程,
不是一个交互工具.可以通过在/etc/modules.conf中指定自己的目录,来把它们
加到缺省目录集中.
2.如果插入模块ov511.o时,出现以下信息:
Ov511.o:unresolved symbol video********之类的,说明还有其它模块videodev.o
没有加.
3.出现错误:ov511.o:couldn't find the kernel version this modules was compile
d
for.这是试图插入一个不是可装入模块的目标文件.因为在内核配置阶段,是
把ov511模块静态加到内核中的,虽然看起来和可装入模块的文件名ov511.o完
全一样,但是不能用insmod命令加入.
4.如果出现Ov511.o:unresolved symbol video********,那就选中video for
linux,用新生成的内核启动系统,再insmod videodev.o,insmod ov511.o就可以啦.
1.2 zc301驱动
摄像头的驱动是从http://mxhaard.free.fr/ϵÄÕë¶Ôembeded»·¾³有专门的patch,
我用的是usb-2.4.31LE06.patch.
(1)把它放到/HHARM9-EDU/kernel/driver/usb下,解压,打补丁.就会在此目
录下看到spca5xx文件夹了.可能会有一些错误,我的错误是在Makefile和config.in
文件中,根据它的提示,进行相应的修改即可.Patch时会将修改方法写到
Makefile.rej和config.in.rej文件中,把这两个文件里的内容加到Makefile和config.i
n
中就行了.
(2)编译内核,进入/HHARM9-EDU/kernel,make menuconfig.我采用和上面
介绍的ov511驱动的方法一样,动态加载.(M)选中SPCA5XX这一项.
(3)make dep;make zImage;make modules.就会在
/HHARM9-EDU/kernel/driver/usb/spca5xx中生成
spca5xx.o,spcadecoder.o,spca_core.o啦.这就是我们要的驱动.
(4)用新内核启动,insmod这三个.o文件(可以不用加载spcadecoder.o),摄像
头就加载成功啦.
不过这种LE的驱动有许多问题,比如运行到设置图像格式(RGB565或RGB24)时
出错,说不支持此参数.原因在于:(摘自驱动程序主页
http://mxhaard.free.fr/spca5le.html)
The spca5xx-LE design is very different from the spca5xx full package(LE版的
驱动
和完全版的差很多).
The memory in use are the most smaller as possible(LE版的驱动会尽量减少内存的
使用)
The spcadecoder is reduce and only raw jpeg webcam are used.(驱动模块只支持输
出原始jpeg格式).
还有一种方法,从http://mxhaard.free.fr/download.htmlÏÂÔØ×îеÄÇý¶¯
spca5xx-20060402.tar.gz.这个可独立编译,无需放到linux内核里面,编译生成一个spc
a5xx.o
即可,不要三个.o做驱动了.因为这个驱动是针对2.6的,编译时会出现很多错误,修改
CFLAGS即可.华恒的群里已经有编译好的驱动提供大家下载.
模块加载中出现的问题:
1.运行./servfox时出现Error Opening V4L interface.
我测试一下,是没有加载驱动.虽然内核中(M)选中了驱动,但是启动后要手
工加进去.insmod一下啦.
2.insmod spcadecoder.o时,出现错误:spcadecoder.o:couldn't find the kernel ver
sion
this modules was compiled for.如果你insmod spca5xx.o成功的话就不需要再
insmod其他模块了.
3.insmod video.o时却说can't find the kernel version the modules was compiled
for.
这是因为video for linux一般是直接编译到内核中去的.不需要加载的.
二 Video4linux编程
2.1 Video4linux简介
Video4Linux是为市场现在常见的电视捕获卡和并口及USB口的摄像头提供
统一的编程接口.同时也提供无线电通信和文字电视广播解码和垂直消隐的数据
接口.本文主要针对USB摄像头设备文件/dev/video0,进行视频图像采集方面的
程序设计.
2.2 Video4linux编程指南
1.视频编程的流程
(1)打开视频设备:
(2)读取设备信息
(3)更改设备当前设置(可以不做)
(4)进行视频采集,两种方法:
a.内存映射
b.直接从设备读取
(5)对采集的视频进行处理
(6)关闭视频设备.
定义的数据结构及使用函数
struct _v4l_struct
{
int fd;
struct video_capability capability;
struct video_buffer buffer;
struct video_window window;
struct video_channel channel[8];
struct video_picture picture;
struct video_mmap mmap;
struct video_mbuf mbuf;
unsigned char *map;
};
typedef struct _v4l_struct v4l_device;
extern int v4l_open(char *, v4l_device *);
extern int v4l_close(v4l_device *);
extern int v4l_get_capability(v4l_device *);
extern int v4l_set_norm(v4l_device *, int);
extern int v4l_get_picture(v4l_device *);
extern int v4l_grab_init(v4l_device *, int, int);
extern int v4l_grab_frame(v4l_device *, int);
extern int v4l_grab_sync(v4l_device *);
extern int v4l_mmap_init(v4l_device *);
extern int v4l_get_mbuf(v4l_device *);
extern int v4l_get_picture(v4l_device *);
extern int v4l_grab_picture(v4l_device *, unsigned int);
extern int v4l_set_buffer(v4l_device *);
extern int v4l_get_buffer(v4l_device *);
extern int v4l_switch_channel(v4l_device *, int);
3.Video4linux支持的数据结构及其用途
(1)video_capability 包含设备的基本信息(设备名称,支持的最大最小分辨
率,信号源信息等)
name[32] 设备名称
maxwidth
maxheight
minwidth
minheight
Channels 信号源个数
type 是否能capture,彩色还是黑白,是否能裁剪等等.值如
VID_TYPE_CAPTURE等
(2)video_picture 设备采集的图象的各种属性
Brightness 0~65535
hue
colour
contrast
whiteness
depth 8 16 24 32
palette VIDEO_PALETTE_RGB24 | VIDEO_PALETTE_RGB565|
VIDEO_PALETTE_JPEG| VIDEO_PALETTE_RGB32
(3)video_channel 关于各个信号源的属性
Channel 信号源的编号
name
tuners
Type VIDEO_TYPE_TV | IDEO_TYPE_CAMERA
Norm 制式 PAL|NSTC|SECAM|AUTO
(4)video_window 包含关于capture area的信息
x x windows 中的坐标.
y y windows 中的坐标.
width The width of the image capture.
height The height of the image capture.
chromakey A host order RGB32 value for the chroma key.
flags Additional capture flags.
clips A list of clipping rectangles. (Set only)
clipcount The number of clipping rectangles. (Set only)
(5)video_mbuf 利用mmap进行映射的帧的信息
size 每帧大小
Frames 最多支持的帧数
Offsets 每帧相对基址的偏移
(6)video_mmap 用于mmap
4.关键步骤介绍
【注】接多个摄像头.方法如下:买一个usb hub接到开发板的usb host上.cat
/proc/devices可以知道video capture device的major是81,再ls –l /dev看到video0
的次设备号是0.两个摄像头当然要两个设备号,所以mknod /dev/video1 c 81 1,
如果接4个,就mknod /dev/video2 c 81 2,mknod /dev/video3 c 81 3.依次类推.
(1)打开视频:
int v4l_open(char *dev, v4l_device *vd)
{
if (!dev)
dev = "/dev/video0";
if ((vd ->fd = open(dev, O_RDWR)) fd, VIDIOCGCAP, &(vd->capability)) capabil
ity各分量
(3)读video_picture中信息
int v4l_get_picture(v4l_device *vd)
{
if (ioctl(vd ->fd, VIDIOCGPICT, &(vd->picture)) picture.colour = 65535;
if(ioctl(vd->fd, VIDIOCSPICT, &(vd->picture)) capability中的信息
int v4l_get_channels(v4l_device *vd)
{
int i;
for (i = 0; i capability.channels; i++) {
vd ->channel[i].channel = i;
if (ioctl(vd ->fd, VIDIOCGCHAN, &(vd->channel[i])) fd);
return 0;
}
重点:截取图象的两种方法
一,用mmap(内存映射)方式截取视频
mmap( )系统调用使得进程之间通过映射同一个普通文件实现共享内存.普
通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访
问,不必再调用read(),write()等操作.两个不同进程A,B共享内存的意思是,
同一块物理内存被映射到进程A,B各自的进程地址空间.进程A可以即时看到进
程B对共享内存中数据的更新,反之亦然.
采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写
内存,而不需要任何数据的拷贝
(1)设置picture的属性
(2) 初始化video_mbuf,以得到所映射的buffer的信息
ioctl(vd->fd, VIDIOCGMBUF, &(vd->mbuf))
(3)可以修改video_mmap和帧状态的当前设置
(4)将mmap与video_mbuf绑定
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_
t offset )
len:映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始
算起
Prot:指定共享内存的访问权限 PROT_READ(可 读), PROT_WRITE (可写),
PROT_EXEC (可执行)
Flags:MAP_SHARED MAP_PRIVATE中必选一个,MAP_ FIXED不推荐使用
Addr:共内存享的起始地址,一般设0,表示由系统分配
Mmap( ) 返回值是系统实际分配的起始地址
int v4l_mmap_init(v4l_device *vd)
{
if (v4l_get_mbuf(vd) map = mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE,
MAP_SHARED, vd->fd, 0)) fd, VIDIOCMCAPTURE, &(vd->mmap)) ;
若调用成功,开始一帧的截取,是非阻塞的,
是否截取完毕留给VIDIOCSYNC来判断
(6)调用VIDIOCSYNC等待一帧截取结束
if(ioctl(vd->fd, VIDIOCSYNC, &frame) map + vd->mbuf.offsets[vd->frame]得到.
采集工作结束后调用munmap取消绑定
munmap(vd->map, vd->mbuf.size)
在实际应用时还可以采用缓冲队列等方式.
二,视频截取的第二种方法:直接读设备
关于缓冲大小,图象等的属性须由使用者事先设置
调用read();
int read (要访问的文件描述符;指向要读写的信息的指针;应该读写的字符数);
返回值为实际读写的字符数
int len ;
unsigned char
*vd->map=
(unsigned char *) malloc(vd capability.maxwidth*vd capability.maxheight );
len = read(vd fd,vd vd->map,
vd capability.maxwidth*vd capability.maxheight*3 );
2.3 编程实例(mouse_capture)
不管是ov511还是zc301的摄像头,它们采集的方式都是相同的,只不过采集
到的数据有所差异,ov511的就是rgb的位流,而zc301是jpeg编码的位流.
mouse_capture是根据servfox改编的一个专门从zc301摄像头获取一张jpeg图片,
用来测试摄像头是否加载成功的小程序.这样就可以不用cat /dev/video0>1.jpg来
测试摄像头是否正常.cat命令一运行,就源源不断地采集jpeg流.但是采到的图
片只能显示第一个jpeg头和jpeg尾之间的数据.mouse_capture仅仅获得一张完整
的jpeg. 可以从
(http://www.hhcn.com/cgi-bin/topic.cgiforum=1&topic=247&start=144&show=0)
处下载参考.
现将主要函数的功能介绍如下:
static int GetVideoPict (struct vdIn *vd);//获取图片属性信息.
static int SetVideoPict (struct vdIn *vd);//设置图片属性.
static int isSpcaChip (const char *BridgeName);//测试芯片类型
static int GetStreamId (const char *BridgeName); //测试输出数据的格式
static int GetDepth (int format);//获取颜色深度.
void exit_fatal(char *messages);//错误显示.
int init_videoIn(struct vdIn *vd,char *device,int width,int height,int forma
t,int
grabmethod);//初始化设备.
int convertframe(unsigned char *dst,unsigned char *src, int width,int height
, int
formatIn, int size);//把共享缓冲区中的数据放到一个变量中,通知系统已获得一
帧.
int v4lGrab (struct vdIn *vd,char *filename );//从摄像头采集图片.
int close_v4l (struct vdIn *vd);//关闭摄像头.
int get_jpegsize (unsigned char *buf, int insize);//获取jpeg图片大小.
三 实例程序
3.1 LCD实时显示从ov511上采集的图像
参考HHARM9-EDU/applications/usbcam2lcd.从摄像头获取bmp位流直接显示
在framebuffer中.此程序图像的采集采用read的方式,注意由于lcd液晶屏显示的
是16bits的RGB图片,所以,ov511输出的图片格式也应该是16bits的RGB图片数
据,宏VIDEO_PALETTE_RGB565定义的就是16bits的RGB数据图片.而linux自
带的ov511驱动中图像采集是32位的,这样采集到的图片显示在lcd上是雪花点.
因此需要修改驱动. 在kernet/driver/usb/目录下有ov511芯片的驱动ov511.c,驱
动里的ov51x_set_default_params函数是设置芯片默认的输出图片的格式,该函数
中的
for (i = 0; i frame[i].width = ov511->maxwidth;
ov511->frame[i].height = ov511->maxheight;
ov511->frame[i].bytes_read = 0;
if (force_palette)
ov511->frame[i].format = force_palette;
else
ov511->frame[i].format = VIDEO_PALETTE_RGB24;
ov511->frame[i].depth = ov511_get_depth(ov511->frame[i].format);
}
部分语句是主要设置ov511默认输出图片格式的,其中maxwidth和maxheight
设置了图片的最大的宽度和高度.Ifelse语句设置了图片的格式,作如下的修改:
for (i = 0; i frame[i].width = ov511->maxwidth;
ov511->frame[i].height = ov511->maxheight;
ov511->frame[i].bytes_read = 0;
ov511->frame[i].format = VIDEO_PALETTE_RGB565;
ov511->frame[i].depth = ov511_get_depth(ov511->frame[i].format);
}
如果需要,也可以改变图片的默认输出大小.
3.2 LCD实时显示从zc301上采集的图像
编程思想:从摄像头采集到的图片存放在本地文件夹,通过minigui加载jpeg
来实现显示.
具体过程:
1.从网上下载jpegsrc-6b的jpeg库,交叉编译.
(1)./configure -enable-static -enable-shared -prefix=.libs
(2)修改Makefile,将编译器改成交叉编译器.
例如:我改成/opt/host/armv4l/bin/armv4l-unknown-linux-gcc
(3)make 后即在.libs目录中生成for arm的
libjpeg.a, libjpeg.la,libjpeg.so,libjpeg.so.62,libjpeg.so.62.0.0.将这些文件拷
贝到系
统库文件目录,我的是/usr/lib中.
2.因为看从zc301采集的图片的二进制位流,jpeg头是ff d8 ff db.而在minigui库
文件libminigui的源文件src/mybmp/jpeg.c中,load_jpg和check_jpg的时候测试的头
位EXIF和JFIF两种格式的jpeg图片.这两种对应的二进制分别是ff d8 ff e1和ff d8
ff e0.所以我们minigui通过判断认为这是错误的jpeg格式而不加载,故无法显示.
实际上通过测试,在源码中去掉这两个判断就能正确加载.
3.交叉编译minigui
(1)编译库:./configure --host=arm-unknown-linux --enable-jpgsupport=yes
--enable-pngsupport=no --enable-gifsupport=no --disable-lite
--prefix=/HHARM9-EDU/applications/minigui-free/nfsroot
--enable-smdk2410ial=yes
make
make install
(2)编译实例程序时,要加上jpeg库的支持,即在Makefile中加上-ljpeg.此时
将在nfsroot生成的库文件和可执行文件移到ramdisk.image.gz相应的目录下.(具
体参考华恒的2410开发手册).
3.Minigui程序的编写
编程小技巧,我采取的方法是一刻不停地从摄像头采集到图片存储在
/tmp/1.jpg中,在minigui中通过loadbitmap函数来加载图片.而图片加载后不会自
动更新,不能自动根据1.jpg的改变自动变化.因此,我在程序中设定一个timer.
每隔100ms刷新屏幕,基本上实现实时更新了.而出现另外一个问题,刷新时会
以背景色来填充桌面,导致屏幕闪烁严重.故想到采用MSG_ERASEBKGND的
方式,用前一张图片做为刷新屏幕时的填充背景图片.这样就保证了lcd上图像
的连续性啦.
Minigui程序如下:其中一些自定义的函数跟mouse_capture中的一样,只是
变采集单幅到采集多幅.具体您可以自己改一下:).也可以向我索取源码.
#include
#include
#include
#include
#include
#include "spcav4l.h"
#define IDTIMER 100
static BITMAP bmp;
static int LoadBmpWinProc(HWND hWnd, int message, WPARAM wParam,
LPARAM lParam)
{
HDC hdc;
RECT rc={0,0,240,320};
switch (message) {
case MSG_CREATE:
SetTimer(hWnd,IDTIMER,100);
return 0;
case MSG_ERASEBKGND:
{
RECT rcTemp;
if( LoadBitmap(HDC_SCREEN,&bmp,"/tmp/1.jpg"))
{
printf("load wrong!n");
return -1;
}
GetClientRect(hWnd, &rcTemp);
hdc = BeginPaint (hWnd);
FillBoxWithBitmap (hdc, rcTemp.left, rcTemp.top, rcTemp.right-rcTemp.left,
rcTemp.bottom-rcTemp.top, &bmp);
EndPaint(hWnd, hdc);
return 0;
}
case MSG_TIMER:
InvalidateRect(hWnd,&rc,TRUE);
return 0;
case MSG_CLOSE:
UnloadBitmap (&bmp);
DestroyMainWindow (hWnd);
PostQuitMessage (hWnd);
return 0;
}
return DefaultMainWinProc(hWnd, message, wParam, lParam);
}
int MiniGUIMain (int argc, const char* argv[])
{
MSG Msg;
HWND hMainWnd;
MAINWINCREATE CreateInfo;
char videodevice[] = "/dev/video0";
char jpegfile[] = "/tmp/1.jpg";
int grabmethod = 0;
int format = VIDEO_PALETTE_JPEG;
int width = 240;
int height = 320;
int i;
#ifdef _LITE_VERSION
SetDesktopRect(0, 0, 1024, 768);
#endif
CreateInfo.dwStyle = WS_VISIBLE | WS_BORDER | WS_CAPTION;
CreateInfo.dwExStyle = WS_EX_NONE;
CreateInfo.spCaption = "Load and display a bitmap";
CreateInfo.hMenu = 0;
CreateInfo.hCursor = GetSystemCursor(0);
CreateInfo.hIcon = 0;
CreateInfo.MainWindowProc = LoadBmpWinProc;
CreateInfo.lx = 0;
CreateInfo.ty = 0;
CreateInfo.rx = 240;
CreateInfo.by = 320;
CreateInfo.iBkColor = PIXEL_lightwhite;
CreateInfo.dwAddData = 0;
CreateInfo.hHosting = HWND_DESKTOP;
hMainWnd = CreateMainWindow (&CreateInfo);
if (hMainWnd == HWND_INVALID)
return -1;
ShowWindow (hMainWnd, SW_SHOWNORMAL);
memset(&videoIn, 0, sizeof (struct vdIn));
if(init_videoIn(&videoIn, videodevice, width, height, format,grabmethod) ==
0)
{
printf("init is ok!n");
}
else printf("init is wrong!n");
while (GetMessage(&Msg, hMainWnd)) {
TranslateMessage(&Msg);
v4lGrab(&videoIn, jpegfile);
DispatchMessage(&Msg);
}
close_v4l (&videoIn);
MainWindowThreadCleanup (hMainWnd);
return 0;
}
#ifndef _LITE_VERSION
#include
#endif
先写到这里吧,呵呵,希望能对您有所帮助.如果您在阅读的过程中发现问题,欢迎和我交流.
参考文献
1.HHARM2410摄像头调试记录 华恒科技
2.基于video4linux的视频设备编程 Lingzhi_Shi Apr 7 2004
3.《video4linux programming》 Alan Cox
4.《video streaming 探讨》 陈俊宏
5.《Video4Linux Kernel API Reference 》
6.http://www.hhcn.com/cgi-bin/topic.cgiforum=1&topic=247&show=0
busybox简介及使用
1 busybox简介
busybox是一个集成了一百多个最常用linux命令和工具的软件,他甚至还集成了一个http服务器和一个telnet服务器,而所有这一切功能却只有区区1M左右的大小.我们平时用的那些linux命令就好比是分立式的电子元件,而busybox就好比是一个集成电路,把常用的工具和命令集成压缩在一个可执行文件里,功能基本不变,而大小却小很多倍,在嵌入式linux应用中,busybox有非常广的应用,另外,大多数linux发行版的安装程序中都有busybox的身影,安装linux的时候案ctrl+alt+F2就能得到一个控制台,而这个控制台中的所有命令都是指向busybox的链接.
Busybox的小身材大作用的特性,给制作一张软盘的linux带来了及大方便.
2,busybox的用法
可以这样用busybox
#busybox ls
他的功能就相当运行ls命令
最常用的用法是建立指向busybox的链接,不同的链接名完成不同的功能.
#ln -s busybox ls
#ln -s busybox rm
#ln -s busybox mkdir
然后分别运行这三个链接:
#./ls
#./rm
#./mkdir
就可以分别完成了ls rm 和mkdir命令的功能.虽然他们都指向同一个可执行程序busybox
但是只要链接名不同,完成的功能就不同,busybox就是这么的神奇.
很多linux网站都提供busybox的源代码下载.目前版本是busybox1.0正式版.
3,配置busybox
busybox的配置程序和linux内核菜单配置方式简直一模一样.熟悉用make menuconfig方式配置linux内核的朋友很容易上手.
#cp busybox-1.00.tar.gz /babylinux
#cd /babylinux
#tar xvfz busybox-1.00.tar.gz
#cd busybox-1.00
#make menuconfig
下面是需要编译进busybox的功能选项,其他的可以根据需要自选,但是同样不要太贪心.
General Configuration应该选的选项
Show verbose applet usage messages
Runtime SUID/SGID configuration via /etc/busybox.conf
Build Options
Build BusyBox as a static binary (no shared libs)
这个选项是一定要选择的,这样才能把busybox编译成静态链接的可执行文件,运行时才独立于其他函数库.否则必需要其他库文件才能运行,在单一个linux内核不能使他正常工作.
Installation Options
Don't use /usr
这个选项也一定要选,否则make install 后busybox将安装在原系统的/usr下,这将覆盖掉系统原有的命令.选择这个选项后,make install后会在busybox目录下生成一个叫_install的目录,里面有busybox和指向他的链接.
其他选项都是一些linux基本命令选项,自己需要哪些命令就编译进去,一般用默认的就可以了.
配置好后退出并保存.
4,编译并安装busybox
#make
#make install
编译好后在busybox目录下生成子目录_install,里面的内容:
drwxr-xr-x 2 root root 4096 11月 24 15:28 bin
lrwxrwxrwx 1 root root 11 11月 24 15:28 linuxrc -> bin/busybox
drwxr-xr-x 2 root root 4096 11月 24 15:28 sbin
其中可执行文件busybox在bin目录下,其他的都是指向他的符号链接.
我编译出来的busybox可执行文件是935K,加上符号链接,整个_install目录是952K.加上845K的内核不是已经超过1440K了吗?别担心,我们将对整个根文件系统做大幅度的压缩.