存儲格式
Oracle Number數(shù)據(jù)類型是變長的,占0~22字節(jié),不像編程語言中的2/4字節(jié)整數(shù)或4/8字節(jié)浮點數(shù),關(guān)于它的存儲格式與解析,DSI上有詳細的描述,如下所示
符號位/指數(shù)字節(jié)描述如下
數(shù)字字節(jié)描述如下
正數(shù)或零值的計算
負數(shù)值的計算
解析實現(xiàn)
由于Oracle Number的精度高達38位,遠超出了基本定長整數(shù)或浮點數(shù)表達的數(shù)值范圍,因此解析實際上是大整數(shù)/實數(shù)的四則運算,為避免造輪子,本文使用了
GMP開源庫(
https://gmplib.org/),用于任意精度的算術(shù)運算,操作有符號整數(shù)、有理數(shù)和浮點數(shù),除了在GMP機器上運行的可用內(nèi)存所暗示的精度之外,對精度沒有實際的限制。解析實現(xiàn)的核心函數(shù)是
orcl_raw2number
1 #include <stdio.h>
2 #include <assert.h>
3 #include <gmp.h>
4
5 #define MAX_PREC 256
6
7 static mpf_t s_base100;
8 static mpf_t s_one;
9
10 static void init_mpf_globals()
11 {
12 mpf_init_set_ui(s_base100, 100);
13 mpf_init_set_ui(s_one, 1);
14 }
15
16 static void clear_mpf_globals()
17 {
18 mpf_clear(s_base100);
19 mpf_clear(s_one);
20 }
21
22 static void orcl_raw2number(unsigned char *data, unsigned int len, mpf_t result)
23 {
24 unsigned int sign = *data, digit, i;
25 int exp = sign>=128 ? sign-193 : 62-sign;
26 int exp_val;
27 mpf_t tmp;
28
29 mpf_init2(tmp, MAX_PREC);
30 mpf_init2(result, MAX_PREC);
31
32 if(sign & 0x80){
33 for(i=1; i<len; ++i){
34 digit = data[i] - 1;
35 assert(0<=digit && digit<=99);
36
37 exp_val = exp - i + 1;
38 if(exp_val < 0){
39 mpf_pow_ui(tmp, s_base100, -exp_val);
40 mpf_div(tmp, s_one, tmp);
41 }else
42 mpf_pow_ui(tmp, s_base100, exp_val);
43
44 mpf_mul_ui(tmp, tmp, digit);
45 mpf_add(result, result, tmp);
46 }
47
48 }else{
49 --len; //ignore the last byte
50 for(i=1; i<len; ++i){
51 digit = 101 - data[i];
52 assert(0<=digit && digit<=99);
53
54 exp_val = exp - i + 1;
55 if(exp_val < 0){
56 mpf_pow_ui(tmp, s_base100, -exp_val);
57 mpf_div(tmp, s_one, tmp);
58 }else
59 mpf_pow_ui(tmp, s_base100, exp_val);
60
61 mpf_mul_ui(tmp, tmp, digit);
62 mpf_add(result, result, tmp);
63 }
64
65 mpf_neg(result, result);
66 }
67
68 mpf_clear(tmp);
69 }
測試用例
測試了123456.789、-123456.789、Oracle Number實際最大最小值、Oracle Number理論最大最小值
1 int main(int argc,
char *argv[])
2 {
3 int n = 19;
4 char buf[256];
5 mpf_t r;
6
7 init_mpf_globals();
8
9 //123456.789
10 unsigned
char data[] = {0xc3,0xd,0x23,0x39,0x4f,0x5b};
11 orcl_raw2number(data,
sizeof(data), r);
12 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
13 printf("result: %s\n", buf);
14 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
15 mpf_clear(r);
16
17 //-123456.789
18 unsigned
char data2[] = {0x3c,0x59,0x43,0x2d,0x17,0xb,0x66};
19 orcl_raw2number(data2,
sizeof(data2), r);
20 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
21 printf("result: %s\n", buf);
22 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
23 mpf_clear(r);
24
25 //0
26 unsigned
char zero[] = {0x80};
27 orcl_raw2number(zero,
sizeof(zero), r);
28 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
29 printf("result: %s\n", buf);
30 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
31 mpf_clear(r);
32
33 //test actual max value:9999
9(the number of 9 is 38)
34 unsigned
char max_data[] = {0xd3,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64};
35 orcl_raw2number(max_data,
sizeof(max_data), r);
36 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
37 printf("result: %s\n", buf);
38 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
39 mpf_clear(r);
40
41 //test actual min value:-9999
9(the number of 9 is 38)
42 unsigned
char min_data[] = {0x2c,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x66};
43 orcl_raw2number(min_data,
sizeof(min_data), r);
44 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
45 printf("result: %s\n", buf);
46 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
47 mpf_clear(r);
48
49 clear_mpf_globals();
50
51 //test max oracle number value
52 mpf_init2(r, 256);
53
54 mpf_set_str(r, "1e125", 10);
55 mpf_out_str(NULL, 10, 0, r); printf("\n");
56 gmp_printf("%Ff\n", r);
57
58 //test min oracle number value
59 mpf_set_str(r, "-1e125", 10);
60 mpf_out_str(NULL, 10, 0, r); printf("\n");
61 gmp_printf("%Ff\n", r);
62
63 mpf_clear(r);
64
65 return 0;
66 }
輸出如下
posted @
2020-05-08 12:23 春秋十二月 閱讀(946) |
評論 (0) |
編輯 收藏
場景說明
選擇ENet代替TCP用于弱網(wǎng)環(huán)境(通常丟包率高)的數(shù)據(jù)傳輸,提高可靠性及傳輸效率。為了說明怎樣正確有效地應用ENet,本文按照TCP C/S同步通信的流程作了對應的接口封裝實現(xiàn),取庫名為
rudp。
接口對照
左邊為rudp庫的API,右邊為標準的Berkeley套接字API。rudp庫所有API前綴為rudp,rudp_listen和rudp_accept僅用于服務端,rudp_connect和rudp_disconnect僅用于客戶端;其它接口共用于兩端,其中rudp_send調(diào)用rudp_sendmsg實現(xiàn),rudp_recv調(diào)用rudp_recvmsg實現(xiàn)。
具體實現(xiàn)
所有接口遵循Berkeley套接字接口的語義,為簡單起見,錯誤描述輸出到標準錯誤流。
◆
監(jiān)聽,成功返回0,失敗返回-1
1 int rudp_listen(const char *ip, int port, ENetHost **host)
2 {
3 ENetAddress address;
4
5 if(!strcmp(ip, "*"))
6 ip = "0.0.0.0";
7
8 if(enet_address_set_host_ip(&address, ip)){
9 fprintf(stderr, "enet_address_set_host_ip %s fail", ip);
10 return -1;
11 }
12
13 address.port = port;
14
15 assert(host);
16 *host = enet_host_create(&address, 1, 1, 0, 0);
17 if(NULL==*host){
18 fprintf(stderr, "enet_host_create %s:%d fail", address.host, address.port);
19 return -1;
20 }
21
22 int size = 1024*1024*1024;
23 if(enet_socket_set_option((*host)->socket, ENET_SOCKOPT_RCVBUF, size)){
24 fprintf(stderr, "enet set server socket rcvbuf %d bytes fail", size);
25 }
26
27 return 0;
28 }
◆
接受連接,成功返回0,失敗返回-1
1 int rudp_accept(ENetHost *host, unsigned int timeout, ENetPeer **peer)
2 {
3 int ret;
4 ENetEvent event;
5
6 ret = enet_host_service(host, &event, timeout);
7 if(ret > 0){
8 if(event.type != ENET_EVENT_TYPE_CONNECT){
9 if(event.type == ENET_EVENT_TYPE_RECEIVE)
10 enet_packet_destroy(event.packet);
11 fprintf(stderr, "enet_host_service event type %d is not connect", event.type);
12 return -1;
13 }
14
15 assert(peer);
16 *peer = event.peer;
17
18 }else if(0==ret){
19 fprintf(stderr, "enet_host_service timeout %d", timeout);
20 return -1;
21
22 }else{
23 fprintf(stderr, "enet_host_service fail");
24 return -1;
25 }
26
27 return 0;
28 }
◆
建立連接,成功返回0,失敗返回-1,conn_timeout是連接超時,rw_timeout是收發(fā)超時,單位為毫秒
1 int rudp_connect(const char *srv_ip, int srv_port, unsigned int conn_timeout, unsigned int rw_timeout, ENetHost **host, ENetPeer **peer)
2 {
3 assert(host);
4 *host = enet_host_create(NULL, 1, 1, 0, 0);
5 if(NULL==*host){
6 fprintf(stderr, "enet_host_create fail");
7 goto fail;
8 }
9 if(enet_socket_set_option((*host)->socket, ENET_SOCKOPT_RCVBUF, 1024*1024*1024)){
10 fprintf(stderr, "enet set server socket rcvbuf 1M bytes fail");
11 }
12
13 ENetAddress srv_addr;
14 if(enet_address_set_host_ip(&srv_addr, srv_ip)){
15 fprintf(stderr, "enet_address_set_host_ip %s fail", srv_ip);
16 goto fail;
17 }
18 srv_addr.port = srv_port;
19
20 assert(peer);
21 *peer = enet_host_connect(*host, &srv_addr, 1, 0);
22 if(*peer==NULL){
23 fprintf(stderr, "enet_host_connect %s:%d fail", srv_ip, srv_port);
24 goto fail;
25 }
26
27 enet_peer_timeout(*peer, 0, rw_timeout, rw_timeout);
28
29 int cnt = 0;
30 ENetEvent event;
31
32 while(1){
33 ret = enet_host_service(*host, &event, 1);
34 if(ret == 0){
35 if(++cnt >= conn_timeout){
36 fprintf(stderr, "enet_host_service timeout %d", conn_timeout);
37 goto fail;
38 }
39
40 }else if(ret > 0){
41 if(event.type != ENET_EVENT_TYPE_CONNECT){
42 fprintf(stderr, "enet_host_service event type %d is not connect", event.type);
43 goto fail;
44 }
45 break; //connect successfully
46
47 }else{
48 fprintf(stderr, "enet_host_service fail");
49 goto fail;
50 }
51 }
52
53 #ifdef _DEBUG
54 char local_ip[16], foreign_ip[16];
55 ENetAddress local_addr;
56
57 enet_socket_get_address((*host)->socket, &local_addr);
58 enet_address_get_host_ip(&local_addr, local_ip, sizeof(local_ip));
59 enet_address_get_host_ip(&(*peer)->address, foreign_ip, sizeof(foreign_ip));
60
61 printf("%s:%d connected to %s:%d", local_ip, loca_addr.port, foreign_ip, (*peer)->address.port);
62 #endif
63
64 return 0;
65
66 fail:
67 if(*host) enet_host_destroy(*host);
68 return -1;
69 }
◆
斷開連接,若成功則返回0,超時返回1,出錯返回-1。先進行優(yōu)雅關(guān)閉,如失敗再強制關(guān)閉
1 int rudp_disconnect(ENetHost *host, ENetPeer *peer)
2 {
3 int ret;
4
5 #ifdef _DEBUG
6 char local_ip[16], foreign_ip[16];
7 ENetAddress local_addr;
8
9 enet_socket_get_address(host->socket, &local_addr);
10 enet_address_get_host_ip(&local_addr, local_ip, sizeof(local_ip));
11 enet_address_get_host_ip(&peer->address, foreign_ip, sizeof(foreign_ip));
12
13 printf("%s:%d is disconnected from %s:%d", local_ip, local_addr.port, foreign_ip, peer->address.port);
14 #endif
15
16 ENetEvent event;
17 enet_peer_disconnect(peer, 0);
18
19 while((ret = enet_host_service(host, &event, peer->roundTripTime)) > 0){
20 switch (event.type){
21 case ENET_EVENT_TYPE_RECEIVE:
22 enet_packet_destroy (event.packet);
23 break;
24
25 case ENET_EVENT_TYPE_DISCONNECT:
26 ret = 0;
27 goto disconn_ok;
28 }
29 }
30
31 ret = 0==ret ? 1 : -1;
32
33 fprintf(stderr, "enet_host_service with timeout %d %s", peer->roundTripTime, 1==ret?"timeout":"failure");
34
35 enet_peer_reset(conn->peer);
36
37 disconn_ok:
38 enet_host_destroy(host);
39 return ret;
40 }
◆
發(fā)送數(shù)據(jù),若成功則返回已發(fā)送數(shù)據(jù)的長度,否則返回-1
1 int rudp_sendmsg(ENetHost *host, ENetPeer *peer, ENetPacket *packet)
2 {
3 int ret;
4
5 if(enet_peer_send(peer, 0, packet)){
6 fprintf(stderr, "enet send packet %lu bytes to peer fail", packet->dataLength);
7 return -1;
8 }
9
10 ret = enet_host_service(host, NULL, peer->roundTripTime);
11 if(ret >= 0){
12 if(peer->state == ENET_PEER_STATE_ZOMBIE){
13 fprintf(stderr, "enet peer state is zombie");
14 return -1;
15 }
16 return packet->dataLength;
17
18 }else{
19 fprintf(stderr, "enet host service %u millsecond failure", peer->roundTripTime);
20 return -1;
21 }
22 }
23
24 int rudp_send(ENetHost *host, ENetPeer *peer, const void *buf, size_t len)
25 {
26 int ret;
27
28 ENetPacket *packet = enet_packet_create(buf, len, ENET_PACKET_FLAG_RELIABLE);
29 if(NULL==packet){
30 fprintf(stderr, "enet create packet %lu bytes fail", sizeof(int)+len);
31 return -1;
32 }
33
34 return rudp_sendmsg(host, peer, packet);
35 }
發(fā)送數(shù)據(jù)時需根據(jù)對端狀態(tài)判斷是否斷線,并且packet標志設為可靠
◆
接收數(shù)據(jù),若成功則返回已接收數(shù)據(jù)的長度,否則返回-1
1 int rudp_recvmsg(ENetHost *host, ENetPeer *peer, ENetPacket **packet, unsigned int timeout)
2 {
3 int ret;
4 ENetEvent event;
5
6 ret = enet_host_service(host, &event, timeout);
7 if(ret > 0){
8 if(event.peer != peer){
9 fprintf(stderr, "enet receive peer is not matched");
10 goto fail;
11 }
12 if(event.type != ENET_EVENT_TYPE_RECEIVE){
13 fprintf(stderr, "enet receive event type %d is not ENET_EVENT_TYPE_RECEIVE", event.type);
14 goto fail;
15 }
16
17 *packet = event.packet;
18 return (*packet)->dataLength;
19
20 fail:
21 enet_packet_destroy(event.packet);
22 return -1;
23
24 }else {
25 fprintf(stderr, "enet receive %u millsecond %s", timeout, ret?"failure":"timeout");
26 return -1;
27 }
28 }
29
30 int rudp_recv(ENetHost *host, ENetPeer *peer, void *buf, size_t maxlen, unsigned int timeout)
31 {
32 ENetPacket *packet;
33
34 if(-1==rudp_recvmsg(host, peer, &packet, timeout))
35 return -1;
36
37 if(packet->dataLength > maxlen) {
38 fprintf(stderr, "enet packet data length %d is greater than maxlen %lu", packet->dataLength, maxlen);
39 return -1;
40 }
41
42 memcpy(buf, packet->data, packet->dataLength);
43 enet_packet_destroy(packet);
44
45 return packet->dataLength;
46 }
◆
等待所有確認,若成功返回0,超時返回1,失敗返回-1
1 int rudp_wait_allack(ENetHost *host, ENetPeer *peer, unsigned int timeout)
2 {
3 int ret, cnt = 0;
4
5 while((ret = enet_host_service(host, NULL, 1)) >= 0){
6 if(enet_peer_is_empty_sent_reliable_commands(peer, 0,
7 ENET_PROTOCOL_COMMAND_SEND_RELIABLE|ENET_PROTOCOL_COMMAND_SEND_FRAGMENT))
8 return 0;
9
10 if(peer->state == ENET_PEER_STATE_ZOMBIE){
11 fprintf(stderr, "enet peer state is zombie");
12 return -1;
13 }
14
15 if(0==ret && ++cnt>=timeout){
16 return 1;
17 }
18 }
19
20 fprintf(stderr, "enet host service fail");
21 return -1;
22 }
等待已發(fā)送數(shù)據(jù)的所有確認時,需根據(jù)對端狀態(tài)判斷是否斷線
示例流程
左邊為客戶端,壓縮并傳輸文件;右邊為服務端,接收并解壓存儲文件。

客戶端【讀文件塊并壓縮】這個環(huán)節(jié),需要顯式創(chuàng)建可靠packet,并將壓縮后的塊拷貝到其中
posted @
2020-05-04 19:08 春秋十二月 閱讀(2367) |
評論 (0) |
編輯 收藏
為什么用VSS
VSS是Windows系統(tǒng)的卷影像拷貝服務,用于解決如下問題:
◆ 許多備份工具涉及打開文件
◆ 但是若一個應用程序已經(jīng)以獨占方式打開文件并進行訪問時,備份工具則不能訪問該文件
◆ 即使備份工具能夠訪問已打開的文件,也可能造成備份文件的不一致性
在實際數(shù)據(jù)災備中,主流廠商實現(xiàn)SQL Server的熱備并不會使用數(shù)據(jù)庫自帶的
backup database/
backup log命令,因為這種方式在應急容災(此時源數(shù)據(jù)庫已宕機)掛載數(shù)據(jù)時要先還原,而還原要連接數(shù)據(jù)庫運行
restore database/
restore log命令,這樣就需要部署一臺機器裝上SQL Server專用于還原,不僅增大了成本而且延長了
RTO;而使用VSS,備份的就是SQL Server的數(shù)據(jù)文件及日志文件,在應急容災掛載時可直接打開并用于增刪改查,無須還原,免去了機器成本并降低了RTO(只存在數(shù)據(jù)庫掛載時的事務恢復時間)。
VSS架構(gòu)
VSS包括Requestor、Writer、Provider和VSS核心模塊四部分,如下圖所示

Requestor在本文中表示熱備份應用程序;Writer主要功能是保證數(shù)據(jù)的一致性,使得那些能夠感知影像拷貝的應用程序能夠接收到凍結(jié)(freeze)和解凍(thaw)通知,以確保其文件的備份拷貝是內(nèi)在一致的,在本文中即指SQL Server自帶的
SQL Writer;Provider主要功能是創(chuàng)建影像拷貝即打快照,允許將ISV特定的存儲方案與影像拷貝服務集成起來,在本文中即
volsnap.sys存儲過濾型驅(qū)動程序,位于文件系統(tǒng)和卷管理器之間;VSS核心模塊即圖中的卷影像拷貝服務,主要功能是協(xié)調(diào)各個模塊的協(xié)作運行,并提供創(chuàng)建及管理卷影像拷貝的API接口。
VSS原理示例
無論何時,當卷影像拷貝驅(qū)動程序看到一個針對原始卷的寫操作時,它把將要被修改的扇區(qū)的內(nèi)容復制到一個與影像卷相關(guān)聯(lián)的、由頁面文件支持的內(nèi)存區(qū)中
◆ 對于已修改扇區(qū)的影像卷讀操作,從該內(nèi)存區(qū)中讀取數(shù)據(jù)
◆ 對于未修改扇區(qū)的影像卷讀操作,從原始卷中讀取
備份應用程序、Provider和SQL Writer的局限
◆ 只能備份Windows系統(tǒng)支持的本地文件系統(tǒng)上的文件,不支持遠程共享或交叉掛載的文件系統(tǒng)
◆ 對于系統(tǒng)提供者(Windows系統(tǒng)默認自帶的Provider,使用寫時拷貝技術(shù)),被拷貝的源卷不必是NTFS卷,但影像卷必須是NTFS卷
◆ SQL Writer支持全量備份及恢復、支持差異備份及恢復和Copy Only備份,但不支持備份連續(xù)事務日志、文件和文件組,不支持頁恢復
怎樣使用VSS
微軟官網(wǎng)提供的VSS SDK 7.2(
https://www.microsoft.com/en-us/download/details.aspx?id=23490)中自帶了
vshadow和
betest工具源碼,經(jīng)過筆者修正一些bug(win 10 + vs2010),并為了備份配置方便將原來的文本換成xml格式,成功地實現(xiàn)了SQL Server的全量熱備及恢復、差量熱備及恢復
vshadow用法
以管理員身份在ms-dos窗口下執(zhí)行vshadow.exe /?,可得到所有的幫助
示例
可用vshadow -wm獲取當前系統(tǒng)所有寫者的元數(shù)據(jù),再從中查找SQL Server Writer的寫者ID及它下面COM組件的邏輯路徑和名稱
betest用法
以管理員身份在ms-dos窗口下執(zhí)行betest.exe /?,可得到所有的幫助
示例
1. 全量備份SQL Server
betest.exe /v
/b /t
FULL /s backupfull.xml /d f:\backupfull /c SQLWriter.xml
/v -- 輸出詳細信息,可選的
/b -- 備份
/t -- 備份類型
/s -- 備份/恢復組件XML格式文檔,內(nèi)含寫者及其下組件的元數(shù)據(jù)(非常重要)
/d -- 備份目錄
/c -- 相關(guān)寫者的配置文件,文件內(nèi)含寫者ID及其下COM組件的邏輯全路徑名
全量恢復SQL Server
betest.exe /v
/r /s backupfull.xml /d f:\backupfull /c SQLWriter.xml
/r -- 恢復
其它選項說明同上,下同
2. 差異備份SQL Server
betest.exe /v /b /t
DIFFERENTIAL /s backupdiff.xml
/pre backupfull.xml /d f:\backupdiff /c SQLWriter.xml
/pre -- 表示前次基準的全量備份生成的組件XML格式文檔
差異恢復SQL Server
a) betest.exe /v /r
/AdditionalRestores /s backupfull.xml /d f:\backupfull /c SQLWriter.xml
/AdditionRestores -- 用于差異恢復的選項,表示全量后面需要緊跟差異恢復才能完成數(shù)據(jù)庫恢復
b) betest.exe /v /r /s backupdiff.xml /d f:\backupdiff /c SQLWriter.xml
注意,此時/s跟的是差異備份生成的backupdiff.xml文件,/d跟的是差異備份目錄
3. SQL Writer配置
xml格式說明
writer節(jié)點
id屬性 --- 寫者唯一ID
server_name屬性 --- SQLServer服務名
stop_restore_start屬性(可選) --- 表示恢復時是否先停止數(shù)據(jù)庫服務再啟動,yes表示先停再啟,no則反之,這個用于恢復系統(tǒng)數(shù)據(jù)庫master,因為master不支持在線恢復
component節(jié)點
pathname屬性 --- 邏輯路徑名
file節(jié)點
src_path節(jié)點 --- SQL Server文件所在路徑的匹配模式
alternate_path節(jié)點 --- 恢復時的備選路徑,用于合成差異增量
示例
<?xml version="1.0" encoding="utf-8"?>
<betest>
<writer id="{a65faa63-5ea8-4ebc-9dbd-a0c4db26912a}" service_name="MSSQLSERVER" stop_restore_start="no">
<component pathname="DESKTOP-JUP320L\master">
<file>
<src_path>E:\*...</src_path>
<alternate_path>f:\sqlserver\</alternate_path>
</file>
</component>
<component pathname="DESKTOP-JUP320L\model">
<!--file>
<src_path>E:\*...</src_path>
<alternate_path>f:\sqlserver\</alternate_path>
</file-->
</component>
<component pathname="DESKTOP-JUP320L\test">
<!--file>
<src_path>E:\*...</src_path>
<alternate_path>f:\sqlserver\</alternate_path>
</file-->
</component>
</writer>
</betest>
posted @
2020-05-02 16:31 春秋十二月 閱讀(1474) |
評論 (0) |
編輯 收藏
閱讀《MySQL Innodb無鎖化設計的日志系統(tǒng)》(
https://zhuanlan.zhihu.com/p/53037796)后的心得:
與oracle日志子系統(tǒng)異曲同工的差異
1. 空洞:對于并發(fā)會話copy重做日志造成的空洞,oracle是由lgwr判斷并等待持有redo copy閂鎖的會話釋放后,這時空洞已被填充,可以刷到磁盤了;mysql則是由log writer線程監(jiān)測到空洞被填充后,再寫入一段連續(xù)最大lsn的日志到磁盤
2. io方式:oracle的lgwr是direct io;mysql的log writer是寫到os的page cache,后由獨立的log flusher線程刷盤,比oracle多了一個過程
3. 喚醒會話:oracle由lgwr掃描所有等待的會話,只喚醒滿足寫入條件(事務提交log已刷盤)的會話;mysql則由獨立的log flush notifier通過滿足條件對應的分片消息隊列來喚醒,比oracle多了一個過程
總結(jié):mysql通過原子變量來管理全局log buffer的幾個內(nèi)存位置來實現(xiàn)無鎖化,而原子操作在多核上仍不利于線性擴展。oracle的閂鎖也存在類似問題,但通過私有redo緩存和多個全局log buffer(相關(guān)閂鎖量與cpu核數(shù)正比),來提升了擴展性。故整體上oracle更優(yōu)
閱讀《MySQL/InnoDB數(shù)據(jù)克隆插件(clone plugin)實現(xiàn)剖析》(
https://zhuanlan.zhihu.com/p/76255304)后的心得:
與oracle老式熱備異曲同工的差異
1. page追蹤:oracle老式熱備實際當每行更新時將整個關(guān)聯(lián)的page記錄在redo日志中;mysql熱備則是記錄變化page的id在單獨一個地方,用于page copy階段從buffer pool讀取并發(fā)送頁數(shù)據(jù)到備庫
2. redo歸檔:oracle老式熱備在拷貝數(shù)據(jù)文件的全過程,只要數(shù)據(jù)文件被修改就會有redo歸檔;mysql熱備則僅在page copy階段啟用redo歸檔,可看做是臨時的
3. 一致性恢復:oracle老式熱備存在數(shù)據(jù)塊分離現(xiàn)象,對此應用被凍結(jié)scn及日志序列號后的redo log來恢復;mysql則通過page copy及應用clone lsn后的redo log來恢復
總結(jié):oracle老式熱備必須處于歸檔模式,由于記錄整塊而非行變化,因此重做日志寫放大而增加了cpu和io的開銷,由于可能判斷并修復分離的塊,因此延長了恢復時間;mysql通過page追蹤和臨時redo歸檔來減少應用redo的體量而縮短了恢復時間。故mysql熱備整體更優(yōu),但相對oracle的現(xiàn)代rman備份則并不更優(yōu)
posted @
2020-04-21 11:19 春秋十二月 閱讀(5975) |
評論 (0) |
編輯 收藏
描述
nginx是一款著名的高性能開源Web與反向代理服務器,支持windows和linux操作系統(tǒng),因為在windows系統(tǒng)上還不支持SCM(服務控制管理),所以只能以控制臺方式運行,但這樣并不是在后臺運行,也不能在系統(tǒng)登錄前啟動。針對這些問題,本方法通過改進源碼,使nginx良好地支持了SCM,方便了部署運行
特點
最大地復用了nginx源碼;支持SCM,并兼容控制臺運行方式;統(tǒng)一處理異常退出而報告服務停止
實現(xiàn) 變換原主函數(shù)
將原來的main函數(shù)更名為ngx_main,并增加第3個參數(shù)is_scm來標識運行方式,非0表示服務方式,0表示控制臺方式,流程如下

圖上紅色部分為插入的邏輯,其它部分為nginx原來的邏輯。由于服務初始化須將錯誤記錄在log(日志)中,所以應在初始化log模塊后調(diào)用
增加主函數(shù)
這個主函數(shù)也就是程序入口main,可被控制臺或SCM調(diào)用,當被SCM調(diào)用時,注冊服務以及啟動服務控制調(diào)度程序,流程如下

如果以命令行啟動nginx 也就是master進程(管理進程),或nginx產(chǎn)生worker進程(工作進程)時,那么以控制臺方式調(diào)用main,進而以is_scm為0調(diào)用ngx_main,當ngx_main返回時,就表示master或worker進程退出了
服務主函數(shù)
由SCM生成的一個邏輯線程調(diào)用,流程如下

這里的邏輯線程代替了nginx的master進程,到這里就表明已經(jīng)以SCM方式運行了,所以以is_scm為1調(diào)用ngx_main,當ngx_main返回時,就表明master進程退出了,應該更新服務狀態(tài)為已停止,然后返回表明當前服務結(jié)束了
服務初始化 由ngx_main調(diào)用,見變換原主函數(shù)流程圖,流程如下
由于在nginx實現(xiàn)中,有多處出現(xiàn)異常錯誤而直接退出,因此首先注冊了進程退出處理器,在其內(nèi)報告服務狀態(tài)為已停止,這樣只要當進程退出了,在SCM上就能看到已停止的狀態(tài)了
服務控制處理器 由SCM的主線程調(diào)用,流程如下
調(diào)用關(guān)系 下圖左邊為master進程調(diào)用模塊與函數(shù),右邊為worker進程調(diào)用模塊與函數(shù),委托主函數(shù)是
ngx_main
posted @
2019-11-20 19:45 春秋十二月 閱讀(868) |
評論 (0) |
編輯 收藏
部署圖 
傳統(tǒng)的vss備份架構(gòu)由于備份應用部署在應用服務器內(nèi),因此比較耗應用服務器的CPU和IO,特別是拷貝大量的文件,為了降低對應用服務器的干擾,可采用server-free架構(gòu),將耗時的拷貝移到另一機器即備份服務器實現(xiàn),而應用服務器只負責占用資源及耗時很少的打快照。這種架構(gòu)運用了vss可傳輸卷影拷貝的特性,要求快照處于共享存儲中,適用于Windows Server 2003 sp1以上版本
協(xié)作流程圖 
VSS快照代理端的SetContext要求設置成
VSS_CTX_APP_BACKUP | VSS_VOLSNAP_ATTR_TRANSPORTABLE
posted @
2019-11-06 18:01 春秋十二月 閱讀(916) |
評論 (0) |
編輯 收藏
1. 綁定變量作為一種優(yōu)化查詢處理的方法,在性能上有利有弊,是一把雙刃劍。它的優(yōu)勢在于可以共享庫緩存中的父游標,從而避免了硬解析及相關(guān)的開銷;劣勢在于因綁定變量掃視增加了查詢優(yōu)化器選擇(非常)低效執(zhí)行計劃的風險,即使支持自適應游標共享,也引入了游標感知判斷和謂詞選擇率估算的代價,而且在生成高效的執(zhí)行計劃前至少有一次是無效率的。因此,是否使用綁定變量,需要衡量實際字面值與處理數(shù)據(jù)量帶來的解析執(zhí)行的收益與損害,當損害大于收益時就不應該使用,反之當處理較少數(shù)據(jù)硬解析耗時比執(zhí)行多時,就可以使用了
2. 存儲快照一般有三種層次:物理卷、文件系統(tǒng)和應用程序
◆ 物理卷快照基于卷扇區(qū)映射表實現(xiàn),宜采用CoFW法,因為它不必每次寫io都去遍歷映射表,比RoFW快
◆ 文件系統(tǒng)快照基于inode樹即元數(shù)據(jù)復制實現(xiàn),每當寫io時更新快照或源inode的指向,必要時向上回溯至根inode。有的文件系統(tǒng)比如NetApp公司的WAFL則更優(yōu),只須復制根inode,因為每次寫io時它會變但其下所有的inode不會變
◆ 應用程序快照最典型的就是數(shù)據(jù)庫,原理本質(zhì)與上述兩種一樣,基于頁改變位圖,當page首次(相對于快照創(chuàng)建時刻)改變時拷貝到快照文件(一種稀疏文件),另外當撤消未提交事務或回滾事務時也會發(fā)生拷貝(此時快照慢慢不再稀疏),這是為了保證快照的可用一致性
3.
數(shù)據(jù)塊的加鎖有單機和分布式兩種情景,前者是為了同步單實例事務的并發(fā),后者是為了協(xié)調(diào)分布式事務的同步,并與緩存一致性協(xié)議緊密聯(lián)系。undo,redo,undo/redo三種日志對數(shù)據(jù)臟塊與提交日志記錄落盤的順序要求各不同,因此恢復方式不同。脫服務器備份架構(gòu)比較好,具有不占用應用服務器資源的優(yōu)勢,而微軟的vss可傳輸卷影拷貝提供了這一支持,足見其技術(shù)的先進前瞻性
4. Oracle的
實例恢復完全靠在線重做日志,介質(zhì)恢復必須靠歸檔重做日志,以及在線重做日志。然而在線重做日志是有限數(shù)量的,那么Oracle是怎樣保證宕機經(jīng)實例恢復后不丟數(shù)據(jù)?答案是檢查點。檢查點是數(shù)據(jù)庫中一個很重要的機制,被重做日志切換觸發(fā),由DBWn執(zhí)行刷新臟塊,并清除老的無用的在線重做日志,以允許被覆蓋
5. Linux內(nèi)核的swap高速緩存和其它的緩存(比如page緩存)不太一樣,因為它存在的主要原因不是為了減少磁盤IO提高性能,而是解決換入換出共享匿名頁同步即并發(fā)swap的問題。那么它是唯一的方法嗎?不一定,可以遍歷所有的anon_vma鏈表,查找匿名頁對應的頁框是否已建立,但該方法沒有swap緩存快。當然,在換入操作很多的情景,swap緩存確實能提高系統(tǒng)性能
6. Linux內(nèi)存回收的核心是LRU鏈表,Oracle的buffer cache也有個LRU,這兩種LRU的共同點是引用計數(shù)(標志)和非活躍鏈表,引用計數(shù)會影響一個對象是否移到非活躍鏈表,非活躍鏈表用于回收或覆蓋這個對象。對于Linux這個對象是頁框,移到非活躍鏈表取決于swap tendency;而Oracle則是數(shù)據(jù)塊buffer及其TCH
7. Linux內(nèi)核中的反向映射讓我想起了Oracle中的反向鍵索引,它們的共同點都是為了高性能,前者是為了快速定位引用同一頁框的所有頁表項,從而方便共享內(nèi)存的回收;后者是為了減少右側(cè)索引葉塊的競爭,從而降低緩沖區(qū)忙等待、提高并發(fā)量
8. mvcc與read uncommitted(簡稱RU)隔離級別的關(guān)系究竟如何?這取決于現(xiàn)代數(shù)據(jù)庫的實現(xiàn)。對于Oracle,RU和RC的讀實現(xiàn)都基于mvcc實現(xiàn),換句話說Oracle其實沒有臟讀;對于MySQL innodb引擎,mvcc不適用于RU而只適用于RC/RR級別,因為RC/RR必須讀取修改已提交的數(shù)據(jù),但基準點不同,前者查詢開始時、后者事務開始時,而RU則可讀取未提交的數(shù)據(jù),當然用mvcc模擬實現(xiàn)RU應該也可以,只需要讀取當前新版本而非舊版本
9. 借助內(nèi)核page cache的數(shù)據(jù)庫或者存儲引擎,一定程度上講,是粗暴懶惰的表現(xiàn),這會導致系統(tǒng)負載比較重的情況下,io性能很差。所以為高性能,必須得處理好direct io,設計self cache,這樣一來,就避免了浪費在原先內(nèi)核頁緩存的頁框,避免處理內(nèi)核頁緩存和預讀的多余指令而提高了系統(tǒng)調(diào)用read和write的效率,同時減少了一次數(shù)據(jù)拷貝
10. SQL半連接的本質(zhì)是在內(nèi)連接的基礎(chǔ)上對內(nèi)表去重,即使內(nèi)表有符合多個連接條件的元組,也只匹配一條,從而減少了連接返回的結(jié)果集。一般地,簡單的in、exists和any子句,都采用半連接實現(xiàn),但若內(nèi)表本身保證了唯一性,則半連接可消除轉(zhuǎn)為內(nèi)連接實現(xiàn),或者內(nèi)表數(shù)據(jù)量很小且外表存在索引,那么也會消除半連接,生成由內(nèi)表驅(qū)動外表,外表走索引的執(zhí)行計劃。由此一例看出,SQL優(yōu)化器偏愛內(nèi)連接,因為內(nèi)連接帶來了驅(qū)動表選擇和謂詞下推的靈活,便于產(chǎn)生更優(yōu)的執(zhí)行計劃
11. 從Oracle數(shù)據(jù)庫內(nèi)核角度講,游標代表SQL語句的句柄,包含了依賴對象及執(zhí)行計劃等信息,它相當于linux的文件描述符和windows的句柄。打開或緩存的游標是指對應SQL語句所占的內(nèi)存(父游標句柄、父堆0和子游標句柄的chunk)被加上kgl lock和pin鎖,意味著第三次后解析同樣的SQL不必再從library cache hash chain中加鎖查找而直接從PGA的子堆6地址中獲取并調(diào)用執(zhí)行計劃,如此優(yōu)化提高了并發(fā)度加快了查詢,這正是軟軟解析;軟軟解析前必須軟解析2次,目的是將library cache的執(zhí)行計劃在PGA中做一份鏈接,軟解析前必須硬解析,目的是將執(zhí)行計劃放在library cache中。然而,如果共享池空閑內(nèi)存不足,或者依賴對象發(fā)生DDL操作導致執(zhí)行計劃失效,那么執(zhí)行計劃所占chunk可以被覆蓋釋放,這樣一來,軟(軟)解析時就需要重新生成執(zhí)行計劃了
12. Oracle的內(nèi)存管理粗略地類似于Linux內(nèi)核,所不同的是內(nèi)存分配單元,前者叫g(shù)ranule通常大小4M~16M,后者叫page通常4K;數(shù)據(jù)塊緩沖的分配類似伙伴算法,共享池(主要用于sql緩存)的chunk分配類似slab算法,共享池中的保留池類似基于slab的內(nèi)存池
13. Oracle數(shù)據(jù)庫究竟是怎樣構(gòu)建表數(shù)據(jù)塊的讀一致性版本?這是個比較復雜、細致和有趣的問題,核心流程如下
◆ 克隆數(shù)據(jù)塊,若不存在則先從磁盤讀,下面幾步以克隆塊為目標
◆ 根據(jù)ITL中的flag及l(fā)ck,對所有已提交的事務做清除操作,即延遲塊清除。延遲塊清除為了獲取足夠精確的提交SCN填充到ITL,分2種情況,若事務表槽沒被覆蓋,則直接用其提交SCN;否則先從事務控制區(qū)獲取SCN,并判斷對于上界提交是否足夠精確,若不夠則需要回滾事務表一直找到合適的SCN或報錯ORA-01555
◆ 根據(jù)ITL中的uba,反向更改所有未提交的事務,也就是應用事務的undo記錄
◆ 根據(jù)ITL中的SCN,不斷反向更改大于目標SCN的已提交事務,直至遇見合適的已提交事務。這里也是應用undo記錄,但不同的是,除了應用行數(shù)據(jù),還會從事務的第一個undo記錄找到先前即前一個已提交事務的ITL項拷貝回當前塊的對應ITL項
14. Oracle的多版本控制機制,為dml不僅提供了一致且正確的結(jié)果,還提高了并發(fā)性,可謂魚和熊掌兼得。那么它的缺點是什么?可能會導致熱表的IO增高,因為讀一致性需要不斷回滾多個事務對數(shù)據(jù)塊的修改,直到查詢開始時的數(shù)據(jù)。事務隔離級別read committed與read uncommitted的相同是不會臟讀,區(qū)別是前者會不可重復讀或幻讀
15. Sql*plus的ARRAYSIZE對查詢數(shù)據(jù)性能有重要的影響,這個值過大過小都不好,而是要接近一個數(shù)據(jù)塊所擁有的行數(shù),如此僅一次邏輯IO就拿到了一批行。那么設置合適的ARRAYSIZE就一定能提高性能嗎?不一定,還要看所查詢的表使用了什么索引列及表數(shù)據(jù)在磁盤上的物理布局,若數(shù)據(jù)分散即聚簇因子低,則優(yōu)化器會選用全表而非索引區(qū)間掃描,去執(zhí)行這個查詢
16. IOT表如果為非主鍵列再建索引,那么就成二級索引。這時候查詢數(shù)據(jù),需要兩次掃描,一是掃描二級索引得到IOT中的位置,二是掃描IOT本身匹配那個位置,之所以這樣是因為行記錄在IOT中的位置會變。而堆組織表,僅需一次掃描索引結(jié)構(gòu),得到rowid,再直接讀磁盤獲取行記錄。因此IOT上再建二級索引,并非明智的選擇
17. 相容性矩陣是封鎖調(diào)度的核心結(jié)構(gòu); 任意一個無環(huán)優(yōu)先圖的封鎖調(diào)度都是沖突可串行化的; 基于樹協(xié)議的無環(huán)優(yōu)先圖的封鎖調(diào)度,其整個事務集合的任意一個拓撲順序都是等價可串行化的
18. 總結(jié)解決數(shù)據(jù)庫丟失更新問題的方案
◆ 對于表不會被悲觀鎖鎖定的情景:使用基于select+update的樂觀鎖方法,查詢保存前映像,以便定位更新。前映像列可為全列,或新增一個時間戳列作為版本列
◆ 對于表可能會被悲觀鎖鎖定的情景:使用select…for update nowait+update的悲觀鎖方法,可以以全列的hash(虛擬列)來定位更新
19. 如果能夠在備庫上打開閃回,那么就可以做到既讓生產(chǎn)系統(tǒng)沒有承擔閃回的開銷,又能快速地為錯誤或故障恢復到以前某個時刻。一舉兩得比較完美,重做日志的創(chuàng)新使用真是太棒了
20. Oracle的索引聚簇表是個創(chuàng)新,它能將多個不同表的行按照索引列存儲在同一塊中,屬于物理上的join,這樣一來既可減少data buffer緩存的塊數(shù)而提高效率,又可提高多個相關(guān)表連接查詢的性能,比如通過外鍵約束的父子表。最典型的應用就是數(shù)據(jù)字典,數(shù)據(jù)字典對于查詢優(yōu)化的成本估算很重要,由此可見oracle的設計之明智,mysql的innodb只有索引組織表,sql server有堆表和索引組織表,但它們都沒有索引聚簇表
21. 分布式事務處理是工程難題。Oracle的serializable串行隔離級別以樂觀鎖實現(xiàn),所以并發(fā)度與非串行相當,需要注意的是:串行并不是說一個事務提交了才能處理下一個,而是多個事務間沒有沖突表現(xiàn)地像只有一個事務在運行,否則Oracle的serializable級別就不存在拋出ORA-08177錯誤了
22. 理清read uncommitted事務隔離級別的鎖策略:讀不加共享鎖,寫加排它鎖直至提交,這里的鎖是指lock;塊的緩沖區(qū)并發(fā)操作必須加鎖,這里的鎖是指latch,若不加,那臟讀讀到的數(shù)據(jù)可能是錯的。臟讀隔離級別允許讀修改但未提交的行記錄,這意味著讀不能被寫阻塞,也不能阻塞寫,所以不會申請共享鎖(顯式鎖定讀除外)
23. 與MySQL不同,Oracle的行鎖無需索引列的限制,是真正的行鎖,其實現(xiàn)為數(shù)據(jù)塊的屬性而非傳統(tǒng)的鎖管理器,但是它需要在事務commit或rollback時才釋放,如果存在慢sql,那么導致的阻塞會比較嚴重
24. 隔離是實現(xiàn)安全的一種辦法,其結(jié)果常被稱作“沙箱”。從這個意義上講Oracle很明智,因為它的事務沒有也不需要read uncommitted隔離級別,Oracle最低且默認的隔離級別是read committed,因為它有基于undo的多版本控制,天生非阻塞讀,根本不會臟讀。我想不出read uncommitted有什么好處,除了非阻塞讀及可能的高并發(fā),要謹慎臟讀是危險不安全的
25. windows內(nèi)存映射和linux內(nèi)存映射的實現(xiàn)機制不太一樣,前者使用了內(nèi)存區(qū)section的專用數(shù)據(jù)結(jié)構(gòu)而不像后者重用了頁緩存,內(nèi)存區(qū)的映射完全由內(nèi)存管理器負責包括物理頁分配及臟頁面寫入器,與緩存管理器無關(guān);緩存管理器基于內(nèi)存管理器維護了文件塊數(shù)據(jù)的視圖,并提供了自己的延遲寫入器。這兩種寫入器即回刷,獨立并行地工作
posted @
2019-11-06 11:29 春秋十二月 閱讀(8087) |
評論 (0) |
編輯 收藏
腳本概述 由于某些sdk或軟件依賴眾多的第三方庫,而從官網(wǎng)下載到windows主機或從linux傳到windows時,所依賴的so庫往往丟失符號鏈接,給編譯運行帶來不便,因此編寫了
ctlsolink腳本,用于自動為單個so或某目錄下的眾多so或創(chuàng)建/刪除一級/二級符號鏈接。該腳本的用法如下:
● 第1參數(shù)為mk或rm子命令,mk表示創(chuàng)建,rm表示刪除
● 第2參數(shù)為文件或目錄
● 第3參數(shù)是可選的-r,且只能是-r,如果指定了,則表示不斷遞歸子目錄
腳本實現(xiàn)
考慮到so庫帶版本一般多為libx.so.1,libx.so.1.2,libx.so.1.2.3這三種形式(x為庫名),對于前一種創(chuàng)建/刪除一級符號鏈接即可,后兩種則創(chuàng)建/刪除二級符號鏈接。為了精確地抽出一級和二級鏈接名稱,這里使用awk來匹配,用shell變量的最短匹配模式從尾部逐步刪除點號及數(shù)字,核心代碼如下
1
if [ "$dir" != "$self_dir" ] || [ "$name" != "$self_name" ]; then
2
if echo $name | awk '{if($0~/\.so\.[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}$/) exit 0; else exit 1}'; then
3
link_name=${name%.[0-9]*}
4
link_name=${link_name%.[0-9]*}
5
link_name=${link_name%.[0-9]*}
6
link_name2=${name%.[0-9]*}
7
link_name2=${link_name2%.[0-9]*}
8
elif echo $name | awk '{if($0~/\.so\.[0-9]{1,}\.[0-9]{1,}$/) exit 0; else exit 1}'; then
9
link_name=${name%.[0-9]*}
10
link_name=${link_name%.[0-9]*}
11
link_name2=${name%.[0-9]*}
12
elif echo $name | awk '{if($0~/\.so\.[0-9]{1,}$/) exit 0; else exit 1}'; then
13
link_name=${name%.[0-9]*}
14
else
15
return
16
fi
17
18
if [ $do_mk = "yes" ]; then
19
#echo "name=$name, link_name=$link_name, link_name2=$link_name2"
20
if [ -n "$link_name2" ]; then
21
ln -sf $name $link_name2
22
ln -sf $link_name2 $link_name
23
else
24
ln -sf $name $link_name
25
fi
26
else
27
if [ -n $link_name2 ]; then
28
rm -f $link_name2
29
fi
30
rm -f $link_name
31
fi
32
fi 要注意的是,這兒不能使用%%刪除最長匹配的尾部來得到
link_name,因為它的模式是
.[0-9]*,這可能會錯誤地匹配了so前的部分,比如libx.1.so.2得到libx,而期望的是libx.1.so
完整腳本下載:
ctlsolink 運行效果 初始狀態(tài)

運行ctlsolink創(chuàng)建軟鏈接后

運行ctlsolink刪除軟鏈接后
posted @
2019-11-05 18:17 春秋十二月 閱讀(1957) |
評論 (0) |
編輯 收藏
為了減少程序中的硬編碼,靈活按需管理字符串空間,使用了ATL中的CString類,代碼如下
1 CString bstrComPathName;
2 WCHAR componentPathName[1];
3 DWORD dwNameLen = 1;
4
5 if (!GetComputerNameEx(ComputerNamePhysicalDnsFullyQualified, componentPathName, &dwNameLen))
6 {
7 DWORD dwErr = GetLastError();
8 if(ERROR_MORE_DATA==dwErr)
9 {
10 if (!GetComputerNameEx(ComputerNamePhysicalDnsFullyQualified, bstrComPathName.GetBuffer(dwNameLen), &dwNameLen))
11 {
12 zlog_error(g_zc, "GetComputerNameEx with ComputerNamePhysicalDnsFullyQualified fail: %d", GetLastError());
13 return -1;
14 }
15 }
16 else
17 {
18 zlog_error(g_zc, "GetComputerNameEx with ComputerNamePhysicalDnsFullyQualified for fail: %d", dwErr);
19 return -1;
20 }
21 }
22 bstrComPathName.ReleaseBuffer();
需要注意的是,GetBuffer方法雖提供方便了直接修改CString對象的內(nèi)部緩沖區(qū),但違背了面向?qū)ο笤O計的原則(由公開方法修改內(nèi)部數(shù)據(jù)),因此不保證對象的完整性,在操作完成后一定要調(diào)用ReleaseBuffer
posted @
2019-07-31 12:51 春秋十二月 閱讀(7974) |
評論 (0) |
編輯 收藏
在GNU make中文手冊這本書中,3.14節(jié)講到了依賴文件的自動生成,如下圖

圖中的規(guī)則對C源文件和Makefile在同一目錄,是正確的。但是不在同一目錄的又希望依賴文件在對應的目錄下,比如src/log/log_file.c,希望依賴文件log_file.d生成在src/log/下。因為gcc(aix平臺xlc編譯器亦如此)生成的依賴文件內(nèi)容中目標文件名沒有帶路徑,例如下所示
log_file.o: src/log/log_file.c src/log/log_file.h src/log/log_type.h \
src/log/../base/io_ext.h
所以sed就找不到src/log/log_file.o而替換了,改正后的規(guī)則如下
%.d: %.c
$(CC) $(CFLAGS) $(INCS) $< $(MFLAGS) $@.$$$$;\
sed 's,$(*F).o[ :]*,$*.o $@: ,g' < $@.$$$$ > $@;\
$(RM) $@.$$$$
該規(guī)則對C源文件和Makefile在同一目錄也適合,生成后的依賴文件內(nèi)容如下
src/log/log_file.o src/log/log_file.d: src/log/log_file.c src/log/log_file.h src/log/log_type.h \
src/log/../base/io_ext.h
posted @
2018-11-16 12:08 春秋十二月 閱讀(861) |
評論 (0) |
編輯 收藏