Basics VFS Virtual File System ,虚拟文件系统通过Unix上标准的系统调用(Syscall)来读写位于不同物理介质上的不同文件系统,为各类文件系统提供了一套统一的API接口。
文件系统一般需要经历几个阶段来具备被操作系统读写的能力:
创建 :通过某种方式将磁盘(Disk)格式化并建立文件系统。在建立文件系统时还会让磁盘里写入文件系统的控制信息。
注册 :各文件系统声明自己能够被操作系统内核支持,使内核能够记录文件系统的有关控制信息。注册又分为静态注册 和动态注册 。静态注册是指在编译内核时进行注册;动态注册指通过加载模块的方式进行注册。
安装 :对应的是平时的mount 操作,将文件系统挂载到操作系统根文件系统的目录树上。在这时候,操作系统便可以正常读写文件系统。
在这之后,VFS接口便可以对该文件系统进行读写。 以 sys_read() 的层次为例,可以看到通过 vfs_read() 的转接,VFS向 sys_read() 屏蔽了具体文件系统读写方法的细节。以编程设计模式的角度来看,就像是在具体文件系统的 read() 上面加了一个 代理层(Proxy) ,代理还可以将读写操作转到不同的文件系统上。
BTree 引用自wikipedia 的简介:
在计算机科学中,B树(英语:B-tree)是一种自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。B树,概括来说是一个一般化的二叉查找树(binary search tree),可以拥有多于2个子节点。与自平衡二叉查找树不同,B树为系统大块数据的读写操作做了优化。B树减少定位记录时所经历的中间过程,从而加快存取速度。B树这种数据结构可以用来描述外部存储。这种数据结构常被应用在数据库和文件系统的实现上。
Pager 模块负责处理SQLite的并发操作控制。同时Pager 确保SQLite是”ACID” (Atomic 原子性的,Consistent 一致性的,Isolated 隔离的,Durable 持久性的),以便支持对事务(Transaction) 的处理。我们对数据库文件的操作并不会直接作用到数据库文件本身,而是先与 Pager 打交道,再由 Pager 将修改写入到数据库文件。
结构 Pager 将SQLite的.db文件划分成尺寸大小和规格一致的块(Blocks),每一个块也叫做 “Page” ,每一个块的大小一般是1024字节。 在Unix系统上,Pager 通过 os_unix.c 来为操作系统提供抽象的操作接口。
多进程/多线程访问 通过以下一些机制,Pager 能够高效地控制多进程/多线程条件下对SQLite数据库文件的访问。
锁 通过加锁,Pager 能够确保进程/线程对数据库文件进行正确地操作,锁有可能处于几个状态当中:
UNLOCKED : db没有加锁,这样的db不用从中读取数据也不用向其中写入数据,该db中的数据被认为是可疑的。
SHARED : 该db允许读取数据但不允许写入数据,同一时间可以有多个进程通过持有该锁而从db中读取数据。但是当db的 SHARED 锁被持有时,不允许同一进程中的其他线程或者其他进程对db进行写入操作。
RESERVED :该锁同时只能被一个进程所获取。当一个进程正在从db中读取数据,并打算在未来的某一个时刻对db进行写入时可以获取该锁。RESERVED 锁存在时获取其他进程获取 SHARED 锁以便对db进行读取。
PENDING :一个进程允许获取该锁以便准备接下来对db的写入;如果db的 PENDING 锁被持有,则该db不允许新的进程或者线程获取 SHARED 锁。获取 PENDING 锁的进程并不能立即写入db,而是必须等待其他早已经被获得的 SHARED 执行完毕,之后该进程会获得 EXCLUSIVE 锁进行真正的写入。
EXCLUSIVE :获取该锁后便可以进行真正地写入;只有 EXCLUSIVE 锁能够写入db;EXCLUSIVE 是唯一一个互斥的,当一个 EXCLUSIVE 存在时,不允许存在其他锁 (UNLOCKED、SHARED、RESERVED)。
写入数据库文件 Rollback 记录 Rollback Journal 机制保证了在数据库操作出错时数据库文件的完整性。
当Pager 修改数据库文件之前,会先保存一份当前数据库内容的记录,叫作 Rollback Journal 到当前数据库文件所在的目录下,文件名为 XXX(db name)-journal 。除了保存数据库内容,还会保存当前数据库的文件大小,以便后面在写入数据库出错时能够正确恢复。
Pager 需要Rollback Journal来恢复数据库文件时,Rollback Journal会被认为处于 hot 状态。当进程/线程在对数据库文件进行修改 过程中因为 进程或者系统Crash 或者 突然断电 等原因而终止,Pager 会创建一个类型为 hot journal 的记录文件来帮助恢复。 当SQLite想要尝试从数据库文件中读取数据时,会先检查对应的 hot journal 是否存在。如存在,则在读取数据之前会先执行数据库文件的rollback恢复操作。
锁获取 对于一个想要写入数据库文件的进程,它必须按照以下顺序来获取锁:
1 SHARED --> RESERVED --> PENDING --> EXCLUSIVE
当一个进程/线程获取RESERVED锁之后,其他进程/线程无法获取RESERVED锁,其他进程/线程的数据库写入操作会执行失败,并返回SQLITE_BUSY 错误。 在获取RESERVED锁之前,想要写入数据库文件的进程/线程会先创建一个 Rollback Journal,并将数据库中的内容都写入到 Rollback Journal 当中,此时其他进程/线程依旧可以从数据库文件中读取数据。
写入
进程对 Page 的修改并不会立即写入到磁盘中而是保留在缓存上。只有当缓存中满了或者事务提交了commit之后才会写入到磁盘中的数据库文件中。此时进程/线程需要等待其他已经被获得的 SHARED锁 的读取执行完毕(获取 PENDING锁,等待获取EXCLUSIVE锁),并确保 Rollback Journal 已经完整创建。
如果因为缓存满而需要写入数据库文件 ,进程/线程并不会立即commit事务,而是会继续执行对其他 Page 的修改。因此,进程/线程所获取的 EXCLUSIVE锁 的生命周期可能非常长,从一开始执行写入直到最后事务执行commit。
commit之后会立即删除 Rollback Journal(如果在写入过程中操作系统Crash会创建 Hot Journal)。当执行完将所有修改真正写入到磁盘中 之后,便会释放EXCLUSIVE 、PENDING 。释放PENDING锁 之后其他进程/线程便可以尝试获取SHARED锁 从而读取最新的数据库文件中的数据。
创建连接、写入数据库源码解析 SQLite暴露了诸多接口给Framework,从连接建立到读写再到事务操作。
1 2 3 4 5 6 7 8 9 10 // /framework/base/core/java/android/database/sqlite/SQLiteConnection.java // 平时Framework对数据库操作最终都是由这些JNI接口转寄到SQLite中 private static native int nativeOpen(String path, int openFlags, String label, boolean enableTrace, boolean enableProfile); private static native void nativeClose(int connectionPtr); ... private static native long nativeExecuteForLastInsertedRowId( int connectionPtr, int statementPtr); ... private static native void nativeResetCancel(int connectionPtr, boolean cancelable);
篇幅所限,这里只介绍连接建立 (数据库连接的存在是Framework操作DB的基础)和写入DB (经历 Pager 锁的全部状态、Rollback Journal,相对读取更复杂)。
连接建立 nativeOpen() ,对应的JNI接口实现在 /framework/base/core/jni/android_database_SQLiteConnection.cpp 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static jint nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFlags, jstring labelStr, jboolean enableTrace, jboolean enableProfile) { ... //sqliteFlags 检查和字符转换 sqlite3* db; //通过 sqlite3_open_v2 方式打开数据库,还有sqlite3_open、sqlite3_open16等方式 int err = sqlite3_open_v2(path.string(), &db, sqliteFlags, NULL); if (err != SQLITE_OK) { throw_sqlite3_exception_errcode(env, err, "Could not open database"); return 0; } ... //1. 如果请求的拥有读写功能数据库连接,则需要检查数据库对象是否真的可以读写 //2. 设置busy handler来处理SQLITE_BUSY //3. 注册android function //将数据库对象包装到数据库连接当中 SQLiteConnection* connection = new SQLiteConnection(db, openFlags, path, label); ... //设置 tracing、profiling 等功能 //返回 return reinterpret_cast<jint>(connection); }
无论使用何种方式打开数据库,最后都会进入到openDatabase() /external/sqlite/dist/sqlite3.c
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 static int openDatabase( const char *zFilename, /* Database filename UTF-8 encoded */ sqlite3 **ppDb, /* OUT: Returned database handle */ unsigned int flags, /* Operational flags */ const char *zVfs /* Name of the VFS to use */ ){ sqlite3 *db; /* Store allocated handle here */ int rc; /* Return code */ int isThreadsafe; /* True for threadsafe connections */ char *zOpen = 0; /* Filename argument to pass to BtreeOpen() */ char *zErrMsg = 0; /* Error message from sqlite3ParseUri() */ *ppDb = 0; ... //flag设置 // SQLite DB 分配内存 db = sqlite3MallocZero( sizeof(sqlite3) ); //分配失败则跳转到 opendb_out if( db==0 ) goto opendb_out; if( isThreadsafe ){ // 没有设置有关线程安全的标识符因此跟具体的SQLite配置有关 db->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE); if( db->mutex==0 ){ sqlite3_free(db); db = 0; goto opendb_out; } } //锁上db.mutex,再进入后面的执行 sqlite3_mutex_enter(db->mutex); //两个db的backend, 第一个是主要操作的main, 第二个是temp,后面会看到 db->nDb = 2; db->aDb = db->aDbStatic; ... //设置db一些参数 // 创建排序函数 createCollation(db, "BINARY", SQLITE_UTF8, 0, binCollFunc, 0); createCollation(db, "BINARY", SQLITE_UTF16BE, 0, binCollFunc, 0); createCollation(db, "BINARY", SQLITE_UTF16LE, 0, binCollFunc, 0); createCollation(db, "RTRIM", SQLITE_UTF8, (void*)1, binCollFunc, 0); if( db->mallocFailed ){ //分配内存失败,跳转到opendb_out goto opendb_out; } // 默认的排序函数为 BINARY db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "BINARY", 0); assert( db->pDfltColl!=0 ); // 创建UTF-8 大小写不敏感的排序函数 createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0); // 将数据库文件名、指定的VFS、flag 等信息转换成对应的 Uri db->openFlags = flags; rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg); if( rc!=SQLITE_OK ){ //Uri parse 出错 if( rc==SQLITE_NOMEM ) db->mallocFailed = 1; sqlite3Error(db, rc, zErrMsg ? "%s" : 0, zErrMsg); sqlite3_free(zErrMsg); goto opendb_out; } //打开数据库驱动并将其指向到 第一个database backend的pBt上 //这里是真实打开数据库文件的地方,使用BTree的方式打开数据库文件 //将创建的BTree 放到db->aDb[0].pBt当中。 rc = sqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, 0, flags | SQLITE_OPEN_MAIN_DB); if( rc!=SQLITE_OK ){ if( rc==SQLITE_IOERR_NOMEM ){ rc = SQLITE_NOMEM; } sqlite3Error(db, rc, 0); goto opendb_out; } //获取database backend 对应的数据库模式 db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt); db->aDb[1].pSchema = sqlite3SchemaGet(db, 0); db->aDb[0].zName = "main"; db->aDb[0].safety_level = 3; db->aDb[1].zName = "temp"; db->aDb[1].safety_level = 1; ... //注册内建函数到db中,这个时候还没有从db中读取内容, //第一次读取内容将是在db对象第一次被访问的时候 sqlite3RegisterBuiltinFunctions(db); ... // 如何退出 opendb_out: sqlite3_free(zOpen); if( db ){ assert( db->mutex!=0 || isThreadsafe==0 || sqlite3GlobalConfig.bFullMutex==0 ); // 有控制线程安全的锁则需要退出 sqlite3_mutex_leave(db->mutex); } rc = sqlite3_errcode(db); assert( db!=0 || rc==SQLITE_NOMEM ); if( rc==SQLITE_NOMEM ){ // 没有空间创建数据库对象,执行数据库关闭,释放资源 sqlite3_close(db); db = 0; }else if( rc!=SQLITE_OK ){ db->magic = SQLITE_MAGIC_SICK; } //让ppDb指针指向db,供调用方使用。 *ppDb = db; return sqlite3ApiExit(0, rc); }
在SQLite3中,使用BTree 作为存储引擎来使用数据库,上面也使用sqlite3BtreeOpen() 的方法来打开数据库,下面看看在这其中都做了什么
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 SQLITE_PRIVATE int sqlite3BtreeOpen( sqlite3_vfs *pVfs, /* VFS to use for this b-tree */ const char *zFilename, /* Name of the file containing the BTree database */ sqlite3 *db, /* Associated database handle */ Btree **ppBtree, /* Pointer to new Btree object written here */ int flags, /* Options */ int vfsFlags /* Flags passed through to sqlite3_vfs.xOpen() */ ){ BtShared *pBt = 0; /* Shared part of btree structure */ Btree *p; /* Handle to return */ sqlite3_mutex *mutexOpen = 0; /* Prevents a race condition. Ticket #3537 */ int rc = SQLITE_OK; /* Result code from this function */ u8 nReserve; /* Byte of unused space on each page */ unsigned char zDbHeader[100]; /* Database header content */ ... if( isMemdb ){ //是否是 in-memory 的DB flags |= BTREE_MEMORY; } if( (vfsFlags & SQLITE_OPEN_MAIN_DB)!=0 && (isMemdb || isTempDb) ){ vfsFlags = (vfsFlags & ~SQLITE_OPEN_MAIN_DB) | SQLITE_OPEN_TEMP_DB; } //分配内存 p = sqlite3MallocZero(sizeof(Btree)); if( !p ){ return SQLITE_NOMEM; } p->inTrans = TRANS_NONE; p->db = db; // 如果设置使用缓存则尝试寻找并使用缓存的BTree 共享内存,找到则可以直接返回,不再执行后续执行。 ... // pBt = 0, 共享的BTree存储对象为空 ... pBt = sqlite3MallocZero( sizeof(*pBt) ); if( pBt==0 ){ rc = SQLITE_NOMEM; goto btree_open_out; } // 打开sqlite3 数据库文件 rc = sqlite3PagerOpen(pVfs, &pBt->pPager, zFilename, EXTRA_SIZE, flags, vfsFlags, pageReinit); if( rc==SQLITE_OK ){ //打开成功,则继续读取该文件的头 rc = sqlite3PagerReadFileheader(pBt->pPager,sizeof(zDbHeader),zDbHeader); } if( rc!=SQLITE_OK ){ goto btree_open_out; } pBt->openFlags = (u8)flags; pBt->db = db; // 注册数据库文件的 BusyHandler sqlite3PagerSetBusyhandler(pBt->pPager, btreeInvokeBusyHandler, pBt); p->pBt = pBt; pBt->pCursor = 0; pBt->pPage1 = 0; // 判断sqlite3 是否以只读方法打开 if( sqlite3PagerIsreadonly(pBt->pPager) ) pBt->btsFlags |= BTS_READ_ONLY; ... // 设置Pager Object的size rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, nReserve); // 将共享的BTree 对象添加进全局的 linkedlist // 将新的BTree 添加进同一个连接的共享Btree 对象链表中 // 如何退出 ... }
写入 以SQLiteConnection中的 executeForLastInsertedRowId JNI方法为例
1 2 3 4 5 6 7 8 9 10 static jlong nativeExecuteForLastInsertedRowId(JNIEnv* env, jclass clazz, jint connectionPtr, jint statementPtr) { // 将上层保存的int指针转换成SQLiteConnection指针 SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr); // 转换Statement指针 sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr); int err = executeNonQuery(env, connection, statement); return err == SQLITE_DONE && sqlite3_changes(connection->db) > 0 ? sqlite3_last_insert_rowid(connection->db) : -1; }
转换到 executeNonQuery 执行
1 2 3 4 5 6 7 8 // 通过 sqlite3_step 到SQLite中执行 int err = sqlite3_step(statement); if (err == SQLITE_ROW) { throw_sqlite3_exception(env, "Queries can be performed using SQLiteDatabase query or rawQuery methods only."); } else if (err != SQLITE_DONE) { throw_sqlite3_exception(env, connection->db); }
然后转换到SQLite中执行 sqlite3.c
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 32 33 34 35 36 37 SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){ int rc = SQLITE_OK; /* Result from sqlite3Step() */ int rc2 = SQLITE_OK; /* Result from sqlite3Reprepare() */ Vdbe *v = (Vdbe*)pStmt; /* the prepared statement */ int cnt = 0; /* Counter to prevent infinite loop of reprepares */ sqlite3 *db; /* The database connection */ if( vdbeSafetyNotNull(v) ){ return SQLITE_MISUSE_BKPT; } db = v->db; // 加锁 sqlite3_mutex_enter(db->mutex); // 循环执行 sqlite3Step, 并最终转发到sqlite3VdbeExec() 中执行 while( (rc = sqlite3Step(v))==SQLITE_SCHEMA && cnt++ < SQLITE_MAX_SCHEMA_RETRY && (rc2 = rc = sqlite3Reprepare(v))==SQLITE_OK ){ // reset SQLiteStatement sqlite3_reset(pStmt); assert( v->expired==0 ); } // 如果执行出错则进行清理阶段 if( rc2!=SQLITE_OK && ALWAYS(v->isPrepareV2) && ALWAYS(db->pErr) ){ const char *zErr = (const char *)sqlite3_value_text(db->pErr); sqlite3DbFree(db, v->zErrMsg); if( !db->mallocFailed ){ v->zErrMsg = sqlite3DbStrDup(db, zErr); v->rc = rc2; } else { v->zErrMsg = 0; v->rc = rc = SQLITE_NOMEM; } } rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; }
最后转发到 sqlite3VdbeExec 中执行,在 sqlite3VdbeExec 中会转换成 SQLite VDBE Program。有关VDBE Program 的内容请参考官方文档 VDBE