zoukankan      html  css  js  c++  java
  • libevent源码分析之信号处理

    新看看官方demo的libevent如何使用信号
    1. int called = 0;
    2. static void
    3. signal_cb(int fd, short event, void *arg)
    4. {
    5. struct event *signal = arg;
    6. printf("%s: got signal %d ", __func__, EVENT_SIGNAL(signal));
    7. if (called >= 2)
    8. event_del(signal);

    9. called++;
    10. }
    11. int
    12. main (int argc, char **argv)
    13. {
    14. struct event signal_int;//这里我们把它称为事件2
    15. /* Initalize the event library */
    16. event_init();
    17. /* 初始化事件2,设置相关信号,回调函数 */
    18. event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb,
    19. &signal_int);
    20. event_add(&signal_int, NULL);//激活信号
    21. event_dispatch();//等待事件的触发
    22. return (0);
    23. }
    这里只提信号相关操作~~~
    event_init()有关信号操作将跳转到epoll.c中的epoll_init->evsignal_init
    1. evsignal_init(base);

    接下来分析evsignal_init函数,event_base结构体中有信号管理结构evsignal_info(注意不是指针)
    而evsignal_info结构本身有一个event事件,这里称为事件1(这里很关键)
    1. struct evsignal_info {
    2. struct event ev_signal;//向event_base注册读事件使用的event结构体,这里我们称为事件1
    3. int ev_signal_pair[2];//sock pair对,也就是clientfd跟servfd
    4. int ev_signal_added;//记录ev_signal信号是否已经注册
    5. volatile sig_atomic_t evsignal_caught;//是否有信号发生
    6. struct event_list evsigevents[NSIG];//注册到信号的事件链表的一个标志
    7. sig_atomic_t evsigcaught[NSIG];//记录每个信号的触发的次数
    8. #ifdef HAVE_SIGACTION
    9. struct sigaction **sh_old;//记录旧的信号处理函数
    10. #else
    11. ev_sighandler_t **sh_old;
    12. #endif
    13. int sh_old_max;
    14. };
    evsignal_init具体流程:event_init中最终会使用evsignal_init(base),来看看做了什么事
    创建了一对Socketpair(作用:信号来临时,通过信号处理函数socketpair写端发送字节,事件1监听socketpair的读fd,触发事件1),并且将读的fd与事件1相关联,并且将事件1与event_base结构体的指针相关联   
    1. int
    2. evsignal_init(struct event_base *base)
    3. {
    4. int i;
    5. /*
    6. * Our signal handler is going to write to one end of the socket
    7. * pair to wake up our event loop. The event loop then scans for
    8. * signals that got delivered.
    9. *///创建一个socketpair
    10. if (evutil_socketpair(
    11. AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) {
    12. #ifdef WIN32
    13. /* Make this nonfatal on win32, where sometimes people
    14. have localhost firewalled. */
    15. event_warn("%s: socketpair", __func__);
    16. #else
    17. event_err(1, "%s: socketpair", __func__);
    18. #endif
    19. return -1;
    20. }
    21. ////子进程不能访问该socketpair
    22. FD_CLOSEONEXEC(base->sig.ev_signal_pair[0]);
    23. FD_CLOSEONEXEC(base->sig.ev_signal_pair[1]);
    24. base->sig.sh_old = NULL;
    25. base->sig.sh_old_max = 0;
    26. base->sig.evsignal_caught = 0;
    27. memset(&base->sig.evsigcaught, 0, sizeof(sig_atomic_t)*NSIG);
    28. /* initialize the queues for all events */
    29. for (i = 0; i < NSIG; ++i)
    30. TAILQ_INIT(&base->sig.evsigevents[i]);
    31. //设置为非阻塞
    32. evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]);
    33. //可读事件设置与fd相关(但还缺乏注册到base注册链表中,需要在event_add中才会被注册到链表中去)
    34. event_set(&base->sig.ev_signal, base->sig.ev_signal_pair[1],
    35. EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal);
    36. base->sig.ev_signal.ev_base = base;//把信号对应的事件跟base相关联
    37. base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;
    38. return 0;
    39. }

    接着分析event_add函数,实际对应epoll_add函数(以epoll举例)
    实际调用的是evsignal_add函数:这里唯一注意的是(事件2并不是注册到base而是挂载到信号链表,因为信号对应的fd为-1,并没有什么卵用)

    1. //添加事件
    2. static int
    3. epoll_add(void *arg, struct event *ev)
    4. {
    5. struct epollop *epollop = arg;//获取epoll管理结构体
    6. struct epoll_event epev = {0, {0}};//epoll事件
    7. struct evepoll *evep;//读写事件指针
    8. int fd, op, events;
    9. if (ev->ev_events & EV_SIGNAL)//是否注册了信号
    10. return (evsignal_add(ev));//是的话,添加信号到此事件
    11. fd = ev->ev_fd;//获取对应的描述符
    12. if (fd >= epollop->nfds) {//判断描述符是否大于最大值,是的话,扩充
    13. /* Extent the file descriptor array as necessary */
    14. if (epoll_recalc(ev->ev_base, epollop, fd) == -1)
    15. return (-1);
    16. }
    17. evep = &epollop->fds[fd];//获取事件指针
    18. op = EPOLL_CTL_ADD;//默认是添加,其实还有修改等
    19. events = 0;
    20. if (evep->evread != NULL) {//这里epoll的修改与添加设置到一起了,如果不为空,说明本身已有事件了,那就只是修改器读写而已
    21. events |= EPOLLIN;//修改
    22. op = EPOLL_CTL_MOD;//设置为默认
    23. }
    24. if (evep->evwrite != NULL) {//为空
    25. events |= EPOLLOUT;
    26. op = EPOLL_CTL_MOD;
    27. }
    28. if (ev->ev_events & EV_READ)//是否可读
    29. events |= EPOLLIN;//events设置为可读
    30. if (ev->ev_events & EV_WRITE)//是否可写
    31. events |= EPOLLOUT;//设置为可写,注意events只是int类型
    32. epev.data.fd = fd;//epoll事件设置fd
    33. epev.events = events;//epoll事件设置为是否可读可写
    34. if (epoll_ctl(epollop->epfd, op, ev->ev_fd, &epev) == -1)
    35. return (-1);
    36. /* Update events responsible */
    37. if (ev->ev_events & EV_READ)//更新ev_events是否可读可写,如果是,那就更新evep读写事件指针,表示此事件可读可写
    38. evep->evread = ev;
    39. if (ev->ev_events & EV_WRITE)
    40. evep->evwrite = ev;
    41. return (0);
    42. }
    evsignal_add函数分析
    设置信号处理函数evsignal_handler,将事件1注册到base中(这样就可以响应了),并且将ev_signal_added标志设置为1,表示因事件1注册而表示有信号加入。
    同时将事件2挂载到信号链表(通过信号值为索引)的末端。
    1. int
    2. evsignal_add(struct event *ev)
    3. {
    4. int evsignal;
    5. struct event_base *base = ev->ev_base;
    6. struct evsignal_info *sig = &ev->ev_base->sig;//获取信号事件结构体
    7. if (ev->ev_events & (EV_READ|EV_WRITE))//信号事件不可以是读写
    8. event_errx(1, "%s: EV_SIGNAL incompatible use", __func__);
    9. evsignal = EVENT_SIGNAL(ev);//信号的fd就是信号的number
    10. assert(evsignal >= 0 && evsignal < NSIG); // //信号不能超过NSIG这个数
    11. if (TAILQ_EMPTY(&sig->evsigevents[evsignal])) {////如果说该信号链表为空
    12. event_debug(("%s: %p: changing signal handler", __func__, ev));
    13. if (_evsignal_set_handler( //设置信号处理函数,同时,保存原来的信号处理函数到ev_base->sh_old中去
    14. base, evsignal, evsignal_handler) == -1)
    15. return (-1);
    16. /* catch signals if they happen quickly */
    17. evsignal_base = base;
    18. if (!sig->ev_signal_added) {//判断是否已加入
    19. if (event_add(&sig->ev_signal, NULL))//正式注册,添加到epoll_wait中去
    20. return (-1);
    21. sig->ev_signal_added = 1;//表示已经添加了
    22. }
    23. }
    24. //把ev->ev_signal_next加入到sig->evsigevents[evsignal]的链表末端
    25. /* multiple events may listen to the same signal */
    26. TAILQ_INSERT_TAIL(&sig->evsigevents[evsignal], ev, ev_signal_next);
    27. return (0);
    28. }

    event_dispatch()分析,实际调用epoll_dispatch,而epoll_dispatch实际调用evsignal_process。

    1. static int
    2. epoll_dispatch(struct event_base *base, void *arg, struct timeval *tv)
    3. {
    4. struct epollop *epollop = arg; //获取管理epoll的结构
    5. struct epoll_event *events = epollop->events;//epoll的事件数组
    6. struct evepoll *evep;
    7. int i, res, timeout = -1;
    8. if (tv != NULL)
    9. timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000;//设置超时事件
    10. if (timeout > MAX_EPOLL_TIMEOUT_MSEC) {//不可以大于最大超时事件
    11. /* Linux kernels can wait forever if the timeout is too big;
    12. * see comment on MAX_EPOLL_TIMEOUT_MSEC. */
    13. timeout = MAX_EPOLL_TIMEOUT_MSEC;
    14. }
    15. res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
    16. if (res == -1) {
    17. if (errno != EINTR) {
    18. event_warn("epoll_wait");
    19. return (-1);
    20. }
    21. evsignal_process(base);//处理信号事件
    22. return (0);
    23. } else if (base->sig.evsignal_caught) {
    24. evsignal_process(base);//处理信号事件
    25. }
    26. event_debug(("%s: epoll_wait reports %d", __func__, res));
    27. for (i = 0; i < res; i++) {
    28. int what = events[i].events;
    29. struct event *evread = NULL, *evwrite = NULL;
    30. int fd = events[i].data.fd;
    31. if (fd < 0 || fd >= epollop->nfds)
    32. continue;
    33. evep = &epollop->fds[fd];
    34. if (what & (EPOLLHUP|EPOLLERR)) {
    35. evread = evep->evread;
    36. evwrite = evep->evwrite;
    37. } else {
    38. if (what & EPOLLIN) {//可读
    39. evread = evep->evread;
    40. }
    41. if (what & EPOLLOUT) {//可写
    42. evwrite = evep->evwrite;
    43. }
    44. }
    45. if (!(evread||evwrite))
    46. continue;
    47. if (evread != NULL)//插入就绪链表
    48. event_active(evread, EV_READ, 1);
    49. if (evwrite != NULL)//插入就绪链表
    50. event_active(evwrite, EV_WRITE, 1);
    51. }
    52. if (res == epollop->nevents && epollop->nevents < MAX_NEVENTS) {
    53. /* We used all of the event space this time. We should
    54. be ready for more events next time. */
    55. int new_nevents = epollop->nevents * 2;
    56. struct epoll_event *new_events;
    57. new_events = realloc(epollop->events,
    58. new_nevents * sizeof(struct epoll_event));
    59. if (new_events) {
    60. epollop->events = new_events;
    61. epollop->nevents = new_nevents;
    62. }
    63. }
    64. return (0);
    65. }
    evsignal_process
    假设有信号发生了~将调用信号处理函数设置信号发生标志位同时发送一个字节数据到socketpair的读端,
    而事件1恰好是监听此读端,所以epoll_wait返回然后看信号触发位是否设置为1了,设置了将调用evsignal_process()
    而evsignal_process函数内容是遍历信号链表看是否有挂载的事件,有的话,将该事件2插入已就绪链表中,另外也将事件1插入就绪链表
    1. void
    2. evsignal_process(struct event_base *base)//遍历信号链表,是否有事件2挂载
    3. {
    4. struct evsignal_info *sig = &base->sig;//获取信号管理结构体
    5. struct event *ev, *next_ev;
    6. sig_atomic_t ncalls;
    7. int i;
    8. base->sig.evsignal_caught = 0;
    9. for (i = 1; i < NSIG; ++i) {
    10. ncalls = sig->evsigcaught[i];//是否有触发
    11. if (ncalls == 0)//没有就可以滚了
    12. continue;
    13. sig->evsigcaught[i] -= ncalls;//有的话。。。清空
    14. for (ev = TAILQ_FIRST(&sig->evsigevents[i]);
    15. ev != NULL; ev = next_ev) {
    16. next_ev = TAILQ_NEXT(ev, ev_signal_next);
    17. if (!(ev->ev_events & EV_PERSIST))//没设置这个位,就只使用一次了
    18. event_del(ev);
    19. event_active(ev, EV_SIGNAL, ncalls);//插入就绪链表
    20. }
    21. }
    22. }

    总流程:
    event_base结构体中有信号管理结构evsignal_info(注意不是指针)
    而evsignal_info结构本身有一个event事件,这里称为事件1(这里很关键)

    event_init中最终会使用evsignal_init(base),来看看做了什么事
    创建了一对Socketpair(作用:通过信号处理函数发送字节,事件1监听读fd,触发事件1发生),并且将读的fd与事件1相关联,并且将事件1与event_base结构体的指针相关联


    然后我们在main函数中,创建一个事件2,通过初始化信号2
    event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb,
       &signal_int);

    然后event_add实际使用的是epoll_add然后把事件插入到已注册链表
    看看epoll_add实际是evsignal_add
    设置信号处理函数evsignal_handler,将事件1注册到base中(这样就可以响应了),并且将ev_signal_added设置为1,表示因事件1注册而表示有信号加入。
    同时将事件2挂载到信号链表(通过信号值查找)的末端(貌似事件2并不是注册到base而是挂载到链表,因为信号对应的fd为-1,并没有什么卵用)

    evsignal_handler信号处理函数的内容如下
    设置信号已经触发位为1,触发次数+1
    同时发送1个字节数据到socketpair的读端



    当当当~接下来开始dispatch了

    ~~~假设有信号发生了~将调用信号处理函数设置信号发生标志位同时发送一个字节数据到socketpair的读端,
    而事件1恰好是监听此读端,所以epoll_wait返回然后看信号触发位是否设置为1了,设置了将调用evsignal_process()
    而evsignal_process函数内容是遍历信号链表看是否有挂载的事件,有的话,将该事件2插入已就绪链表中,另外也将事件1插入就绪链表

    接着发现有就绪事件,就调用event_process_active(),实际也就是调用其事件对应的回调函数完成处理~~End





  • 相关阅读:
    T450的Fn lock
    移民,不应该是走投无路后的选择
    门槛低的行业看天赋,门槛高的行业看毅力
    个人是时代的一朵浪花
    转载:XPath基本语法
    爪哇国新游记之三十四----Dom4j的XPath操作
    常去的论坛今天两个传统行业的坛友要下岗了
    异常中要了解的Throwable类中的几个方法
    感觉JVM的默认异常处理不够好,既然不好那我们就自己来处理异常呗!那么如何自己处理异常呢?
    JVM对异常的默认处理方案
  • 原文地址:https://www.cnblogs.com/zengyiwen/p/2314b6dea4146d7ca739d7e6a3847019.html
Copyright © 2011-2022 走看看