PILE讀書筆記_進程環境
進程是操作系統運行程序的一個實例, 也是操作系統分配資源的單位。 在Linux環境中, 每個進程都有獨立的進程空間, 以便對不同的進程進行隔離, 使之不會互相影響。
atexit函數
1 #include <stdlib.h> 2 int atexit(void (*function)(void));
atexit用於註冊進程正常退出時的回調函數。 若註冊了多個回調函數, 最後的調用順序與註冊順序相反;類似於棧。
使用atexit註冊的退出函數是在進程正常退出時, 才會被調用。 這裏的正常退出是指, 使用exit退出或使用main中最後的return語句退出。 若是因為收到信號而導致程序退出,
putenv函數
1 #include <stdlib.h> 2 int putenv(char *string);
putenv用於增加或修改當前的環境變量。 string的格式為“名字=值”。 如果當前環境變量沒有該名稱的環境變量, 則增加這個新的環境變量; 如果已經存在, 則使用新值。如果非要用putenv來設置環境變量, 就必須要保證參數是一個長期存在的內容。 因此, 只能選擇全局變量、 常量或動態內存等。
比如:
1 #include <stdlib.h> 2#include <stdio.h> 3 4 static void set_env_string(void) 5 { 6 char test_env[] = "test_env=test"; 7 if (0 != putenv(test_env)) { 8 printf("fail to putenv\n"); 9 } 10 printf("1. The test_evn string is %s\n", getenv("test_env")); 11 } 12 13 static voidshow_env_string(void) 14 { 15 printf("2. The test_env string is %s\n", getenv("test_env")); 16 } 17 18 int main() 19 { 20 set_env_string(); 21 show_env_string(); 22 return 0; 23 }
運行結果:
改為全局變量之後就正確了:
1 #include <stdlib.h> 2 #include <stdio.h> 3 4 char test_env[] = "test_env=test"; 5 6 static void set_env_string(void) 7 { 8 if (0 != putenv(test_env)) { 9 printf("fail to putenv\n"); 10 } 11 printf("1. The test_evn string is %s\n", getenv("test_env")); 12 } 13 14 static void show_env_string(void) 15 { 16 printf("2. The test_env string is %s\n", getenv("test_env")); 17 } 18 19 int main() 20 { 21 set_env_string(); 22 show_env_string(); 23 return 0; 24 }
運行結果:
setenv函數
1 #include <stdlib.h> 2 int setenv(const char *name, const char *value, int overwrite);
為了杜絕上面putenv的缺點,可以用setenv函數
參數說明:
(1)name: 要加入的環境變量名稱。
(2)value: 該環境變量的值。
(3)overwrite: 用於指示是否覆蓋已存在的重名環境變量。
1 #include <stdlib.h> 2 #include <stdio.h> 3 static void set_env_string(void) 4 { 5 setenv("test_env", "test", 1); 6 printf("1. The test_evn string is %s\n", getenv("test_env")); 7 } 8 9 static void show_env_string(void) 10 { 11 printf("2. The test_env string is %s\n", getenv("test_env")); 12 } 13 14 int main() 15 { 16 set_env_string(); 17 show_env_string(); 18 return 0; 19 }
運行結果:
動態庫和靜態庫
靜態庫在鏈接階段, 會被直接鏈接進最終的二進制文件中, 因此最終生成的二進制文件體積會比較大,但是可以不再依賴於庫文件。 而動態庫並不是被鏈接到文件中的, 只是保存了依賴關系, 因此最終生成的二進制文件體積較小, 但是在運行階段需要加載動態庫。
靜態庫
在鏈接階段,會將匯編生成的目標文件.o與引用到的庫一起鏈接打包到可執行文件中。因此對應的鏈接方式稱為靜態鏈接。
試想一下,靜態庫與匯編生成的目標文件一起鏈接為可執行文件,那麽靜態庫必定跟.o文件格式相似。其實一個靜態庫可以簡單看成是一組目標文件(.o/.obj文件)的集合,即很多目標文件經過壓縮打包後形成的一個文件。靜態庫特點總結:
l 靜態庫對函數庫的鏈接是放在編譯時期完成的。
l 程序在運行時與函數庫再無瓜葛,移植方便。
l 浪費空間和資源,因為所有相關的目標文件與牽涉到的函數庫被鏈接合成一個可執行文件。
下面編寫一些簡單的四則運算C++類,將其編譯成靜態庫給他人用,頭文件如下所示:
StaticMath.h頭文件 |
#pragma once class StaticMath { public: StaticMath(void); ~StaticMath(void);
static double add(double a, double b);//加法 static double sub(double a, double b);//減法 static double mul(double a, double b);//乘法 static double div(double a, double b);//除法
void print(); }; |
Linux下使用ar工具、Windows下vs使用lib.exe,將目標文件壓縮到一起,並且對其進行編號和索引,以便於查找和檢索。一般創建靜態庫的步驟如圖所示:
通過上面的流程可以知道,Linux創建靜態庫過程如下:
l 首先,將代碼文件編譯成目標文件.o(StaticMath.o)
g++ -c StaticMath.cpp |
註意帶參數-c,否則直接編譯為可執行文件
l 然後,通過ar工具將目標文件打包成.a靜態庫文件
ar -crv libstaticmath.a StaticMath.o |
生成靜態庫libstaticmath.a。
大一點的項目會編寫makefile文件(CMake等等工程管理工具)來生成靜態庫,輸入多個命令太麻煩了。
編寫使用上面創建的靜態庫的測試代碼:
測試代碼: |
#include "StaticMath.h" #include <iostream> using namespace std;
int main(int argc, char* argv[]) { double a = 10; double b = 2;
cout << "a + b = " << StaticMath::add(a, b) << endl; cout << "a - b = " << StaticMath::sub(a, b) << endl; cout << "a * b = " << StaticMath::mul(a, b) << endl; cout << "a / b = " << StaticMath::div(a, b) << endl;
StaticMath sm; sm.print();
system("pause"); return 0; } |
Linux下使用靜態庫,只需要在編譯的時候,指定靜態庫的搜索路徑(-L選項)、指定靜態庫名(不需要lib前綴和.a後綴,-l選項)。
# g++ TestStaticLibrary.cpp -L../StaticLibrary -lstaticmath
l -L:表示要連接的庫所在目錄
l -l:指定鏈接時需要的動態庫,編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.a或.so來確定庫的名稱。
動態庫
為什麽需要動態庫,其實也是靜態庫的特點導致。
l 空間浪費是靜態庫的一個問題。
l 另一個問題是靜態庫對程序的更新、部署和發布頁會帶來麻煩。如果靜態庫liba.lib更新了,所以使用它的應用程序都需要重新編譯、發布給用戶(對於玩家來說,可能是一個很小的改動,卻導致整個程序重新下載,全量更新)。
動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入。不同的應用程序如果調用相同的庫,那麽在內存裏只需要有一份該共享庫的實例,規避了空間浪費問題。動態庫在程序運行是才被載入,也解決了靜態庫對程序的更新、部署和發布頁會帶來麻煩。用戶只需要更新動態庫即可,增量更新。
動態庫特點總結:
l 動態庫把對一些庫函數的鏈接載入推遲到程序運行的時期。
l 可以實現進程之間的資源共享。(因此動態庫也稱為共享庫)
l 將一些程序升級變得簡單。
l 甚至可以真正做到鏈接載入完全由程序員在程序代碼中控制(顯示調用)。
編寫四則運算動態庫代碼:
DynamicMath.h頭文件 |
#pragma once class DynamicMath { public: DynamicMath(void); ~DynamicMath(void);
static double add(double a, double b);//¼Ó·¨ static double sub(double a, double b);//¼õ·¨ static double mul(double a, double b);//³Ë·¨ static double div(double a, double b);//³ý·¨ void print(); }; |
l 首先,生成目標文件,此時要加編譯器選項-fpic
g++ -fPIC -c DynamicMath.cpp |
-fPIC 創建與地址無關的編譯程序(pic,position independent code),是為了能夠在多個應用程序間共享。
l 然後,生成動態庫,此時要加鏈接器選項-shared
g++ -shared -o libdynmath.so DynamicMath.o |
-shared指定生成動態鏈接庫。
其實上面兩個步驟可以合並為一個命令:
g++ -fPIC -shared -o libdynmath.so DynamicMath.cpp |
編寫使用動態庫的測試代碼:
測試代碼: |
#include "../DynamicLibrary/DynamicMath.h"
#include <iostream> using namespace std;
int main(int argc, char* argv[]) { double a = 10; double b = 2;
cout << "a + b = " << DynamicMath::add(a, b) << endl; cout << "a - b = " << DynamicMath::sub(a, b) << endl; cout << "a * b = " << DynamicMath::mul(a, b) << endl; cout << "a / b = " << DynamicMath::div(a, b) << endl;
DynamicMath dyn; dyn.print(); return 0; } |
引用動態庫編譯成可執行文件(跟靜態庫方式一樣):
g++ TestDynamicLibrary.cpp -L../DynamicLibrary -ldynmath |
然後運行:./a.out,發現竟然報錯了!!!
可能大家會猜測,是因為動態庫跟測試程序不是一個目錄,那我們驗證下是否如此:
發現還是報錯!!!那麽,在執行的時候是如何定位共享庫文件的呢?
1) 當系統加載可執行代碼時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑。此時就需要系統動態載入器(dynamic linker/loader)。
2) 對於elf格式的可執行程序,是由ld-linux.so*來完成的,它先後搜索elf文件的 DT_RPATH段—環境變量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib 目錄找到庫文件後將其載入內存。
如何讓系統能夠找到它:
l 如果安裝在/lib或者/usr/lib下,那麽ld默認能夠找到,無需其他操作。
l 如果安裝在其他目錄,需要將其添加到/etc/ld.so.cache文件中,步驟如下:
n 編輯/etc/ld.so.conf文件,加入庫文件所在目錄的路徑
n 運行ldconfig ,該命令會重建/etc/ld.so.cache文件
我們將創建的動態庫復制到/usr/lib下面,然後運行測試程序。
本文中靜態庫和動態庫部分參考自:
http://www.cnblogs.com/skynet/p/3372855.html
PILE讀書筆記_進程環境