小知识点

来源:互联网 发布:cf手游尼泊尔战龙 数据 编辑:程序博客网 时间:2024/05/18 01:11

printf

当printf接收到字符串时候,第一件事情把字符串复制到一个char数组(缓冲区)里,当这个数组遇到特定字符,比如’\n’字符,回车或者装满等,就会把字符写到屏幕终端;
而当我们把printf重定向到文件时候,如果printf函数遇到’\n’字符,并不会立即把字符写到文件里,这是printf函数将字符重定向到屏幕和文件的重要区别;
当./myfork>tmp这个进程执行到fork的时候,printf里的缓冲区数据还没有刷新到tmp文件中,就被fork函数复制了,同时printf的缓冲区也被复制了一份一模一样的出来。
fork就是将进程的地址空间完全复制一份(不是100%复制,除了少数几个地方),实际是在复制完成后,操作系统直接给fork出来的子进程把它赋值成0,这是子进程fork函数返回0的原因。

wait函数

子进程在死的时候会通知父亲(给父进程发signal),所以父进程只要处理好子进程的通知就行了,用到的是wait函数。
//参数保存子进程的通知码,返回-1表示没有子进程或者错误,否则返回子进程的进程id号
pid_t wait(int *status);

waitpid函数

waitpid函数和wait函数功能都是一样的,都是用来获取子进程的状态;
pid_t waitpid(pid_t pid,int *status,int option);

    参数pid
  • pid>0,表示waitpid只等待子进程pid
  • pid=0,表示waitpid等待和当前调用waitpid一个组的所有子进程
  • pid=-1,表示等待所有子进程pid
  • pid 小于-1,表示等待组id=|pid|(pid绝对值)的所有子进程pid

因此调用wait(&status)实际上就是调用waitpid(-1,&status,0);
其中参数status描述的是子进程的退出状态,它包含了不止一种信息,可以通过宏来判断status到底属于哪种信息;

    status分成下面四类
  • 1.进程正常退出
  • 2.进程被信号终止
  • 3.进程被暂停执行
  • 4.进程被恢复执行

由4个宏函数判断是哪一种,分别是WIFEXITED(status)、WIFSIGNALED(status)、WIFSTOPPED(status)、WIFCONTINUED(status),这些能从宏函数名字里面看到(exit,signaled,stopped,continued),这种实现技巧实际上是归因于status不同比特位代表了不同含义。
waitpid能否收到状态3(stopped)和4(continued),是取决于options参数的。

signal函数

#include<signal.h>typedef void (*sighandler_t)(int)sighandler_t signal(int signum,sighanlder_t handler);

其第二个参数是个函数指针。一般来说,参数里有函数指针的函数,称之为’注册’函数,而指针指向的那个函数,称之为’回调函数’。
所以signal函数可以称为信号注册函数,你注册了指定的函数,你的函数就可以被回调了。回调的意思是不需要亲自调用,而是由别人(一般来说是操作系统)调用。
系统为我们提供2个宏,分别是SIG_DFL(default)和SIG_IGN(ignore).如果handler被指定为SIG_DFL,系统将为该信号指定默认的信号处理函数;如果handler被指定为SIG_IGN,系统将忽略该信号。实际上在启动时候,所有信号处理函数都被指定为默认或者忽略;
如果需要指定自己编写的信号处理函数,你的函数格式必须是 void func(int)这种形式,函数的名字可以随便,但是参数和返回值不能随便改
参数:signum
signal的第一个参数指示了你需要捕捉哪个信号
返回值:
signal的返回值表示旧的信号处理函数。如果返回值等于SIG_ERR说明注册失败。

信号的不可靠性

1-31号被称为standard signals,也就是标准信号。32-64被称为real-time signals,也就是实时信号。

    不可靠性:如果同时来了很多信号,而且还没来得及处理,这些相同的信号会被合并成一个信号。而实时信号就没有这个问题,只要来一次,就会处理一次。

可重入函数

线程安全的函数一种可重入的函数。
如:

int a=0; //全局变量int fun(){++a;return a;]

当执行fun()函数的return a的时候(假设a此时值已经为1),你的代码突然由于信号的打断跳转到另一段代码执行。然而十分不巧的是,那段代码把fun函数执行了一遍,此时a的值是2.当重新回到你的代码时,你的fun函数返回值已经不是你期望的1,而是2;
产生这种现象的原因是,该函数引用了全局变量a;
除此之外,使用局部静态变量也会出现这个问题。我们把所有引用了全局变量或者静态变量的函数,称为不可重入函数,不可重入函数都不是信号安全的,也不是线程安全的。
如果一个函数对于信号处理来说是可冲入的,那么称为异步信号安全函数。

线程安全的函数不一定是异步信号安全的

如果一个函数中使用了不可重入函数,那么该函数也会变成不可重入函数。这意味着在信号处理函数中不能使用不可重入函数。
有很多C和Linux系统调用都是不可重入函数,比如malloc、getpwdnam。很多标准IO函数都是不可重入的,因为这些函数使用了缓冲区。

不可重入导致的bug

alarm函数

unsigned int alarm(unsigned int seconds)
alarm参数作用是设定多少秒之后给本进程发送sigalrm信号,返回值表示上一次设定的定时炸弹还有多少秒的时间会爆炸(同时会取消上一次还没来得及爆炸的定时炸弹)

异步IO控制块aiocb

#include<aiocb.h>struct aiocb{    int aio_fildes;//文件描述符    off_t aio_offsets;//文件偏移    volatile *aio_buf;// 缓冲区长度    size_t aio_nbytes; //传输的数据长度    int aio_reqprio;//请求优先级    struct sigevent aio_sigevent;   //通知方法    int aio_lio_opcode; //仅被lio_listio()函数调用}

  • aio_fildes:fildes就是file description的缩写,相当于使用read或者write函数时第一个参数fd,表示想操作那个文件描述符上的IO
  • aio_offset:文件偏移指针,表示你想从文件哪个位置开始操作,如你要从文件中第10个字节开始读,那这里就设置成10
  • aio_buf:这个位置,缓冲区的地址
  • aio_nbytes:要传输多少字节的数据
  • aio_read

    int aio_read(struct aiocb *aiocbp);
    aio_read接受一个aiocb结构体对象的指针,通过参数aiocbp指针将请求送入到请求队列,他的参数相当于调用read(aio_fildes,aio_buf,aio_nbytes)的参数;
    aio_read函数从文件的绝对偏移aiocbp->aio_offset开始读数据,它会忽略当前文件本身的文件偏移,返回0表示成功,-1失败,即请求未成功加入请求队列,此时会设置errno

    int main(){    int fd,ret;    char buf[64];    //定义一个异步控制块结构;    struct aiocb my_aiocb;    //将所有成员清0;    bzero((char *)&my_aiocb,sizeof(struct aiocb));    my_aio.aio_buf=buf; //告诉内核,有数据就就放在这儿;    my_aio.aio_fildes=STDIN_FILENO; //告诉内核,想从标准输入读数据;    my_aio.aio_nbytes=64; //告诉内核,缓冲区大小只有64;    my_aio.aio_offset=0; //告诉内核,从偏移为0的地方开始读;    //发起异步读操作,立即返回。你并不知道何时buf中会有数据    //将请求放到请求队列中    ret=aio_read(&my_aiocb);    if(ret<0)    ERR_EXIT("aio_read");    //不断的检查异步读的状态,如果返回EINPROGRESS,说明异步读还没有完成;    //轮询检查状态是一种很笨的方式,其实可以让操作系统用信号的方式来通知,或者让操作系统完成读后主动创建一个线程执行。    while(aio_error(&my_aiocb)==EINPROGRESS){        write(STDOUT_FILENO,".",1);        sleep(1);    }    //打印缓存区内容,你并不知道内核什么时候讲缓冲区内容复制到你的buf中;    printf("content:%s\n",buf);    return  0;]

    当你调用aio_read或者aio_write等函数发起了异步读或者写时,内核就自己去干活了。如果不知道异步通知的方式,你就只能不断的询问内核:“读完没”

    aio_error

    实际上,它只是为了获得异步请求的状态,原型如下
    int aio_error(const struct aiocb *aiocb);
    返回值:

    EINPROGRESS,异步请求还未完成
    ECANCELED,异步请求被取消
    0:请求完成;
    >0的错误码,表示异步操作失败,该值相当于同步IO函数read、write出错时,并设置的errno变量;
    aio_error是线程安全的;

    aio_return


    原型如下
    //ssize_t可以理解为int类型
    ssize_t aio_return( struct aiocb *aiocb);
    该函数返回最终的异步请求状态
    这个函数对于每个请求只能使用一次,而且要在aio_error返回值不是EINPROGRESS情况下使用
    返回值:
    如果异步操作完成,该函数返回值就相当于同步IO类型read write fsync或fdatasync等返回值
    如果异步操作未完成使用它,结果就是未定义的。

    lio_listio

    int lio_listio(int mode,struct aiocb *const aiocb_list[],int nitems,struct sigevent *sevp);

    mode:

    值含义LIO_WAITlio_listio会堵塞,直到所有的异步io请求完成,此时sevp被忽略掉LIO_NOWAITio_listio会立即返回,当所有异步io请求完成,会异步通知,通知的方式由sevp指定,该参数可以为NULL,表示不需要异步通知
    #include<aiocb.h>struct aiocb{    int aio_fildes;//文件描述符    off_t aio_offsets;//文件偏移    volatile *aio_buf;// 缓冲区长度    size_t aio_nbytes; //传输的数据长度    int aio_reqprio;//请求优先级    struct sigevent aio_sigevent;   //通知方法    int aio_lio_opcode; //仅被lio_listio()函数调用}

    aiocb_list就是aiocb结构体指针的数组,nitems表示该数组的大小。在使用lio_listio函数时候,需要将aiocb中的aio_lio_opcode成员赋值,该成员告诉内核发起的是何种异步IO操作。
    aio_lio_opcode的值可以为

    值含义LIO_READ发起异步读操作LIO_WRITE发起异步写操作LIO_NOP表示忽略掉该aiocb

    异步通知aio_sigevent

    union signal{    int sigval_int;    void * sival_ptr;};
    struct sigevent{    int sigev_notify;  //通知方式    int sigev_signo; //通知所用的信号,可以自己指定,比如SIGUSR1    union sigval sigev_value;   //通知附带的数据    pid_t sigev_notify_thread_id;  //通知线程    void (*sigev_notify_function)(union sigval);   //线程通知函数    void *sigev_notify_attributes;//通知线程的属性,一般指定为pthread_attr_t结构的地址    //这个成员仅仅用于sigev_thread_id通知方式    pid_t sigev_notify_thread_id;}
    0 0