IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    《Advanced linux progamming》笔记

    Yukang (moorekang@gmail.com)发表于 2011-06-14 00:00:00
    love 0

    Advanced linux progamming

    Writing and using Libraries
    Archives
    Shared Library
    Pros and Cons
    动态加载和卸载库
    进程
    创建进程
    signal
    process exit
    Threads
    互斥锁
    Semaphores for Threads
    Threads VS Process
    Interprocess Communication
    Share Memory
    Process Semaphores
    Mapped memory
    管道
    Socket
    Mastering Linux
    Device
    Device Entry
    /proc
    Linux system call
    Inline Assembly Code
    Security
    用户组 文件 进程权限
    缓冲区漏洞
    Race Conditions in /tmp
    system ,popen函数的危险

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



沪ICP备19023445号-2号
友情链接