signal linux kernel 实现 浅析

一 : POSIX标准-signal与thread之间的关系

二 : signal 内核实现

static int __send_signal(int sig,struct siginfo * info,struct taks_struct * t,int group,int from_ancestor_ns)
{
    struct sigpending * pending;    
    /*
        一上来就准备好了,下面会调用__sigqueue_alloc()来分配
        然后 挂接到链表,最后 填充 这个结构体,
        内核你这样的做法可以说也是一个固定了的模式吧
                      分配(对应结构体) ---> 填充(赋值初始化) ---> 挂接到链表上(其他函数会对该链表进行操作)
                      挂接前后要加锁、解锁,或者使用rcu,操作链表之前也是一样的.
    */
    struct sigqueue   * q;
    int override_rlimit;
    int ret = 0,result;
    /**
        内核将信号挂入那个队列 ? 
        是由 传入的这个 group的值 来决定的,这里是怎么选择的呢?

        group : 0  将信号挂入线程私有的挂起队列pending
        group : 1  将信号挂入多个线程(同一个进程)共享的signal->shared_pending

        看一下它的源头活水 : 
        kill_fasync(...)  // driver 向用户空间进程发送信号,driver 有数据 就通知你,你也别轮询、poll了
        {
            kill_fasync_rcu(rcu_dereference(*fp), sig, band);
            {
                while (fa) {
                    ....
                    send_sigio()
                    {
                        struct task_struct * p;
                        重点来了!group在这里被设置为 1
                        int group = 1;

                        send_sigio_to_task(p, fown, fd, band, group);
                        {
                            case 0 :
                                 这个函数很重要,线程组通知函数
                                 无论是向进程发信号,还是向线程发信号,无论是用户进程->用户进程
                                 还是 内核进程->用户进程 都要经过这个函数
                                 多种表现形式,到了内核就来一个统一处理,内核就喜好做这样的事情,提供机制而不是提供策略 殊途同归吧。
                                do_send_sig_info(SIGIO, SEND_SIG_PRIV, p, group);
                                {
                                    ret = send_signal(sig, info, p, group);
                                    {
                                        return __send_signal(sig, info, t, group, from_ancestor_ns);
                                        {
                                            group = 1 毫发无损的被传入到这里了
                                        }
                                    }
                                }
                        }
                    }
                    不断从链表上取出节点,对每一个节点进行处理,下面将会说 什么时候被放入到链表上的
                    fa = rcu_dereference(fa->fa_next);
                }
            }
        }
    */
    pending = group ? &t->signal->shared_pending : &t->pending;
    /*
        调用kmem_cache_alloc() 从高速缓存中获取一个 struct sigqueue.
        什么时候把它返还给slab ?也就是说kmem_cache_free()在什么时候会被调用呢?
    */
    q = __sigqueue_alloc(sig,t,GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,override_rlimit);
    if(q)
    {
        /*
            将这个sigqueue挂接到队列上
            看,有两个队列,一个是线程的私有队列,
            一个是线程组共享的公有队列

            这里是挂到了线程私有队列上,如果你在用户空间 使用 kill()函数,他会挂到共享的队列上
            这是由POSIX规定的,像函数kill、sigqueue() 必需是对进程而言的,不是对进程下的某个线程的
            如果你分析kill()在kernel中的实现,那么他是挂在共享队列上的,
        */
        list_add_tail(&q->list,&pending->list);
        /*
        看 这个info的来源
            {
                case 0 :

                    //#define SEND_SIG_PRIV ((struct siginfo *) 1) : 信号由内核进程发送的

                    do_send_sig_info(SIGIO,SEND_SIG_PRIV,p,group);
                    {
                        ret = send_signal(sig,info,p,group);
                        {
                            return __send_signal(sig, info, t, group, from_ancestor_ns);
                            {
                                这个info 也是毫发无伤的 传到了这个 __send_signal()函数中,
                            }
                        }
                    }
            }
            根据上面的这个流程,这个switch会执行第二个case
        */
        switch((unsigned long)info){
            case (unsigned long) SEND_SIG_NOINFO:  //信号由 用户空间进程发送
                q->info.si_signo = sig;
                q->info.si_errno = 0;
                q->info.si_code  = SI_USER; //信号的来源
                q->info.si_pid   = ,        //发送信号进程的进程ID
                q->info.si_uid   = current_uid(),// 发送信号进程的真是用户ID
                break; 
            case (unsigned long) SEND_SIG_PRIV:     //信号由内核空间进程发送
                /*
                    赋值初始化,这里先挂接到链表上,然后在初始化 ,可谓在linux kernel中很少见
                    难道不可以先赋值 然后在挂到链表上去吗?你一上来就挂到队列上,慌这么很干什么!
                */
                q->info.si_signo = sig;   //SIGIO : 22
                q->info.si_errno = 0;
                /*
                    信号来源与哪里?
                    用户空间进程、内核空间进程、定时器到期、消息到达消息队列、异步IO完成 ??还有哪些??
                */
                q->info.si_code  = SI_KERNEL; //信号的来源 : 来源与 Kernel
                q->info.si_pid   = 0;
                q->info.si_uid   = 0;
                break;
            default:
                /*
                    如果信号不是用户空间进程发送的,也不是内核进程发送的,那么就会执行这个default分支
                    主要做的工作就是将 info中的信息拷贝到 struct sigqueue中,总之,这个switch语句
                    做的工作就是 初始化已经挂接在链表上的struct sigqueue
                */
                copy_siginfo(&q->info,info);
                if(from_ancestor_ns)
                    q->info.si_pid = 0;
                break;          
        }
    }
}   

这里写图片描述

猜你喜欢

转载自blog.csdn.net/leesagacious/article/details/53678666