LINUX驱动异步编程之信号实现梗概
【
相关源码版本:
LINUX内核源码版本:linux-3.0.86
UBOOT版本:uboot-2010.12.
Android系统源码版本:Android-5.0.2】
Linux系统中进程间.进程组内.进程本身当中都可能发生信息交互既通信。信号是实现这种交互的一种方式。内核框架中对于信号的实现有一整套的框架。大体上分为如下几个部分来实现:
-
注册回调函数,既当某进程收到信号之后要做的反应。一般由下面函数来实现signal(int signum, sighandler_t handler)/int sigaction(int signal, const struct sigaction* bionic_new_action, struct sigaction* bionic_old_action) 其实signal最终也是调用sigaction来实现的。他们的作用是把handler挂入其所在进程的TODO链表中。
-
信号发送. kill(pid_t, int)/killpg(int, int)等。向进程发送一个信号
-
响应信号,进程响应信号,执行函数为进程TODO链表中注册函数注册的回调函数。去执行这个函数。
-
注册回调函数,既当某进程收到信号之后要做的反应
注册回调函数一般由应用层来执行。在某一个应用程序中来注册。通过调用signal或sigaction函数为为当前程序所在进程注册一个回调函数,既signal或sigaction所在的线程或进程(task_struct一个这个结构体我们可以认为是一个线程,这两个函数应该是基于线程来说的,如果当前程序就一个线程也就可以称为进程了,内核无线程进程之分,都是由一个task_struct来管理)。因此回调函数的注册也就是把函数注册进task_struct的TODO链表中。(无论在应用是进程或是线程,在内核中都对应一个task_struct,根据PID进程号在不同的纯种执行,内核通过一定方法也能找到对应的线程号和进程号),getpid(){long sys_getpid(void)
{
return current->tgid;
}
}函数获取的就是一个task_struct可以认为是一个线程,而在内核中都认为是一个进程。
退过上面分析我们就是把回调函数注册进current的一个TODO链表中既满足某种条件就执行的地方。这儿的条件就是当收到别人发的信号之后就应该根据某种条件来执行这个TODO中的函数。本文只分析SIGNAL函数,因为SIGNAL函数的低层实现就是sigaction,SIGACTION是SIGNAL的全集。
下面应用层
sighandler_t signal(int signum, sighandler_t handler) {
return _signal(signum, handler, SA_RESTART);
}应有层signal原型 signum就是信号号 表示这个handler回调函数能响应的信号类型,或者信号ID,满足才会执行。
sighandler_t _signal(int signum, sighandler_t handler, int flags) {
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_handler = handler; //这个就是回调函数指针,收到信号时要得到执行
sa.sa_flags = flags;
if (sigaction(signum, &sa, &sa) == -1) {//前面构建sigaction结构完整,则调用此函数去处理具体事宜了。
return SIG_ERR;
}
return (sighandler_t) sa.sa_handler;
}
对于复杂的函数我们只分析主框架,对于理解问题就帮助就好。细节太难懂。
int sigaction(int signal, const struct sigaction* bionic_new_action, struct sigaction* bionic_old_action) {
return __sigaction(signal, bionic_new_action, bionic_old_action);//此函数调用是汇编了来陷入内核通过SWI软件中断
}
//\bionic\libc\arch-arm\syscalls\__sigaction.S
ENTRY(__sigaction)
ldr r7, =__NR_sigaction
swi #0 //#define __NR_sigaction (__NR_SYSCALL_BASE+ 67)==67系统函数号为67 和LINUX内核中是一一对应的。
END(__sigaction)
内核层。
//#define __NR_sigaction (__NR_SYSCALL_BASE+ 67)==67
Arch/arm/kernel/signal.c
asmlinkage int
sys_sigaction(int sig, const struct old_sigaction __user *act,
struct old_sigaction __user *oact){
ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL);//相在条件处理
}
int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
{
struct task_struct *t = current;获取当前进程结构体(在应用层中可能是一个线程,但无论是进程还是线程在内核中都是由一个task_struct结构来描述的。)
struct k_sigaction *k;//进程信号处理函数链表,也是存储应用层传过来的sigaction结构体
k = &t->sighand->action[sig-1];//表示应用程序注册了那个信号 能响应那个信号赋值给K
*k = *act;把应用程序传进来的SIGACTION注册进SINUM对应的结构体中。
//通过上面步骤CURRENT的current->sighand->action[sig-1]有了应用程序中有传过来的sigaction结构体。这里面有回调函数。//为信号的实现 和CPU的硬件中断是同样的思路实现的 包括中断源 屏蔽寄存器 使能寄存器 标记寄存器 暂存寄器 信号也是来模拟中断的实现来实现信号的。
我们不去分析当中的细节 知道current->sighand->action[sig-1]有了应用程序中有传过来的sigaction结构体就够了。为唤醒的发送做好了一步。
}
B.信号发送. kill(pid_t, int)/killpg(int, int)等。向进程发送一个信号
发送信号,也就是满足某种条件发送信号。让需要信号的进程能够得到触好。PID_T值可以为!=-1(发送给进程组中的所有进程 是相当于当前进程所在的CURRENT) ==-1(发送给进程组的group_leader领导进程 是相当于当前进程所在的CURRENT) >0(向这个进程号发送一个信号)分别代码像不同的发送目标。
//下面是应用层
int kill(pid_t, int);
//bionic/libc/arch-arm/syscalls/kill.s
ENTRY(kill)
mov x8, __NR_kill //#define __NR_kill (__NR_SYSCALL_BASE+ 37) 37系统调用函数号
svc #0
END(kill)
下面进入内核层
//sys_kill-send a signal to a process发送一个信号给一个进程(应用层可以认为是一个线程)
/**
* sys_kill - send a signal to a process
* @pid: the PID of the process//进程ID进程中通过GETPID其实得到的就是CURRENT
* @sig: signal to be sent //发送的信号。
*/
SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
{
return kill_something_info(sig, &info, pid);
}
static int kill_something_info(int sig, struct siginfo *info, pid_t pid)
{
If(pid>0){
发送给进程号为PID的进程(应用层可以看成线程)
ret = kill_pid_info(sig, info, find_vpid(pid));
}
If(pid!=-1){
发送给进程组的主进程。对于就用层来说可以看主线程。因为主线程下面应该有很多线程但主进程号是相同的,每个都有不同的CURRENT.但同性一个进程组,有相同的进程号相当于内核。
}else if(pid_d ==-1){
发送给进程组中的所有成员 虽然应用程序中的线程都有不同的CURRENT但其属于同一个进程组 有相同的进程号 只是子进程不同
}
}
//下面特定分析像某一个进程PID发送信号。
int kill_pid_info(int sig, struct siginfo *info, struct pid *pid)
{
struct task_struct *p;
p = pid_task(pid, PIDTYPE_PID);//根据PID获取到它对应的current这个CURRENT就是上面注册时用的CURRENT PID找到就是不同函数执行时上面调用产生一样的。不对的线程对应不同的CURRENT这儿找到它才进一步找到回调函数。
group_send_sig_info(sig, info, p);
}
int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
do_send_sig_info(sig, info, p, true);
}
int do_send_sig_info(int sig, struct siginfo *info, struct task_struct *p,
bool group){
send_signal(sig, info, p, group);
}
static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
int group){
return __send_signal(sig, info, t, group, from_ancestor_ns);
}
static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
int group, int from_ancestor_ns)
{
pending = group ? &t->signal->shared_pending : &t->pending;
complete_signal(sig, t, group);
}
static void complete_signal(int sig, struct task_struct *p, int group)
{
struct signal_struct *signal = p->signal;
struct task_struct *t;
t = p;
t = signal->curr_target;
signal_wake_up(t, sig == SIGKILL);//唤醒我们前面注册的回调函数。 执行到这儿就表明给PID发送了信号了 执行了唤醒函数之后KILL工作就算完结。
}
C响应信号,进程响应信号,执行函数为进程TODO链表中注册函数注册的回调函数。去执行这个函数。
signal_wake_up唤醒之后 我们注册的回调函数是如何被调用的了。其实内核中回调函数真正的执行是内核返回用户空间 中断返回时 SWI返回时 。signal_wake_up只是置一标志,表示已经收到信号,你可在满足执行TODO链表时去调用回调函数了
//arch/arm/kernel/entry-common.s
ret_fast_syscall://返回应用空间去调用注册的回调函数
bldo_notify_resume//进入回调函数执行流程。
asmlinkage void
do_notify_resume(struct pt_regs *regs, unsigned int thread_flags, int syscall)
{
do_signal(regs, syscall);
}
static void do_signal(struct pt_regs *regs, int syscall)
{
//此函数细节太过复杂 不分析了 他主要实现返回应用空间执行回调函数,然后再进入内核空间,继续其它工作。主要工作就是判断KILL发送了信号。对这个发送的目的进程调用其回调函数。
}