Writing and using Libraries
链接分为动态链接和静态链接。
Archives
archive(静态链接) 为目标文件的集合,linker 从 archive 文件中找到 obj 文件进行链接。
% ar cr libtest.a test1.o test2.o
创建库文件 libtest.a(类似 windows 下 test.lib),当 linker 处理 archive 文件的时候,将在库文件中查找当前已经处理但是还没定义的 symbols。所以库文件应该出现在命令的最后。
% gcc -o app app.o -L. -ltest
Shared Library
Shared lib 和 archive 的两个区别:1,当进行的是动态链接,最后得到的可执行程序中不包含实际库中的执行代码,只是一个对库的引用。所以动态链接最后得到的可执行程序要小一些。2 多个程序可以共享动态链接库,动态链接库不只是 obj 文件的集合,其中是单一的一个 obj 文件,包含了库中所有的信息,所以一个程序动态加载 shared lib 的时候是把库中所有的东西都加载了,而不是所引用的那部分。
% gcc -c -fPIC test1.c
% gcc -shared - fPIC libtest.so test1.o test2.o
-fPIC 选项指编译为位置独立的执行代码,这样可以动态加载,产生 libtest.so 文件。
默认的库文件寻找路径变量:LD_LIBRARY_PATH
库文件之间的依赖关系:如果是动态链接,链接库会自动寻找到自己所依赖的其他库文件,如果是静态链接,必须为 linker 提供所有依赖的库文件名称。
% gcc -static -o tifftest tifftest.c -ltiff -ljpeg -lz -lm
上面例子中 tiff 依赖 jpeg 库,因为是-static 链接,必须指明所有依赖的库文件。
Pros and Cons
动态链接的优势:可以减少可执行文件的 size,如果库文件进行升级,原程序可以不用重新链接。如果是静态链接,库文件改变了程序要重新进行 link。
也有一些特殊情况必须使用 static link。
动态加载和卸载库
void* handle = dlopen (“libtest.so”, RTLD_LAZY);
void (*test)() = dlsym (handle, “my_function”);
(*test)();
dlclose (handle);
上面例子中打开 libtest.so 动态链接库,找到 my_function 定义,执行,然后卸载库文件。
进程
创建进程
using system
#include <stdlib.h>
int main ()
{
int return_value;
return_value = system ("ls -l /");
return return_value;
}
system 将执行/bin/sh,然后执行命令,因为不同系统中/bin/sh 所链接的 shell 不同,所以会导致执行差异,同时这种方式存在安全隐患。
using fork and exec
fork 创建一个子进程,fork 的返回值用来区别父进程和子进程。子进程将和拷贝父进程一些信息,更详细的东西在这本书内没说明。
exec 函数家族,fork 创建一个子进程,用 exec 在子进程中执行命令。
process scheduling
nice 命令可以调节 process 的优先权值。
niceness value 越大,进程的优先权越低,越小进程的优先权越高。一般进程的 niceness value 为 0。只有 root 的进程可以减少一个进程的 niceness value。
signal
signal is asynchronous:进程收到信号的时候会立即处理信号,处理信号的一般方式分为几类:忽略,执行默认处理,执行特定的处理程序。
因为信号处理是异步的,所以在信号处理程序中尽量不要执行 IO,或者调用库函数。信号处理函数应该作最少量的工作,尽快返回到主流程中,或者干脆结束掉程序。一般只是设置变量表明某个信号发生了,主程序定时检查变量再处理。SIGTERM 和 SIGKILL 区别,前一个可能被忽略,后一个不能被忽略。
改变 sig_atomic_t 的值的操作是原子性的。
process exit
exit(int return_value) 函数退出一个进程,并把 exit_code 告诉父进车。kill(pid_t,KILL_TYPE) 向某个进程发送相应的退出信号。
wait 函数家族,让父进程等待某个子进程的结束。WIFEXITED 宏判断子进程是否正常退出或者是由于其他原因意外退出。
zombie process(僵死进程) 为一个进程已经退出,但是没有进行善后处理。一个父进程有责任处理子进程的善后处理,wait 函数即为此用,父进程调用 wait 一直被阻塞 (当子进程没有退出的时候),子进程退出后 wait 函数返回。如果父进程没有为已经退出的子进程处理善后,子进程将变为 init 的子进程,然后被处理删除。
一种更好的处理方法是当子进程退出的时候发信号通知父进程,有几种方式可以实现 (进程间通信),其中一种比较方便的方式是父进程处理 SIGCHLD 信号。
Threads
线程作为亲量级进程,切换引起的开销更小,一个进程的多个子线程共享进程的资源。
create thread
创建线程:
pthread_create(&thread_id, NULL(pointer_to_thread_info),
&thread_func, NULL(argument))
线程的执行顺序是异步的,不能假设其执行顺序。向 thread 传递数据:可以通过 pthread_create 的地四个参数,传递一个 void* 的指针,指针指向一个数据结构体。注意在多线程中的数据空间的销毁。
More about thread_id:
if (!pthread_equal (pthread_self (), other_thread))
pthread_join (other_thread, NULL);
Thread Attributes,为了设定线程的某些属性,detach 线程退出后自动回首资源,joinable 则等到另一个线程调用 pthread_jion 获得其返回值。
Thread-specific data:每个线程都有一份自己的拷贝,修改自己的数据不会影响到其他线程。
Cleanup Handlers:使用 pthread_cleanup_push(function,param) 和 pthread_cleanup_pop(int) 在线程退出的时候自动调用清理函数,释放资源。
多线程程序可能出现的问题:竞争,需要使用 atomic 操作。
互斥锁
只有一个线程能够拥有,此时其他线程访问互斥锁将被阻塞。
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL);
//或者 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//线程中使用 pthread_mutex_lock 和 pthread_mutex_unlock 来锁住和解锁互斥锁,
<h3><a name="sec12" id="sec12"></a>
Semaphores for Threads</h3>
<p class="first">sem_t 可以作为一个 share counter 来使用,
```c
sem_t job_queue_count;
//initialize
sem_init(&job_queue_count,0,0);
//wait for
sem_wait(&job_queue_count);
//lock mutext
//and do somethting
//unlock
//new job
sem_post(&job_queue_count)
Threads VS Process
Guidelines:
1,所有线程所执行的指令必须是在一个可执行文件里面,而多进程可以执行多个命令。
2,因为多个线程共享相同的虚拟内存地址,所以一个线程的错误可能会影响到其他线程,而多进程程序中一个进程的错误不会影响到其他进程。
3,为新进程拷贝内存将增加开销,但是只有在新进程写其内存的时候才会进行拷贝 (写拷贝)。
4,多线程适用于多个相似的执行任务,而多进程可以执行不同类型的任务。
5,多个线程中共享数据要容一些,但是也会产生相关问题 (条件竞争,死锁),多个进程共享数据难一些,使用 IPC 机制,虽然实现要难一些,但是不容易出现并发 bug。
Interprocess Communication
Share Memory
share Memeory 是最简单的进程间共享数据的方式。
Allocation
shmget 函数创建或者访问一个已经存在的 share mem。
int segment_id = shmget (shm_key, getpagesize (),
IPC_CREAT | S_IRUSR | S_IWUSER);
Attachment and Detachment
函数 shmat(SHMID,pointer to address,flag) 使得一个进程 attach 到一个共享内存。进程通过 fork 创建的子进程也将继承这一共享内存。函数 shmdt(address) 将 detach 共享内存。
int segment_size;
const int shared_segment_size=0x6400;
//allocate a shared mem
segment_id=shmget(IPC_PRIVATE,shared_segment_size,
IPC_CREAT|IPC_EXCL|S_IRUSR|S_IWUSR);
//atach the share mem
share_memory = (char*)shmat(segment_id,0,0);
printf("share memory attached at addreass %p\n",share_memory);
Control share mem
函数调用 exit 或者 exec 可以 detach 一个共享内存,但是并没有释放它。
必须调用 shmctl 去释放其空间。ipcs -m 命令可以查看系统中当前的 share mem 的信息,如果没有删除遗留的 shared mem,其 nattch 为 0。可以使用 ipcrm shm segment_id 删除。
Process Semaphores
semaphore 和 shared memory 的使用方式类似,可以通过 semget,shmctl 创建和删除,提供的参数表明要创建 semaphore。
没详细说,查看其他书。
Mapped memory
Mapped memory 是不同进程可以通过一个公用的共享文件进行交流。Mapped mem 在进程是进程和文件的一个桥梁,linux 通过把文件映射到虚拟内存,这样进程可以像访问普通内存一样访问该文件。
void* mmap(address,LENGTH,prot_option,option,file_rp,pos) //将一个文件映射到 address,如果不提供系统将映射到合适的地址
munmap(file_memory,FILE_LENGTH);// 释放 memory
设置了 MAP_SHARED,多个进程可以通过同一文件访问该内存区。
管道
pipe
int pipe_fds[2];
int read_fd;
int write_fd;
pipe (pipe_fds);
read_fd = pipe_fds[0];
write_fd = pipe_fds[1];
pipe_fds[0] 为 reading file desc,pipe_fds1为 writing file desc。Pipe 只能用于同一个进程的子进程之间。
dup2 重定向标准输入输出符。
popen,pclose 很方便,FILE* stream=popen("progam","w") 向 program 发送。pclose(stream) 关闭。
FIFO
为有名字的 pipe,任何不相关的两个进程可以通过 fifo 来进行数据传递。mkfifo 函数创建 FIFO。
Socket
系统调用:
socket-- Creates a socket close -- Destroys a socket connect -- Creates a connection between two sockets bind -- Labels a server socket with an address listen -- Configures a socket to accept conditions accept -- Accepts a connection and creates a new socket for the connection
Unix-domain sockets 能用于同一机器上的进程通信。Internet-domain sockets 用于不同机子上的通信。
struct sockaddr_in addr 类型变量为其地址结构。
addr.sin_family=AF_INET
addr.sin_addr 存储一个 32bit 的 IP 地址。
只是给了两个程序例子,详细内容看网络编程相关书籍。
Mastering Linux
Device
分为字符设备和块设备,块设备可一随机访问,字符设备提供流。一般应用程序不会直接访问块设备,而是通过系统调用来使用块设备。
设备号,主设备号是根据设备类型分的,从设备号根据具体设备分。
cat /proc/devices 查看设备类型和主设备号。
Device Entry
只有 root 的进程可以通过 mknod 创建新的 Device Entry。
mknod name b/c 主设备号 从设备号
linux 目录/dev 下面是系统所支持的 Device Entry。
字符设备可以像一般文件一样访问,甚至可以用重定向去访问。
cat somefile > /dev/audio 可以发出声音了
特殊设备:/dev/null /dev/zero /dev/full /dev/random /dev/urandom
Loopback Devices:环回设备,在文件系统上新建一个普通文件,可用于模拟特定设备,比如软盘。
也可把实际设备中的内容拷贝到其中,比如把光盘中的内容拷贝到新建的一个 cdrom-image 中。
/proc
mount 命令可以看到一行输出:proc on /proc type proc (rw,noexec,nosuid,nodev)
/proc 包含系统的一些配置信息,不和任何设备相关联。
$cat /proc/version 查看内核版本 $cat /proc/cpuinfo 查看 cpu 信息
/proc 目录下同时包含系统中当前的进程信息,由于权限设置,有的只能由进程本身访问。可以通过访问文件获取系统中进程的相关信息,
比如参数,运行环境,内存使用信息等等。
Linux system call
system call 和一般的 C 库函数的区别:系统调用一般通过门陷入实现,是系统内核和用户程序的接口,运行过程中会进入系统内核。C 库函数一般和普通的函数没有区别。
strace:该命令可以追踪一个程序执行过程中的调用的 system call。
access:测试进程对于一个文件的权限。 int access(path,bit_flag),注意返回值和 errno。
fcntl:锁住文件和控制文件操作。
fsync,fdatasync:flush disk buffer。
getrlimit,setrlimit:资源限制设置。
getusage:获取进程的统计信息。
gettimeofday:获取 wall_clock time。
mlock:锁住一段物理内存,使得该内存不能因为 swap 换出,一些速度要求很高的和安全性要求很高的代码会使用这个功能。 mlock(address,mem_length)
mprotect:设置内存的权限。
nanosleep:高分辨率睡眠函数。
readlink:read symbolic links。
sendfile:Fast file Transfer。
setitimer:定时器。
sysinfo:获取系统统计信息。
uname:获取系统版本信息和硬件信息。
Inline Assembly Code
/usr/include/asm/io.h 定义了汇编代码中能够直接访问的端口。
/usr/src/linux/include/asm and /usr/src/linux/include/asm-i386 linux 内核中汇编代码头文件
/usr/src/linux/arch/i386/ and /usr/src/linux/drivers/ 汇编代码
当使用特定平台的汇编代码时使用宏和函数来简化兼容问题。
Security
用户组 文件 进程权限
用户和组的概念
超级用户 无穷权力
proccess user id 和 proccess group id。进程开始的时候其 id 和启动该程序的用户信息相同。
文件权限 chmod stat(filename,&(struct stat))
program without Execution Permissions: a security hole。 其他用户能够拷贝该文件,然后修改其权限。
Sticky bit:用于文件夹,当一个文件夹的 sticky bit 设置了后,要删除该文件夹下的一个文件必须拥有对该文件的拥有权,即使已经拥有该文件夹访问权。Linux 下的/tmp 设置了 sticky bit。
Real and Effective ID::EID 代表进程所具有的系统权限,如果是非 root 用户,EID=RID;只有 root 用户可以改变它的 EID 为任何有效的用户 ID。
su 命令:是一个 setuid 程序,当程序执行的时候其 EID 是文件的拥有者,而不是启动程序的用户号。chmod a+s 使得文件有这个属性。
缓冲区漏洞
如果栈中有固定长度的输入区,则会含有缓冲区漏洞。最通常的形式:
char username[32];
/ Prompt the user for the username. /
printf (“Enter your username: “);
/ Read a line of input. /
gets (username);
/ Do other things here… /
攻击者可以故意使得缓冲区读满,然后在超出的区域植入想执行的代码段,获得控制权。
Race Conditions in /tmp
攻击者先创建一个链接,如果应用程序在/tmp 下创建打开一个相同名称的文件,所有写入的数据将传送到链接所指向的文件里。
解决方法:在文件名称内使用 Random,open 函数使用 O_EXCL 参数,如果文件存在则失败,打开一个文件后用 lstat 查看是否是链接文件,检查文件的所有者是否和进程所有者一样。
/tmp 文件不能挂载在 NFS 下,因为 O_EXCL 不能在 NFS 文件系统下使用。
system ,popen 函数的危险
替代使用 exec 族函数。