• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-159  評論-223  文章-30  trackbacks-0
             
            存儲格式
               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:99999(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:-99999(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)中自帶了vshadowbetest工具源碼,經(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 | aw'{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 [ -"$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)編輯 收藏
            僅列出標題
            共16頁: First 3 4 5 6 7 8 9 10 11 Last 
            AV无码久久久久不卡蜜桃| 久久成人精品视频| 91精品国产综合久久香蕉| 囯产极品美女高潮无套久久久| 国产日韩久久久精品影院首页| 高清免费久久午夜精品| 色偷偷久久一区二区三区| 久久夜色精品国产亚洲| 免费久久人人爽人人爽av| 久久综合九色综合久99| 欧美日韩成人精品久久久免费看 | 久久综合久久鬼色| 久久精品国产99久久丝袜| 久久www免费人成精品香蕉| 久久精品成人免费观看97| 91亚洲国产成人久久精品| 国产69精品久久久久9999| 国产精品熟女福利久久AV| 久久男人中文字幕资源站| 久久精品视频一| 漂亮人妻被黑人久久精品| 1000部精品久久久久久久久| 丁香五月网久久综合| 国产福利电影一区二区三区久久久久成人精品综合 | 久久久久夜夜夜精品国产| 久久久国产精品网站| 精品久久人人做人人爽综合| 精品免费久久久久国产一区| 日本久久中文字幕| 亚洲成色www久久网站夜月| 久久久久久夜精品精品免费啦 | AV色综合久久天堂AV色综合在| A狠狠久久蜜臀婷色中文网| 国内精品久久久久久久涩爱| 色综合久久88色综合天天 | 亚洲愉拍99热成人精品热久久| 国产精品一区二区久久| 亚洲精品国产自在久久| 狼狼综合久久久久综合网| 国产亚洲成人久久| 五月丁香综合激情六月久久 |