关于APUE的一些整理

7进程环境数据库

   7.2 内核执行C程序时,在调用main前先调用一个启动例程。编译器调用连接器将此例程指定为程序起始地址,这个启动例程从内核取得命令行参数和环境变量值,而后调用main。编程

       7.3 进程终止的8种方式:安全

       正常的5种:从main返回;调用exit;调用_exit和_Exit;最后一个线程从其启动例程返回;最后一个线程调用pthread_exit;多线程

       异常的3种:调用abort,接到一个信号终止,最后一个线程对取消请求作出回应。 异步

       exit函数会执行一系列的清理工做,而后才会进入内核,而_exit和_Exit则会直接进入内核。例如当在程序中使用atexit系统调用注册了清理函数,在使用exit退出时,注册的函数将会按照顺序依次执行;而使用_exit _Exit函数退出时,注册的函数将不会执行。函数

        7.6 C程序的存储布局空间工具

clip_image001

课后习题中有一个与本节相关的,即 为何有些系统不容许程序访问本身的低地址0,由此图可得知。布局

7.8存储器分配的三个函数 malloc calloc(此函数分配空间后空间中每一位被初始化为0),realloc。优化

这三个函数返回的指针必定是适当对齐的以使其用于任何数据对象。要知足最大的数据类型的对齐。好比存在int,char,double等数据时,指针的值要按照对齐要求最多的数据类型double来肯定为8的倍数。ui

这三个alloc程序返回void*,若是程序中包含了#include<stdlib.h>就无需再进行强制类型转换。

这三个函数使用sbrk调用实现,sbrk能够扩充或缩小进程的存储空间,可是大多数malloc和free都不减少进程空间,一般将free掉的内存继续放在进程的malloc池中而不返回给内核。

大多数malloc分配的空间要比要求的更大一点,由于要记录管理信息,该块的长度,下一个块地址等等。

7.10 setjmp和longjmp与goto函数的区别,前者跨越函数(在多个函数间)进行跳转,而goto只能在一个函数中进行跳转。

当调用跳转时,自动、寄存器变量和易失变量的变化较为复杂,书中例子以下

static int globval;

1: int main()

 2: {
 3:     int             autoval;
 4:     register int    regival;
 5:     volatile int    volaval;
 6:     static int      statval;
 7:
 8:     globval=1;autoval=2;regival=3;volaval=4;statval=5;
 9:
 10:     if(setjmp(jmpbuffer) != 0)
 11:     {
 12:         printf("after longjmp:\n");
 13:         printf("globval = %d,autoval = %d,regival = %d,volaval = %d,statval = %d\n
 14:                 globval,autoval,regival,volaval,statval");
 15:          exit(0);
 16:     }
 17:
 18:     globval=95;autoval=96;regival=97;volaval=98;statval=99;
 19:     printf("before longjmp:\n");
 20:     printf("globval = %d,autoval = %d,regival = %d,volaval = %d,statval = %d\n
 21:                 globval,autoval,regival,volaval,statval");
 22:     longjmp(jmpbuffer);
 23: }

运行结果:

gcc testjmp.c 而后运行,两次输出结果相同,就是说跳转之后,程序从内存中从新读取了数据;

若是gcc –O testjmp.c 而后运行,静态变量和vovatile变量数据为90+的数据,而自动和局部变量都是跳转之前的值。由于进行了优化编译后,编译器会优先采用寄存器中的值而不是读取存储器,因此咱们未加vovatile修饰的局部变量就会读取寄存器中的值。而longjmp跳转后,寄存器中的值会被恢复为setjmp时候的值,因此咱们打印出的自动和局部变量是原来的小数字,而静态和vovatile数据会从新读取存储器得到,就是大数字。

关于自动变量还有一个潜在问题,就是声明自动变量的函数返回后,该自动变量因为退栈就已经不存在了,在这以后不能引用该自动变量。

8进程控制

8.2fork函数

     由fork建立子进程,子进程是父进程的副本,得到父进程数据空间、堆和栈的副本,父子进程共享正文段。因为fork后常常跟随者exec调用,因此如今不少实现是这样的,在fork后并不复制父进程的数据段、堆栈;采用写时复制,即这些区域由父子共享,且这些区域为只读;当父子进程有一个试图改变这些区域,内核将为修改区域那块内存制做一个副本。父子进程共享同一张文件表,这意味着父子进程对同一个文件共享文件偏移量,父进程更改了文件偏移量,子进程也会更新。

8.4vfork函数

    vfork建立一个子进程,子进程会立刻调用exec或exit,因此vfork出来的子进程并不复制父进程的地址空间,在执行exec和exit以前,它在父进程地址空间中运行。

8.5

    僵死进程:一个已经终止可是其父进程并未对其进行善后处理(获取终止子进程的有关信息,释放其占用资源)的进程。

    父进程如何获取子进程状态:内核为每一个终止子进程保存必定信息,父进程调用wait或者waitpid时就能够获得这些信息。

    若是子进程还未结束,父进程就已经挂掉,那么这些孤儿进程的父进程被设置为init进程。init进程永远不会结束,那么在init收养的某个进程结束后,init就会调用一个wait函数来取得其终止状态,并进行处理,防止这些进程成为僵死进程。

8.11更改用户ID和组ID

    在UNIX系统中,特权是基于用户和组ID的,当程序须要增长特权,或须要访问当前并不容许访问的资源时,咱们须要更换本身的用户ID或者组ID,使得新ID具备合适的特权或访问权限。同时,当程序须要下降其特权或阻止对某些资源的访问时,也须要更换用户ID和组ID。

    改变用户ID和组ID有两种途径:1,exec执行程序时,根据程序文件的设置用户ID位是否打开,来更改,有效用户ID位为程序文件拥有者,实际用户ID为运行此程序的用户。若是设置用户ID位未打开,则不能改变。

    2,setuid函数,当运行程序的进程有超级用户权限时候,setuid(uid)将实际用户ID有效用户ID保存的设置用户ID设置为uid;运行程序没有超级用户权限,可是uid=实际用户ID或者设置用户ID,那么僵有效用户ID设置为uid;若是不是上面两种状况,那么setuid则会设置errno返回。

书中使用man程序对这个过程进行解释。

10 信号

10.2  处理信号的三种方式:1,忽略;2,捕捉;3执行系统默认动做。

        两个常量的意义:SIG_IGN表明内核忽略信号,SIG_DFL表示采用系统默认动做。

10.5 中断的系统调用

        若是进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断再也不继续执行。返回出错值,将errno设置为EINTR。

10.6可重入函数

        进程捕捉到信号并对其进行处理时,进程正在执行的指令序列就被信号处理程序中断,执行完信号处理程序后,返回到中断处再继续执行被中断的指令序列。可是有些函数被中断后返回可能会形成问题,这些函数就是不可重入的,相反中断后返回继续执行无问题的函数即称为可重入的函数。

         可重入函数主要用于多任务环境中,一个可重入的函数简单来讲就是能够被中断的函数,也就是说,能够在这个函数执行的任什么时候刻中断它,转入OS调度下去执行另一段代码,而返回控制时不会出现什么错误;而不可重入的函数因为使用了一系统资源,好比全局变量区,中断向量表,因此它若是被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

10.11信号相关的一些函数和结构

         sigset_t 信号集;sigprocmask(int,const sigset_t *,sigset_t *)更改信号屏蔽字的函数;sigaction(int,const struct sigaction*,struct sigaction* ),修改与指定与信号相关联的处理动做;sigsuspend,pause,sigwait这几个函数可用于实现等待某个信号;

11线程

11.2线程概念

       线程的一些好处:

经过为每种事件类型的处理分配单独的线程,可以简化处理异步事件的代码;

多个进程必须使用操做系统提供的复杂机制才能实现内存和文件描述符的共享,多个线程自动地能够访问相同的存储地址空间和文件描述符;

多线程能够提高程序的并行性;

11.5线程终止

终止的三种方式:1,调用pthread_exit;2从启动例程中返回;3被同一进程中其余线程取消(经过调用pthread_cancle);

前两种方式退出后,进程中其余线程能够经过调用pthread_join函数得到该线程的退出状态。

线程能够安排本身退出时须要调用的函数来处理后事,相似于进程的atexit注册函数,线程也有本身的函数pthread_cleanup_push和pthread_cleanup_pop。

11.6线程同步

       多个线程共享相同的内存时,须要确保每一个线程看到一致的视图。若是共享的变量是只读的则对此变量的访问无需同步,可是当变量非只读时就必须考虑同步。

       互斥量,读写锁,条件变量是经常使用的实现同步的工具。

       互斥量即pthread_mutex_t数据类型,经过使用pthread_mutex_init初始化,pthread_mutex_lock和pthread_mutex_unlock来加解锁。要避免死锁问题,同一个执行流中对一个互斥量连续加锁两次会死锁,两个线程使用两个互斥量的状况下要确保线程加锁顺序相同不然也会死锁。

       读写锁容许更高的并行,一次只有一个线程能够占有写模式的读写锁,可是多个线程能够同时占有读模式的读写锁。

       条件变量与互斥量一块儿使用时,容许线程以无竞争的方式等待特定条件的发生。

12线程控制

12.5重入

       若是一个函数在同一时刻能够被多个线程安全调用,就称该函数式线程安全的。若是一个函数对多个线程来讲是可重入的,则说这个函数是线程安全的,但这并不可以说明对信号处理程序来讲该函数也是能够重入的。若是函数对信号处理程序的重入是安全的,那么就说函数是异步-信号安全的。

12.6 线程私有数据

      errno是线程私有化数据。

12.8线程和信号

      每一个线程都有本身的信号屏蔽字,可是信号的处理程序是进程中全部线程共享的。

      进程中的信号时递送到单个线程去的,若是信号与硬件故障或者计时器超时有关,该信号就被发送到引发事件的线程去,而其余信号则被发送到任意一个线程。

      线程能够经过调用sigwait等待一个或者多个信号的发生。使用sigwait的好处是能够简化信号处理,为了防止信号中断线程,把信号加入到每一个线程的信号屏蔽字中,而后安排专用线程作信号处理,这些专用线程能够进行函数调用,不须要担忧在信号处理程序中调用哪些函数是安全的,由于这些函数调用来自正常线程环境。

12.9线程和fork

      在子进程内部只存在一个线程,它是由父进程中调用fork的线程的副本构成的。

14高级I/O

非阻塞I/O,对一个给定的描述符有两种方法对其指定非阻塞I/O,1,若是调用open得到描述符,能够指定O_NOBLOCK标志;2,对于已经打开的描述符,能够调用fcntl打开O_NOBLOCK文件状态标志。

记录锁:当一个进程在读或者修改文件的某个部分时,它能够阻止其余进程修改同一文件区。

       对早期的UNIX系统的一种批评是它们不能用来运行数据库系统,由于这些系统不支持部分的对文件加锁。如今能够经过使用fcntl函数来实现记录锁功能。

int fcntl(int filedes,int cmd,…)。对于记录锁,cmd是F_GETLK、F_SETLK、F_SETLKW。第三个参数是一个纸箱flock结构的指针。

I/O多路转接:当从一个描述符读,而后又写到另外一个描述符时,能够在下列形式的循环中使用阻塞I/O:

 1: while((n = read(STDIN_FILENO,buf,BUFSIZ)) > 0)
 2:     if(write(STDOUT_FILENO,buf,n) != n)
 3:         err_sys("write error");

这种形式的I/O很常见,可是若是必须从两个描述符读,若是使用阻塞I/O,那么可能长时间阻塞在一个描述符上。select和poll函数能够用来面对这个问题。

select等待设定的描述符集中哪一个可用,当有一个可用时select就返回。

select在Socket编程中仍是比较重要的,但是对于初学Socket的人来讲都不太爱用Select写程序,他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,若是事件没有发生,进程或线程就被阻塞,函数不能当即返回)。但是使用Select就能够完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时没必要非要等待事件的发生,一旦执行确定返回,以返回值的不一样来反映函数的执行状况,若是事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,因此效率较高)方式工做的程序,它可以监视咱们需要监视的文件描述符的变化状况——读写或是异常。

相关文章
相关标签/搜索