Yukang's Page

《Advanced linux progamming》笔记

2011-06-14

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来锁住和解锁互斥锁,


Semaphores for Threads

sem_t 可以作为一个share counter 来使用,


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族函数。


使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章