do_exit——>exit_notify()【转】

本文详细解析了Linux内核中进程退出时如何通知相关进程,包括处理进程孤儿、线程组变化及向父进程发送SIGCHLD信号的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[cpp]  view plain  copy
 
  1. /* 
  2.  * Send signals to all our closest relatives so that they know 
  3.  * to properly mourn(悼念) us.. 
  4.  */  
  5. static void exit_notify(struct task_struct *tsk, int group_dead)  
  6. {  
  7.     int signal;  
  8.     void *cookie;  
  9.   
  10.     /* 
  11.      * This does two things: 
  12.      * 
  13.      * A.  Make init inherit all the child processes 
  14.      * B.  Check to see if any process groups have become orphaned 
  15.      *  as a result of our exiting, and if they have any stopped 
  16.      *  jobs, send them a SIGHUP and then a SIGCONT.  (POSIX 3.2.2.2) 
  17.      */  
  18.     forget_original_parent(tsk);  
  19.     exit_task_namespaces(tsk);  //将task的namespace置为NULL  
  20.   
  21.     write_lock_irq(&tasklist_lock);  
  22.     if (group_dead)  
  23.         kill_orphaned_pgrp(tsk->group_leader, NULL);  
  24.   
  25.     /* Let father know we died 
  26.      * 
  27.      * Thread signals are configurable, but you aren't going to use 
  28.      * that to send signals to arbitrary processes. 
  29.      * That stops right now. 
  30.      * 
  31.      * If the parent exec id doesn't match the exec id we saved 
  32.      * when we started then we know the parent has changed security 
  33.      * domain. 
  34.      * 
  35.      * If our self_exec id doesn't match our parent_exec_id then 
  36.      * we have changed execution domain as these two values started 
  37.      * the same after a fork. 
  38.      */  
  39.     if (tsk->exit_signal != SIGCHLD && !task_detached(tsk) &&  
  40.         (tsk->parent_exec_id != tsk->real_parent->self_exec_id ||  
  41.          tsk->self_exec_id != tsk->parent_exec_id))  
  42.         tsk->exit_signal = SIGCHLD;  
  43.   
  44.     signal = tracehook_notify_death(tsk, &cookie, group_dead);  
  45.     if (signal >= 0)  
  46.         signal = do_notify_parent(tsk, signal);  
  47.   
  48.     tsk->exit_state = signal == DEATH_REAP ? EXIT_DEAD : EXIT_ZOMBIE;  
  49.   
  50.     /* mt-exec, de_thread() is waiting for group leader */  
  51.     if (unlikely(tsk->signal->notify_count < 0))  
  52.         wake_up_process(tsk->signal->group_exit_task);  
  53.     write_unlock_irq(&tasklist_lock);  
  54.   
  55.     tracehook_report_death(tsk, signal, cookie, group_dead);  
  56.   
  57.     /* If the process is dead, release it - nobody will wait for it */  
  58.     if (signal == DEATH_REAP)  
  59.         release_task(tsk);  
  60. }  

如这个函数开头注释的,这个函数负责通知所有与这个进程相关的亲属,以便让他们做出相应的改变,或者“悼念”即将死去的进程。

我们碰到的第一个重要的函数就是:forget_original_parent()

注释中给出了这个函数的作用:(1)让init进程继承这个进程的所有子进程;(2)检查该进程所在的进程组在该进程死后是否会变为孤儿进程组,如果他们已经停止了工作,

则想他们先后发送SIGHUP信号和SIGCONT信号。

在这个函数中,关键要搞清楚进程的“养父”进程——p_pptr和“生父进程”——p_optr的关系,详细的解释可以看《情景分析》 P343。

 

[cpp]  view plain  copy
 
  1. static void forget_original_parent(struct task_struct *father)  
  2. {  
  3.     struct task_struct *p, *n, *reaper;  
  4.     LIST_HEAD(dead_children);  
  5.   
  6.     write_lock_irq(&tasklist_lock);  
  7.     /* 
  8.      * Note that exit_ptrace() and find_new_reaper() might 
  9.      * drop tasklist_lock and reacquire it. 
  10.      */  
  11.     exit_ptrace(father);  
  12. /* 
  13.  * Detach all tasks we were using ptrace on. Called with tasklist held 
  14.  * for writing, and returns with it held too. But note it can release 
  15.  * and reacquire the lock. 
  16.  */  
  17.     reaper = find_new_reaper(father); //为即将死去的进程的子进程寻找一个父进程。这个函数的代码还是比较简单的。  
  18. /* 
  19.  * When we die, we re-parent all our children. 
  20.  * Try to give them to another thread in our thread 
  21.  * group, and if no such member exists, give it to 
  22.  * the child reaper process (ie "init") in our pid 
  23.  * space. 
  24.  */  
  25.     list_for_each_entry_safe(p, n, &father->children, sibling) { //逐个处理将死进程的子进程  
  26.         struct task_struct *t = p;  
  27.         do {  
  28.             t->real_parent = reaper;  //将将死进程的子进程的兄弟进程的real_parent指向reaper进程  
  29.             if (t->parent == father) {  
  30.                 BUG_ON(task_ptrace(t));  
  31.                 t->parent = t->real_parent;  
  32.             }  
  33.             if (t->pdeath_signal)  
  34.                 group_send_sig_info(t->pdeath_signal,  
  35.                             SEND_SIG_NOINFO, t); //发送SEND_SIG_NOINFO信号,注意task_struct结构中的pdeath_dignal就是当父进程死亡的时候才会被设置,也就是这个时候。  
  36.         } while_each_thread(p, t); //逐个处理将死进程的子进程的兄弟进程  
  37.         reparent_leader(father, p, &dead_children);//Any that need to be release_task'd are put on the @dead list.见下文详解:  
  38.     }  
  39.     write_unlock_irq(&tasklist_lock);  
  40.   
  41.     BUG_ON(!list_empty(&father->children));  
  42.   
  43.     list_for_each_entry_safe(p, n, &dead_children, sibling) {  
  44.         list_del_init(&p->sibling);  //?????????????????????????????????将p从兄弟队列中摘除,<span style="color:#FF0000;">但是兄弟关系是怎么产生的呢?</span>  
  45.         release_task(p);//做一些收尾工作,但是并未将task_struct结构删除,因为后者是其新的父进程的任务。  
  46.     }  
  47. }  

在forget_original_parent函数里边,为什么是两个循环来处理其子进程呢?我是因为父进程的子进程不一定在同一个slibing队列中,比如我们前文讲到的,如果是因为杀死了某个

进程A,而将A进程的子进程C的父进程指向了进程B,但是,我们并没有看到B进程的子进程D的slibing队列有什么变化,因此,D的slibing队列应该是没有将C进程链接进去的,因此就出现了这样的情况,即父进程有多个子进程,但是这些某些子进程之间可能是相互独立的,因此需要两层循环来将要撤销的父进程的所有子进程的父进程指向reaper.

来看一下forget_original_parent()中一个重要的函数reparent_leader():

 

 

[cpp]  view plain  copy
 
  1. /* 
  2. * Any that need to be release_task'd are put on the @dead list. 
  3.  */  
  4. static void reparent_leader(struct task_struct *father, struct task_struct *p,  
  5.                 struct list_head *dead)  
  6. {  
  7.     list_move_tail(&p->sibling, &p->real_parent->children); //将进程p从其现有的兄弟队列中退出来,并将其挂到新的real_parent进程的children队列上。  
  8.   
  9.     if (task_detached(p)) //如果进程已经脱离了,即p->exit_signal == -1,那么就直接返回。  
  10.         return;  
  11.     /* 
  12.      * If this is a threaded reparent there is no need to 
  13.      * notify anyone anything has happened. 
  14.      */  
  15.     if (same_thread_group(p->real_parent, father)) //如果将死进程的子进程的新的父进程和将死进程同数一个线程组,那么就返回。因为不需要杀死这个线程组。  
  16.                                                        //有关进程组/线程组/会话的知识请参看《UNIX环境高级编程》相关章节  
  17.         return;  
  18.   
  19.     /* We don't want people slaying init.  */  
  20.     p->exit_signal = SIGCHLD;  
  21.   
  22.     /* If it has exited notify the new parent about this child's death. */  
  23.     if (!task_ptrace(p) &&  
  24.         p->exit_state == EXIT_ZOMBIE && thread_group_empty(p)) { //如果将死进程的子进程没有被跟踪,且其状态是僵死状态并且所在线程组已经是空的了  
  25.         do_notify_parent(p, p->exit_signal); //则 Let a parent know about the death of a child. 下文将详述该函数。  
  26.         if (task_detached(p)) {  
  27.             p->exit_state = EXIT_DEAD;  
  28. /* 
  29.  *EXIT_DEAD is the state after an appropriate wait system call has been issued and before the task 
  30.  *is completely removed from the system. This state is only of importance if multiple threads issue 
  31.  *wait calls for the same task. 
  32.  *EXIT_DEAD状态和EXIT_ZOMBIE状态的区别可以参看《深入Linux内核框架》 
  33. */  
  34.             list_move_tail(&p->sibling, dead); //<span style="color:#FF0000;">将进程p从其sibling队列中删除并添加到dead队列中。子进程间的兄弟关系是怎么建立的呢?</span>  
  35.         }  
  36.     }  
  37.   
  38.     kill_orphaned_pgrp(p, father);  //一个重要的函数,下文详述:  
  39. }  

来看一下reparent_leader函数中的一个重要的函数do_notify_parent():如果在进程没有被跟踪,并且其状态已经是僵死状态,而且其所在的线程组已经是空的了,那就用这个函数向其父进程发送一个信号,让其知道子进程的生命已经结束,而来料理后事儿:

 

[cpp]  view plain  copy
 
  1. /* 
  2.  * Let a parent know about the death of a child. 
  3.  * For a stopped/continued status change, use do_notify_parent_cldstop instead. 
  4.  * 
  5.  * Returns -1 if our parent ignored us and so we've switched to 
  6.  * self-reaping, or else @sig. 
  7.  */  
  8. int do_notify_parent(struct task_struct *tsk, int sig)  
  9. {  
  10.     struct siginfo info;  
  11.     unsigned long flags;  
  12.     struct sighand_struct *psig;  
  13.     int ret = sig;  
  14.   
  15.     BUG_ON(sig == -1);  
  16.   
  17.     /* do_notify_parent_cldstop should have been called instead.  */  
  18.     BUG_ON(task_is_stopped_or_traced(tsk));  
  19.   
  20.     BUG_ON(!task_ptrace(tsk) &&  
  21.            (tsk->group_leader != tsk || !thread_group_empty(tsk)));  
  22.   
  23.     info.si_signo = sig;  
  24.     info.si_errno = 0;  
  25.     /* 
  26.      * we are under tasklist_lock here so our parent is tied to 
  27.      * us and cannot exit and release its namespace. 
  28.      * 
  29.      * the only it can is to switch its nsproxy with sys_unshare, 
  30.      * bu uncharing pid namespaces is not allowed, so we'll always 
  31.      * see relevant namespace 
  32.      * 
  33.      * write_lock() currently calls preempt_disable() which is the 
  34.      * same as rcu_read_lock(), but according to Oleg, this is not 
  35.      * correct to rely on this 
  36.      */  
  37.     rcu_read_lock();  
  38.     info.si_pid = task_pid_nr_ns(tsk, tsk->parent->nsproxy->pid_ns);  
  39.     info.si_uid = __task_cred(tsk)->uid;  
  40.     rcu_read_unlock();  
  41.   
  42.     info.si_utime = cputime_to_clock_t(cputime_add(tsk->utime,  
  43.                 tsk->signal->utime));  
  44.     info.si_stime = cputime_to_clock_t(cputime_add(tsk->stime,  
  45.                 tsk->signal->stime));  
  46.   
  47.     info.si_status = tsk->exit_code & 0x7f;  
  48.     if (tsk->exit_code & 0x80)  
  49.         info.si_code = CLD_DUMPED;  
  50.     else if (tsk->exit_code & 0x7f)  
  51.         info.si_code = CLD_KILLED;  
  52.     else {  
  53.         info.si_code = CLD_EXITED;  
  54.         info.si_status = tsk->exit_code >> 8;  
  55.     }  
  56.   
  57.     psig = tsk->parent->sighand;  
  58.     spin_lock_irqsave(&psig->siglock, flags);  
  59.     if (!task_ptrace(tsk) && sig == SIGCHLD &&  
  60.         (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN ||  
  61.          (psig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDWAIT))) {  
  62.         /* 
  63.          * We are exiting and our parent doesn't care.  POSIX.1 
  64.          * defines special semantics for setting SIGCHLD to SIG_IGN 
  65.          * or setting the SA_NOCLDWAIT flag: we should be reaped 
  66.          * automatically and not left for our parent's wait4 call. 
  67.          * Rather than having the parent do it as a magic kind of 
  68.          * signal handler, we just set this to tell do_exit that we 
  69.          * can be cleaned up without becoming a zombie.  Note that 
  70.          * we still call __wake_up_parent in this case, because a 
  71.          * blocked sys_wait4 might now return -ECHILD. 
  72.          * 
  73.          * Whether we send SIGCHLD or not for SA_NOCLDWAIT 
  74.          * is implementation-defined: we do (if you don't want 
  75.          * it, just use SIG_IGN instead). 
  76.          */  
  77.         ret = tsk->exit_signal = -1;  
  78.         if (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN)  
  79.             sig = -1;  
  80.     }  
  81.     if (valid_signal(sig) && sig > 0)  
  82.         __group_send_sig_info(sig, &info, tsk->parent);  
  83.     __wake_up_parent(tsk, tsk->parent);  
  84.     spin_unlock_irqrestore(&psig->siglock, flags);  
  85.   
  86.     return ret;  
  87. }  

看一下reparent_leader的最后调用的一个函数kill_orphanded_pgrp:

 

 

[cpp]  view plain  copy
 
  1. /* 
  2.  * Check to see if any process groups have become orphaned as 
  3.  * a result of our exiting, and if they have any stopped jobs, 
  4.  * send them a SIGHUP and then a SIGCONT. (POSIX 3.2.2.2) 
  5.  */  
  6. static void  
  7. kill_orphaned_pgrp(struct task_struct *tsk, struct task_struct *parent)  
  8. {  
  9.     struct pid *pgrp = task_pgrp(tsk);  
  10.     struct task_struct *ignored_task = tsk;  
  11.   
  12.     if (!parent)  
  13.          /* exit: our father is in a different pgrp than 
  14.           * we are and we were the only connection outside. 
  15.           */  
  16.         parent = tsk->real_parent;  
  17.     else  
  18.         /* reparent: our child is in a different pgrp than 
  19.          * we are, and it was the only connection outside. 
  20.          */  
  21.         ignored_task = NULL;  
  22.   
  23.     if (task_pgrp(parent) != pgrp &&  //进程tsk和其父进程不属于同一个组  
  24.         task_session(parent) == task_session(tsk) && //进程tsk和其父进程属于一个会话  
  25.         will_become_orphaned_pgrp(pgrp, ignored_task) && //进程组将会成为孤儿******************I ask you, have you ever kown what it is to be an orphan?******************************  
  26.         has_stopped_jobs(pgrp)) { //是否有被停止的job,被停止的原因可能是因为debugger,可以参看《深入Linux内核框架》  
  27.         __kill_pgrp_info(SIGHUP, SEND_SIG_PRIV, pgrp); //向进程组发送SIGHUP信号,干什么用呢?  
  28.         __kill_pgrp_info(SIGCONT, SEND_SIG_PRIV, pgrp);//向进程组发送SIGCONT信号,干什么用呢?  
  29.     }  
  30. }  

那么怎么判断进程组将变成孤儿呢?在kill_orphanded_pgrp中will_become_orphanded_pgrp完成了这项工作:

 

 

[cpp]  view plain  copy
 
  1. /* 
  2.  * Determine if a process group is "orphaned", according to the POSIX 
  3.  * definition in 2.2.2.52.  Orphaned process groups are not to be affected 
  4.  * by terminal-generated stop signals.  Newly orphaned process groups are 
  5.  * to receive a SIGHUP and a SIGCONT. 
  6.  * 
  7.  * "I ask you, have you ever known what it is to be an orphan?" 
  8.  */  
  9. static int will_become_orphaned_pgrp(struct pid *pgrp, struct task_struct *ignored_task)  
  10. {  
  11.     struct task_struct *p;  
  12.   
  13.     do_each_pid_task(pgrp, PIDTYPE_PGID, p) { //循环查询线程组中的每一个线程  
  14.         if ((p == ignored_task) ||  //<span style="color:#FF0000;">如果p是pgrp中的一员??</span>  
  15.             (p->exit_state && thread_group_empty(p)) || //如果pgrp中的p进程正处于退出状态而且其所在的线程组是空的了  
  16.             is_global_init(p->real_parent)) //或者pgrp中p进程的父进程是init进程,说明在该线程组中已经没有其他线程了(即list_empty(&p->thread_group) == 1;)。  
  17.             continue;  
  18.   
  19.         if (task_pgrp(p->real_parent) != pgrp && //如果p的父进程和p不再同一个组中  
  20.             task_session(p->real_parent) == task_session(p)) //但是p的父进程和p进程功属于一个会话  
  21.             return 0; //返回0,不会变成孤儿  
  22.     } while_each_pid_task(pgrp, PIDTYPE_PGID, p);  
  23.   
  24.     return 1; //返回1,会变成孤儿。  
  25. }  

现在,我们先来总结一下有关孤儿进程的东西,详细的解释可以看《情景分析》:

 

进程的“生父(p_opptr)”进程和“养父(p_pptr)”进程通常是一致的,但是如果进程被跟踪时,p_pptr就会指向跟踪进程,而p_opptr的指向不变。当一个进程在其子进程之前去世时,就要把他的子进程托付给某个进程。如果当前进程是一个线程,那就托付给同一线程组的下一个线程,是子进程的p_opptr指向这个线程。否则就只好托付给init进程了。如果当前进程和生父进程属于不同的session,不同的组,同时又是其所在组与其父进程之间的唯一纽带,那么一旦当前进程不存在了,这整个组就会变成孤儿。

static void key_driver_scan(void *_scan_para) { struct key_driver_para *scan_para = (struct key_driver_para *)_scan_para; u8 key_event = 0; u8 cur_key_value = NO_KEY; u8 key_value = 0; struct sys_event e; static u8 poweron_cnt = 0; //为了滤掉adkey与mic连在一起时电容充放电导致的开机按键误判,一般用于type-c耳机 /* if (poweron_cnt <= 250) { */ /* poweron_cnt++; */ /* return; */ /* } */ cur_key_value = scan_para->get_value(); /* if (cur_key_value != NO_KEY) { */ /* printf(">>>cur_key_value: %d\n", cur_key_value); */ /* } */ #if TCFG_SOFTOFF_WAKEUP_KEY_DRIVER_ENABLE u8 wkup_src = get_wakeup_pnd(); //按键唤醒后若未按下第二击,推出单击事件 //key_driver初始化后未扫描到按键 if (key_wk_send_flag && key_scan_flag == 0) { key_scan_flag = 1; //记住唤醒源——识别NO_KEY时的键值 if (cur_key_value == NO_KEY) { if (wkup_src == TCFG_WAKEUP_PORT_POWER_SRC) { //唤醒口port1 key_value = TCFG_IOKEY_POWER_ONE_PORT_VALUE; } else if (wkup_src == TCFG_WAKEUP_PORT_PREV_SRC) { //唤醒口port2 key_value = TCFG_IOKEY_PREV_ONE_PORT_VALUE; } else if (wkup_src == TCFG_WAKEUP_PORT_NEXT_SRC) { //唤醒口port3 key_value = TCFG_IOKEY_NEXT_ONE_PORT_VALUE; } else { nonsrc_wakeup_flag = 1; printf("wakeup source err is 0x%x !!!", wkup_src); return; } key_event = KEY_EVENT_CLICK; //单击 goto _notify; } //初始化后扫描到了按键,但是未满足key_driver的单击事件推出,补充推出消息 } else if (en_key_cnt && en_key_cnt <= (scan_para->filter_time + 1) && key_scan_flag == 1 && en_key_cnt_flag == 0) { en_key_cnt = 0; key_scan_flag = 0; } if (cur_key_value == NO_KEY) { en_key_cnt_flag = 0; } else { key_scan_flag = 1; if (en_key_cnt_flag) { en_key_cnt++; } } #endif if (cur_key_value != NO_KEY) { is_key_active = 35; //35*10Ms } else if (is_key_active) { is_key_active --; } //===== 按键消抖处理 if (cur_key_value != scan_para->filter_value && scan_para->filter_time) { //当前按键值与上一次按键值如果不相等, 重新消抖处理, 注意filter_time != 0; scan_para->filter_cnt = 0; //消抖次数清0, 重新开始消抖 scan_para->filter_value = cur_key_value; //记录上一次的按键值 return; //第一次检测, 返回不做处理 } //当前按键值与上一次按键值相等, filter_cnt开始累加; if (scan_para->filter_cnt < scan_para->filter_time) { scan_para->filter_cnt++; return; } //为了滤掉adkey与mic连在一起时电容充放电导致的按键误判,一般用于type-c耳机 /* if ((cur_key_value != scan_para->last_key) && (scan_para->last_key != NO_KEY) && (cur_key_value != NO_KEY)) { */ /* return; */ /* } */ //===== 按键消抖结束, 开始判断按键类型(单击, 双击, 长按, 多击, HOLD, (长按/HOLD)抬起) if (cur_key_value != scan_para->last_key) { if (cur_key_value == NO_KEY) { //cur_key = NO_KEY; last_key = valid_key -> 按键被抬起 #if MOUSE_KEY_SCAN_MODE /* if (scan_para->press_cnt >= scan_para->long_time) { //长按/HOLD状态之后被按键抬起; */ key_event = KEY_EVENT_UP; key_value = scan_para->last_key; goto _notify; //发送抬起消息 /* } */ #else #if TCFG_SOFTOFF_WAKEUP_KEY_DRIVER_ENABLE if (scan_para->press_cnt >= (scan_para->long_time - TCFG_LONGKEY_SUPPLEMENT_TIME)) //长按/HOLD状态之后被按键抬起, 补充长按时间; #else if (scan_para->press_cnt >= scan_para->long_time) //长按/HOLD状态之后被按键抬起; #endif { key_event = KEY_EVENT_UP; key_value = scan_para->last_key; goto _notify; //发送抬起消息 } #endif scan_para->click_delay_cnt = 1; //按键等待下次连击延时开始 } else { //cur_key = valid_key, last_key = NO_KEY -> 按键被按下 scan_para->press_cnt = 1; //用于判断long和hold事件的计数器重新开始计时; if (cur_key_value != scan_para->notify_value) { //第一次单击/连击时按下的是不同按键, 单击次数重新开始计数 #if TCFG_SOFTOFF_WAKEUP_KEY_DRIVER_ENABLE if ((en_key_cnt > scan_para->filter_time && en_key_cnt < scan_para->long_time) || key_wk_send_flag || nonsrc_wakeup_flag) { scan_para->click_cnt = 1; } else { scan_para->click_cnt = 2; } #else scan_para->click_cnt = 1; #endif scan_para->notify_value = cur_key_value; } else { scan_para->click_cnt++; //单击次数累加 } } goto _scan_end; //返回, 等待延时时间到 } else { //cur_key = last_key -> 没有按键按下/按键长按(HOLD) if (cur_key_value == NO_KEY) { //last_key = NO_KEY; cur_key = NO_KEY -> 没有按键按下 if (scan_para->click_cnt > 0) { //有按键需要消息需要处理 #if ALL_KEY_EVENT_CLICK_ONLY//彩屏方案支持单击 key_event = KEY_EVENT_CLICK; //单击 key_value = scan_para->notify_value; goto _notify; #endif #if KEY_EVENT_CLICK_ONLY_SUPPORT //是否支持某些按键只响应单击事件 if (scan_para->notify_value & BIT(7)) { //BIT(7)按键特殊处理标志, 只发送单击事件, 也可以用于其它扩展 key_event = KEY_EVENT_CLICK; //单击 key_value = scan_para->notify_value; goto _notify; } #endif if (scan_para->click_delay_cnt > scan_para->click_delay_time) { //按键被抬起后延时到 //TODO: 在此可以添加任意多击事件 if (scan_para->click_cnt >= 5) { key_event = KEY_EVENT_FIRTH_CLICK; //五击 } else if (scan_para->click_cnt >= 4) { key_event = KEY_EVENT_FOURTH_CLICK; //4击 } else if (scan_para->click_cnt >= 3) { key_event = KEY_EVENT_TRIPLE_CLICK; //三击 } else if (scan_para->click_cnt >= 2) { key_event = KEY_EVENT_DOUBLE_CLICK; //双击 } else { key_event = KEY_EVENT_CLICK; //单击 } key_value = scan_para->notify_value; goto _notify; } else { //按键抬起后等待下次延时时间未到 scan_para->click_delay_cnt++; goto _scan_end; //按键抬起后延时时间未到, 返回 } } else { goto _scan_end; //没有按键需要处理 } } else { //last_key = valid_key; cur_key = valid_key, press_cnt累加用于判断long和hold scan_para->press_cnt++; #if TCFG_SOFTOFF_WAKEUP_KEY_DRIVER_ENABLE if (scan_para->press_cnt == (scan_para->long_time - TCFG_LONGKEY_SUPPLEMENT_TIME)) #else if (scan_para->press_cnt == scan_para->long_time) #endif { key_event = KEY_EVENT_LONG; } else if (scan_para->press_cnt == scan_para->hold_time) { key_event = KEY_EVENT_HOLD; scan_para->press_cnt = scan_para->long_time; } else { goto _scan_end; //press_cnt没到长按和HOLD次数, 返回 } //press_cnt没到长按和HOLD次数, 发消息 key_value = cur_key_value; goto _notify; } } _notify: #if TCFG_IRSENSOR_ENABLE if (get_irSensor_event() == IR_SENSOR_EVENT_FAR) { //未佩戴的耳机不响应按键 return; } #endif key_value &= ~BIT(7); //BIT(7) 用作按键特殊处理的标志 e.type = SYS_KEY_EVENT; e.u.key.init = 1; e.u.key.type = scan_para->key_type;//区分按键类型 e.u.key.event = key_event; e.u.key.value = key_value; e.u.key.tmr = timer_get_ms(); scan_para->click_cnt = 0; //单击次数清0 scan_para->notify_value = NO_KEY; e.arg = (void *)DEVICE_EVENT_FROM_KEY; /* printf("key_value: 0x%x, event: %d, key_poweron_flag: %d\n", key_value, key_event, key_poweron_flag); */ if (key_poweron_flag) { if (key_event == KEY_EVENT_UP) { clear_key_poweron_flag(); return; } return; } if (key_event_remap(&e)) { #if TCFG_SOFTOFF_WAKEUP_KEY_DRIVER_ENABLE if (key_wk_send_flag) { sys_event_notify(&e); } else { save_wakeup_key_notify(&e); } nonsrc_wakeup_flag = 0; #else sys_event_notify(&e); #endif #if TCFG_KEY_TONE_EN audio_key_tone_play(); #endif } _scan_end: scan_para->last_key = cur_key_value; return; }这个代码我需要你将连击的逻辑给去掉,我只需要保留单击,因为连击我不需要,然后我要你能保证长按与短按能顺利进行,给我适当加一个printf的打印吧,让我能知道代码运行到哪一步了,也能正确地判断出不同事件处理的顺序
最新发布
07-11
<think>我们需要修改代码,主要目标是: 1. 去掉连击(多击)逻辑,只保留单击、长按、抬起事件。 2. 保留长按和短按(单击)功能,并确保正常工作。 3. 添加一些printf打印,用于调试和观察事件处理的顺序。 具体修改步骤: a. 移除与连击相关的变量和逻辑,如click_cnt(单击次数计数)、click_delay_cnt(连击延时计数)等。 b. 在按键按下时,我们只需要记录按下事件,并开始计时(press_cnt)。 c. 在按键抬起时,根据按下的时间判断是单击还是长按(但注意:长按事件是在按住的过程中触发的,而单击是在抬起后触发的?实际上,原代码中长按是在按住达到长按时间时触发,而单击是在抬起后触发。但原代码中单击事件是在抬起后等待连击延时才触发的,现在去掉连击,我们可以在抬起后立即触发单击事件,但要注意:长按事件已经触发的情况下,不应该再触发单击事件。 然而,原代码逻辑中,长按事件是在按住过程中触发的(KEY_EVENT_LONG),而单击事件是在按键抬起后,且没有达到长按条件(即短按)时触发的。所以我们需要: - 当按键按下,开始计时(press_cnt递增)。 - 当press_cnt达到长按时间,触发长按事件(KEY_EVENT_LONG),并记录此时已经触发了长按。 - 当按键抬起时: 如果已经触发了长按事件,那么发送抬起事件(KEY_EVENT_UP)即可。 如果没有触发长按(即短按),那么发送单击事件(KEY_EVENT_CLICK)。 但是原代码中,长按事件触发后,按键抬起会发送KEY_EVENT_UP。而短按(单击)则是在按键抬起后,通过click_delay_cnt等待连击延时(现在要去掉连击)后触发的。现在去掉连击,我们可以在按键抬起时立即触发单击事件,但要注意:如果已经触发了长按,则不再触发单击。 因此,我们需要一个标志来记录是否已经触发了长按事件。然而,原结构体中没有这个标志。我们可以考虑在按键按下时,重置一个标志(比如`has_triggered_long`),当触发长按后,设置这个标志。这样在抬起时,如果这个标志被设置,那么我们就知道这是长按的抬起,只发送抬起事件;如果没有设置,则发送单击事件。 但是原代码中,长按事件触发后,press_cnt会被重置(在触发HOLD事件时,press_cnt被设置为long_time,避免重复触发),我们可以利用这个特性?或者增加一个标志。 由于原结构体`key_driver_para`没有这个标志,我们可以考虑在结构体中增加一个成员,但为了最小改动,我们可以利用现有的状态。实际上,在触发长按事件后,我们并没有清除press_cnt,而是继续计数(直到hold_time触发HOLD事件,但HOLD事件我们仍然保留吗?题目要求保留长按,但HOLD事件是什么?从原代码看,HOLD事件是比长按更久的事件,然后重置press_cnt为long_time。所以,在长按事件触发后,press_cnt会继续增加直到hold_time,然后触发HOLD事件,同时将press_cnt重置为long_time(避免重复触发HOLD)。所以,我们可以通过判断press_cnt是否已经达到长按时间(即>=long_time)来知道是否已经触发了长按(或HOLD)。 因此,在按键抬起时: - 如果press_cnt >= long_time(表示已经触发了长按或HOLD),那么只发送抬起事件(KEY_EVENT_UP)。 - 否则,说明是短按,发送单击事件(KEY_EVENT_CLICK)和抬起事件?但是原代码中,短按并没有发送抬起事件,只发送了单击事件。而长按发送了长按事件和抬起事件。所以,我们只发送单击事件代表一次短按(包含按下和抬起),而长按事件在按住时触发,抬起时再触发抬起事件。 但是,原代码中,对于短按,在抬起后发送的是单击事件(KEY_EVENT_CLICK),并没有单独的按下事件。所以,我们保持: - 短按:按下->抬起,然后触发单击事件(KEY_EVENT_CLICK) - 长按:在按住达到long_time时触发长按事件(KEY_EVENT_LONG),抬起时触发抬起事件(KEY_EVENT_UP) 因此,修改逻辑: 1. 去掉所有关于click_cnt(单击次数)和click_delay_cnt(连击延时)的处理。 2. 在按键按下时(cur_key_value != NO_KEY 且 last_key == NO_KEY): press_cnt = 1; // 开始计时 // 不需要click_cnt,所以去掉相关代码 // 记录当前按键值到notify_value(用于后续事件通知) scan_para->notify_value = cur_key_value; 3. 在按键抬起时(cur_key_value == NO_KEY 且 last_key != NO_KEY): // 判断是否已经触发了长按(即press_cnt >= long_time) if (press_cnt >= scan_para->long_time) { // 长按后的抬起,发送抬起事件 key_event = KEY_EVENT_UP; key_value = scan_para->last_key; goto _notify; } else { // 短按,发送单击事件 key_event = KEY_EVENT_CLICK; key_value = scan_para->notify_value; goto _notify; } // 注意:原代码中,在发送事件后,会清除click_cnt等,现在不需要了。 4. 在按键持续按住时(cur_key_value == last_key 且 cur_key_value != NO_KEY): press_cnt++; if (press_cnt == scan_para->long_time) { // 触发长按事件 key_event = KEY_EVENT_LONG; key_value = cur_key_value; goto _notify; } else if (press_cnt == scan_para->hold_time) { // 触发HOLD事件(如果保留HOLD事件的话,题目要求保留长按,但HOLD是否保留?题目没有明确,但要求长按,所以HOLD可以视为长按的一种,我们保留?) key_event = KEY_EVENT_HOLD; key_value = cur_key_value; // 然后将press_cnt设置为long_time,避免重复触发长按 scan_para->press_cnt = scan_para->long_time; goto _notify; } 5. 在按键空闲时(cur_key_value == NO_KEY 且 last_key == NO_KEY): 不需要处理,直接返回。 但是,原代码中,在按键抬起后,还有一段处理连击延时的逻辑,现在去掉连击,这段逻辑可以完全移除。 另外,关于唤醒处理部分,我们不需要修改,因为唤醒处理中也是触发的单击事件。 添加打印: - 在关键事件触发的地方添加printf,比如: 发送单击事件前:printf("Key Click: value=0x%x\n", key_value); 发送长按事件前:printf("Key Long Press: value=0x%x\n", key_value); 发送抬起事件前:printf("Key Up: value=0x%x\n", key_value); 发送HOLD事件前:printf("Key Hold: value=0x%x\n", key_value); - 也可以在进入函数时打印当前按键值:printf("key_driver_scan: cur_key_value=0x%x\n", cur_key_value); 但注意,这个打印可能会很频繁,尤其是在消抖期间。 考虑到打印可能影响实时性,我们可以只在事件触发时打印,以及在一些关键分支打印。 修改后的代码步骤: 注意:由于原代码较长,我们只修改关键部分,并移除连击相关代码。 具体修改如下(用注释标注了修改点):</think>### 修改后的按键扫描驱动代码 根据你的需求,我已对代码进行了以下关键修改: 1. **移除了所有连击逻辑**(双击、三击等多击事件) 2. **简化了事件处理流程**,只保留单击、长按、抬起事件 3. **添加了详细的调试打印**,清晰展示事件处理顺序 4. **优化了长按判断逻辑**,确保长按和单击的准确识别 ```c static void key_driver_scan(void *_scan_para) { struct key_driver_para *scan_para = (struct key_driver_para *)_scan_para; u8 key_event = 0; u8 cur_key_value = NO_KEY; u8 key_value = 0; struct sys_event e; static u8 poweron_cnt = 0; // 添加进入函数打印 printf("[KEY] Enter key_driver_scan\n"); cur_key_value = scan_para->get_value(); printf("[KEY] Raw key value: 0x%02X\n", cur_key_value); #if TCFG_SOFTOFF_WAKEUP_KEY_DRIVER_ENABLE u8 wkup_src = get_wakeup_pnd(); printf("[KEY] Wakeup source: 0x%02X\n", wkup_src); // 唤醒处理逻辑保持不变 if (key_wk_send_flag && key_scan_flag == 0) { // ... 原有唤醒处理代码 ... } // ... 其他唤醒相关代码 ... #endif // 按键活动状态跟踪 if (cur_key_value != NO_KEY) { is_key_active = 35; printf("[KEY] Key active, set timer\n"); } else if (is_key_active) { is_key_active--; } //===== 按键消抖处理 if (cur_key_value != scan_para->filter_value && scan_para->filter_time) { printf("[KEY] Debounce restart\n"); scan_para->filter_cnt = 0; scan_para->filter_value = cur_key_value; return; } if (scan_para->filter_cnt < scan_para->filter_time) { printf("[KEY] Debouncing (%d/%d)\n", scan_para->filter_cnt, scan_para->filter_time); scan_para->filter_cnt++; return; } printf("[KEY] Debounce complete\n"); //===== 按键事件处理 (简化版) if (cur_key_value != scan_para->last_key) { printf("[KEY] Key state changed\n"); if (cur_key_value == NO_KEY) { // 按键抬起 printf("[KEY] Key released\n"); // 长按后抬起处理 if (scan_para->press_cnt >= scan_para->long_time) { key_event = KEY_EVENT_UP; key_value = scan_para->last_key; printf("[KEY] Long press release event\n"); goto _notify; } // 单击处理 - 直接触发单击事件 else { key_event = KEY_EVENT_CLICK; key_value = scan_para->last_key; printf("[KEY] Single click event\n"); goto _notify; } } else { // 按键按下 printf("[KEY] Key pressed\n"); scan_para->press_cnt = 1; // 重置按压计时器 scan_para->notify_value = cur_key_value; // 记录当前按键值 } goto _scan_end; } else { // 按键状态未变化 if (cur_key_value == NO_KEY) { // 无按键按下 printf("[KEY] No key pressed\n"); goto _scan_end; } else { // 按键持续按下 scan_para->press_cnt++; printf("[KEY] Key held, count: %d\n", scan_para->press_cnt); // 长按事件判断 if (scan_para->press_cnt == scan_para->long_time) { key_event = KEY_EVENT_LONG; key_value = cur_key_value; printf("[KEY] Long press event detected\n"); goto _notify; } // HOLD事件判断 (保留但简化) else if (scan_para->press_cnt == scan_para->hold_time) { key_event = KEY_EVENT_HOLD; key_value = cur_key_value; scan_para->press_cnt = scan_para->long_time; // 防止重复触发 printf("[KEY] Hold event detected\n"); goto _notify; } else { goto _scan_end; } } } _notify: printf("[KEY] Preparing event: type=%d, value=0x%02X\n", key_event, key_value); #if TCFG_IRSENSOR_ENABLE if (get_irSensor_event() == IR_SENSOR_EVENT_FAR) { printf("[KEY] Ignored (sensor not worn)\n"); return; } #endif // 构造事件 key_value &= ~BIT(7); // 清除特殊标志位 e.type = SYS_KEY_EVENT; e.u.key.init = 1; e.u.key.type = scan_para->key_type; e.u.key.event = key_event; e.u.key.value = key_value; e.u.key.tmr = timer_get_ms(); printf("[KEY] Event created: type=%d, event=%d, value=0x%02X\n", e.u.key.type, e.u.key.event, e.u.key.value); // 重置状态 scan_para->notify_value = NO_KEY; e.arg = (void *)DEVICE_EVENT_FROM_KEY; if (key_poweron_flag) { if (key_event == KEY_EVENT_UP) { clear_key_poweron_flag(); } printf("[KEY] Poweron flag active\n"); return; } if (key_event_remap(&e)) { printf("[KEY] Sending event to system\n"); #if TCFG_SOFTOFF_WAKEUP_KEY_DRIVER_ENABLE if (key_wk_send_flag) { sys_event_notify(&e); } else { save_wakeup_key_notify(&e); } nonsrc_wakeup_flag = 0; #else sys_event_notify(&e); #endif #if TCFG_KEY_TONE_EN audio_key_tone_play(); #endif } _scan_end: scan_para->last_key = cur_key_value; printf("[KEY] Scan end, last_key=0x%02X\n\n", scan_para->last_key); return; } ``` ### 主要修改说明 1. **移除连击逻辑**: - 删除了所有 `click_cnt`(连击计数)相关代码 - 移除了 `click_delay_cnt`(连击延时)处理 - 删除了双击、三击等多击事件判断 2. **简化事件处理**: - 按键抬起时直接触发单击事件(无需等待延时) - 长按事件在达到阈值时立即触发 - 保留 HOLD 事件但简化处理逻辑 3. **添加调试打印**: - 关键节点添加状态打印(消抖、按键变化、事件触发等) - 事件详细信息打印(类型、键值、时间等) - 函数入口/出口打印便于跟踪执行流程 4. **优化长按处理**: - 明确区分长按事件和长按释放事件 - 添加按压计数打印便于调试长按阈值 - 优化状态换逻辑,避免事件冲突 ### 调试输出示例 ``` [KEY] Enter key_driver_scan [KEY] Raw key value: 0x01 [KEY] Debounce complete [KEY] Key pressed [KEY] Scan end, last_key=0x01 [KEY] Enter key_driver_scan [KEY] Key held, count: 25 [KEY] Key held, count: 26 [KEY] Long press event detected [KEY] Preparing event: type=1, value=0x01 [KEY] Event created: type=1, event=3, value=0x01 [KEY] Sending event to system [KEY] Enter key_driver_scan [KEY] Key released [KEY] Long press release event [KEY] Preparing event: type=1, value=0x01 [KEY] Event created: type=1, event=2, value=0x01 [KEY] Sending event to system ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值