成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux 多線程的信號(hào)處理機(jī)制

系統(tǒng) Linux
操作系統(tǒng)會(huì)根據(jù)情況選擇一個(gè)線程并喚醒它,然后在該線程的執(zhí)行上下文處理信號(hào)時(shí),會(huì)先判斷有沒有收到線程級(jí)的信號(hào),如果沒有的話再判斷是否有進(jìn)程級(jí)的信號(hào),然后進(jìn)行處理。

信號(hào)機(jī)制是操作系統(tǒng)中非常重要的部分,它可以用于進(jìn)程/線程間通信、控制進(jìn)程線程的行為和處理高優(yōu)的任務(wù),GDB、CPU Profile 采集、Go 搶占式調(diào)度都依賴信號(hào)機(jī)制。信號(hào)的大致原理不是很復(fù)雜,在單進(jìn)程單線程環(huán)境中,信號(hào)機(jī)制類似一個(gè)訂閱發(fā)布模式,用戶注冊(cè)信號(hào)處理函數(shù),收到信號(hào)時(shí),操作系統(tǒng)執(zhí)行對(duì)應(yīng)的函數(shù),但是在多線程環(huán)境下,里面的邏輯就變得復(fù)雜了,比如說(shuō)我們是否可以單獨(dú)給線程發(fā)送信號(hào),應(yīng)該在哪個(gè)線程中注冊(cè)信號(hào)處理函數(shù),給進(jìn)程發(fā)送信號(hào)時(shí)哪個(gè)線程會(huì)處理等等。本文從 Linux 源碼角度分析信號(hào)的實(shí)現(xiàn)原理。

進(jìn)程的信號(hào)原理

首先從早期的內(nèi)核代碼(1.2.13)看一下信號(hào)的實(shí)現(xiàn),因?yàn)樵缙诘拇a易于我們理解原理。我們知道 Linux 不區(qū)分進(jìn)程線程,統(tǒng)一使用 task_struct 來(lái)表示,task_struct 中有幾個(gè)字段和信號(hào)機(jī)制相關(guān)。

unsigned long signal;  // 當(dāng)前收到的信號(hào),每一 bit 對(duì)應(yīng)一個(gè)信號(hào),比如 0b10 對(duì)應(yīng)信號(hào) 2
unsigned long blocked; // 屏蔽這些信號(hào),即不處理這些信號(hào)
/*
struct sigaction {
 __sighandler_t sa_handler; // 處理函數(shù)
 sigset_t sa_mask;
 unsigned long sa_flags;
 void (*sa_restorer)(void);
};
*/
struct sigaction sigaction[32]; // 信號(hào)對(duì)應(yīng)的處理函數(shù),和信號(hào)的值一一對(duì)應(yīng),比如信號(hào) 1 對(duì)應(yīng)數(shù)組 第一個(gè)元素

了解了和信號(hào)相關(guān)的數(shù)據(jù)結(jié)構(gòu)后,接著從注冊(cè)信號(hào)、發(fā)送信號(hào)、處理信號(hào)幾個(gè)方面分析信號(hào)的實(shí)現(xiàn)。

注冊(cè)信號(hào)

asmlinkage int sys_sigaction(int signum, const struct sigaction * action,
 struct sigaction * oldaction)
{
struct sigaction new_sa, *p;
// current 表示當(dāng)前調(diào)用進(jìn)程,p 指向 signum 對(duì)應(yīng)的處理函數(shù)
 p = signum - 1 + current->sigaction;
if (action) {
    // 復(fù)制內(nèi)存
  memcpy_fromfs(&new_sa, action, sizeof(struct sigaction));
 }
// 返回舊的處理函數(shù)
if (oldaction) {
int err = verify_area(VERIFY_WRITE, oldaction, sizeof(*oldaction));
if (err)
   return err;
  memcpy_tofs(oldaction, p, sizeof(struct sigaction));
 }
// 設(shè)置新的處理函數(shù)
if (action) {
  *p = new_sa;
  check_pending(signum);
 }
return0;
}

注冊(cè)信號(hào)就是在 task_struct 的 sigaction 中記錄信號(hào)對(duì)應(yīng)的處理函數(shù)。

發(fā)送信號(hào)

我們應(yīng)該都試過用 kill 命令給某個(gè)進(jìn)程發(fā)送信息,我們也可以通過操作系統(tǒng)底層提供的 kill 系統(tǒng)調(diào)用給進(jìn)程發(fā)送信息。

asmlinkage int sys_kill(int pid,int sig)
{
int err, retval = 0, count = 0;
// 如果沒有傳pid,則給該進(jìn)程所在組所有進(jìn)程發(fā)該信號(hào)
if (!pid)
return(kill_pg(current->pgrp,sig,0));
// 如果pid等于-1,則給除了自己和0進(jìn)程外的所有進(jìn)程發(fā)該信號(hào)
if (pid == -1) {
struct task_struct * p;
  for_each_task(p) {
   if (p->pid > 1 && p != current) {
    ++count;
    if ((err = send_sig(sig,p,0)) != -EPERM)
     retval = err;
   }
  }
return(count ? retval : -ESRCH);
 }
// 如果pid等于除-1外的負(fù)數(shù),則取絕對(duì)值后,給該進(jìn)程組發(fā)該信號(hào)
if (pid < 0) 
return(kill_pg(-pid,sig,0));
/* Normal kill */
// 否則給某個(gè)進(jìn)程發(fā)該信號(hào)
return(kill_proc(pid,sig,0));
}

sys_kill 支持多種場(chǎng)景,這里我們只關(guān)注給指定進(jìn)程發(fā)送的部分。

int kill_proc(int pid, int sig, int priv)
{
struct task_struct *p;
// 遍歷進(jìn)程列表,找到對(duì)應(yīng)的進(jìn)程,然后發(fā)送信號(hào)
 for_each_task(p) {
if (p && p->pid == pid)
   return send_sig(sig,p,priv);
 }
return(-ESRCH);
}

int send_sig(unsigned long sig,struct task_struct * p,int priv)
{
// 設(shè)置對(duì)應(yīng)的位為 1
 p->signal |= 1 << (sig-1);
return0;
}

發(fā)送信號(hào)的實(shí)現(xiàn)很簡(jiǎn)單,就是在 task_struct 的 signal 字段置對(duì)應(yīng)的為 1,表示收到了該信息。從這里可以看到重復(fù)發(fā)生信號(hào)可能會(huì)發(fā)生覆蓋,最終只會(huì)處理一次,不過現(xiàn)在的內(nèi)核版本已經(jīng)支持記錄每一個(gè)收到的信號(hào)了。

處理信號(hào)

剛才看到,發(fā)送信號(hào)時(shí)只是打了個(gè)標(biāo)記,并沒有處理該信號(hào),也就是執(zhí)行用戶注冊(cè)的函數(shù)。那么什么時(shí)候才會(huì)處理呢?處理的時(shí)機(jī)有幾個(gè),比如中斷處理后、系統(tǒng)調(diào)用結(jié)束后。下面是系統(tǒng)調(diào)用后處理信號(hào)的邏輯。

_system_call:
 pushl %eax   # save orig_eax
 SAVE_ALL
 // 執(zhí)行系統(tǒng)調(diào)用
 movl _sys_call_table(,%eax,4),%eax
 call *%eax
 movl %eax,EAX(%esp)  # save the return value
 movl errno(%ebx),%edx
 negl %edx
 je ret_from_sys_call

ret_from_sys_call:
// 當(dāng)天進(jìn)程結(jié)構(gòu)體賦值到 eax
  movl _current,%eax
// 判斷是否有信號(hào)并且沒有被屏蔽
 movl blocked(%eax),%ecx
 movl %ecx,%ebx   
 notl %ecx
 andl signal(%eax),%ecx
// 非 0 說(shuō)明有信號(hào)需要處理
 jne signal_return

signal_return:
 movl %esp,%ecx
 pushl %ecx
 pushl %ebx
// 處理信號(hào)
 call _do_signal
 popl %ebx
 popl %ebx

接著看一些 do_signal 的實(shí)現(xiàn)。

asmlinkage int do_signal(unsigned long oldmask, struct pt_regs * regs)
{
// mask 等于 blocked 取反,表示沒有被屏蔽的信號(hào)
unsignedlong mask = ~current->blocked;
unsignedlong handler_signal = 0;
unsignedlong *frame = NULL;
unsignedlong eip = 0;
unsignedlong signr;
struct sigaction * sa;
// 收集需要處理的信號(hào)
while ((signr = current->signal & mask)) {
    // 獲取
  __asm__("bsf %3,%1\n\t"
   "btrl %1,%0"
   :"=m" (current->signal),"=r" (signr)
   :"0" (current->signal), "1" (signr));
  signr++;
    // 哪些信息需要處理
  handler_signal |= 1 << (signr-1);
    // 執(zhí)行當(dāng)前信號(hào)時(shí)需要屏蔽的信息,取反再與得到最終需要處理的信息
  mask &= ~sa->sa_mask;
 }

// 當(dāng)前的指令地址
 eip = regs->eip;
 frame = (unsignedlong *) regs->esp;
 signr = 1;
 sa = current->sigaction;
// 逐個(gè)信號(hào)處理
for (mask = 1 ; mask ; sa++,signr++,mask += mask) {
    // 構(gòu)造棧內(nèi)存布局
  setup_frame(sa,&frame,eip,regs,signr,oldmask);
    // do_signal 執(zhí)行完畢后執(zhí)行的執(zhí)行
  eip = (unsignedlong) sa->sa_handler;
  current->blocked |= sa->sa_mask;
  oldmask |= sa->sa_mask;
 }
// 設(shè)置信息的棧地址和指令
 regs->esp = (unsignedlong) frame;
 regs->eip = eip;  /* "return" to the first handler */
return1;
}

void setup_frame(struct sigaction * sa, 
                     unsigned long ** fp, // 當(dāng)前的棧地址
                     unsigned long eip,
                     struct pt_regs * regs,
                     int signr, 
                     unsigned long oldmask)
{
      unsignedlong * frame;
      frame = *fp;
      // ...
      put_fs_long(signr, frame+1);
      put_fs_long(eip, frame+16);// 執(zhí)行完處理函數(shù)后的回跳地址
      put_fs_long(regs->cs, frame+17);
      put_fs_long(regs->eflags, frame+18);
      put_fs_long(regs->esp, frame+19);
      put_fs_long(regs->ss, frame+20);
      put_fs_long(0x0000b858, CODE(0)); /* popl %eax ; movl $,%eax */
      put_fs_long(0x80cd0000, CODE(4)); /* int $0x80 */
      // 恢復(fù)現(xiàn)場(chǎng),回到正常流程。
      put_fs_long(__NR_sigreturn, CODE(2));
}

信號(hào)處理的過程涉及到的東西比較復(fù)雜,如果大家對(duì)函數(shù)調(diào)用時(shí)棧的布局了解的話應(yīng)該會(huì)比較好理解,大概就是在當(dāng)前的棧上進(jìn)行內(nèi)存布局,讓所有的處理函數(shù)連成一條執(zhí)行鏈,然后 do_signal 執(zhí)行完后會(huì)從第一個(gè)執(zhí)行函數(shù)開始執(zhí)行,一直執(zhí)行到最后一個(gè),最終恢復(fù)現(xiàn)場(chǎng)回到正常執(zhí)行流程。Go 的搶占式調(diào)度同樣是用了類似的原理,就是在信號(hào)處理函數(shù)里修改棧內(nèi)存注入自定義的函數(shù),信號(hào)處理完成后,執(zhí)行自定義的函數(shù)實(shí)現(xiàn)搶占。

多線程的信號(hào)原理

通過剛才的介紹,大概了解了信號(hào)處理的過程和原理。但是多線程中的情況有一點(diǎn)不一樣,在 Linux 中,可以給進(jìn)程或線程發(fā)送信號(hào),線程有自己的接收信號(hào)和信號(hào)屏蔽字,但是進(jìn)程內(nèi)的所有線程共享信號(hào)處理函數(shù),另外線程還會(huì)共享進(jìn)程收到的信號(hào)。

圖片圖片

下面通過內(nèi)核源碼看一下實(shí)現(xiàn)(2.6.11.1),該版本代碼中和信號(hào)相關(guān)的字段如下。

/*
  struct signal_struct {
    // 進(jìn)程級(jí)的信號(hào),多個(gè)線程共享
    struct sigpending shared_pending;
  }
*/
struct signal_struct *signal;   // 進(jìn)程級(jí)信號(hào)
struct sighand_struct *sighand; // 進(jìn)程級(jí)信號(hào)處理函數(shù)
sigset_t blocked, real_blocked; // 線程級(jí)信號(hào)屏蔽字
/*
  // 同一個(gè)信號(hào)可能收到多次,在 list 中排隊(duì),通過 signal 表示收到了什么信號(hào)
  struct sigpending {
    struct list_head list; // 收到的所有信號(hào)
    sigset_t signal; // 收到了哪些信號(hào)
  };
*/
struct sigpending pending; // 線程級(jí)收到的信號(hào)

unsigned long sas_ss_sp; // 信號(hào)處理函數(shù)的棧
size_t sas_ss_size;

創(chuàng)建線程

當(dāng)通過 clone 創(chuàng)建線程時(shí),會(huì)對(duì)上面的字段進(jìn)行處理。

asmlinkage int sys_clone(struct pt_regs regs)
{
return do_fork(...);
}

long do_fork(...)
{
struct task_struct *p;
// 復(fù)制內(nèi)容
 p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);
return pid;
}

static task_t *copy_process(...)
{
int retval;
struct task_struct *p = NULL;

// 創(chuàng)建線程必須設(shè)置 CLONE_SIGHAN,共享信號(hào)處理函數(shù)
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);
    
// 獲取一個(gè)新的 task_struct 結(jié)構(gòu)體,內(nèi)容復(fù)制當(dāng)前進(jìn)程的
 p = dup_task_struct(current);  
// 初始化信號(hào)相關(guān)字段
 clear_tsk_thread_flag(p, TIF_SIGPENDING);
 init_sigpending(&p->pending);

/*
    // 引用計(jì)數(shù)加一
    if (clone_flags & (CLONE_SIGHAND | CLONE_THREAD)) {
      atomic_inc(¤t->sighand->count);
      return 0;
    }
  */
  copy_sighand(clone_flags, p));

/*
    // 引用計(jì)數(shù)加一
    if (clone_flags & CLONE_THREAD) {
      atomic_inc(¤t->signal->count);
      atomic_inc(¤t->signal->live);
      return 0;
    }
  */
  copy_signal(clone_flags, p));
}

可以每個(gè)線程都有自己接收的信號(hào)(p->pending 字段),但是進(jìn)程級(jí)的數(shù)據(jù)結(jié)構(gòu)只是引用計(jì)數(shù)加一,也就是說(shuō)它們是多個(gè)線程共享的。

發(fā)送信號(hào)

接著看給線程發(fā)送信號(hào)時(shí)的過程。

// tgid 為線程組 id,即進(jìn)程 id,pid 為線程 id,即 tid
asmlinkage long sys_tgkill(int tgid, int pid, int sig)
{
struct siginfo info;
int error;
struct task_struct *p;

 info.si_signo = sig;
 info.si_errno = 0;
 info.si_code = SI_TKILL;
 info.si_pid = current->tgid;
 info.si_uid = current->uid;
// 根據(jù)線程 id 找到對(duì)應(yīng)的 task_struct
 p = find_task_by_pid(pid);
 error = -ESRCH;
// 只能給同進(jìn)程的線程發(fā)
if (p && (p->tgid == tgid)) {
if (sig && p->sighand) {
   error = specific_send_sig_info(sig, &info, p);
  }
 }
return error;
}

static int specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
int ret = 0;
 ret = send_signal(sig, info, t, &t->pending);
return ret;
}

static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
   struct sigpending *signals)
{
struct sigqueue * q = NULL;
int ret = 0;
// 分配一個(gè) struct sigqueue,表示一個(gè)信號(hào)
 q = __sigqueue_alloc(t, GFP_ATOMIC);
if (q) {
    // 插入 task_struct 結(jié)構(gòu)體 pending 字段的隊(duì)列,即線程級(jí)的信號(hào)
  list_add_tail(&q->list, &signals->list);
    // ...
 }
// 設(shè)置 bitmap,表示收到該信號(hào)
  sigaddset(&signals->signal, sig);
}

給線程發(fā)送信號(hào),最后就是在 task_struct 的 pending 字段記錄置位并把信號(hào)信息加入隊(duì)列中。接下來(lái),再看下通過 sys_kill 給進(jìn)程發(fā)送信號(hào)的流程。

asmlinkage long sys_kill(int pid, int sig)
{
struct siginfo info;

 info.si_signo = sig;
 info.si_errno = 0;
 info.si_code = SI_USER;
 info.si_pid = current->tgid;
 info.si_uid = current->uid;
return kill_something_info(sig, &info, pid);
}

static int kill_something_info(int sig, struct siginfo *info, int pid)
{
return kill_proc_info(sig, info, pid);
}

int kill_proc_info(int sig, struct siginfo *info, pid_t pid)
{
int error;
struct task_struct *p;

 read_lock(&tasklist_lock);
// 通過 pid 找到進(jìn)程結(jié)構(gòu)體
 p = find_task_by_pid(pid);
 error = -ESRCH;
if (p)
  error = group_send_sig_info(sig, info, p);
 read_unlock(&tasklist_lock);
return error;
}

int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
unsignedlong flags;
int ret;

if (!ret && sig && p->sighand) {
  ret = __group_send_sig_info(sig, info, p);
 }

return ret;
}

staticint __group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
int ret = 0;
 ret = send_signal(sig, info, p, &p->signal->shared_pending);
 __group_complete_signal(sig, p);
return0;
}

給進(jìn)程發(fā)送信號(hào)的流程和線程的類似,最終都是調(diào) send_signal,但是有一些區(qū)別,給線程發(fā)送時(shí) send_signal 的最后一個(gè)參數(shù)是 p->pending,給進(jìn)程發(fā)送時(shí)參數(shù)是 p->signal->shared_pending,所以分別是在不同的字段記錄了接收到的信號(hào),另外還有一個(gè)核心的邏輯在__group_complete_signal 中。

// p 沒有屏蔽該信號(hào),p 不是退出狀態(tài),p 是當(dāng)前 task_struct 或沒有待處理的信號(hào)
#define wants_signal(sig, p, mask)    \
 (!sigismember(&(p)->blocked, sig)  \
  && !((p)->state & mask)   \
  && !((p)->flags & PF_EXITING)   \
  && (task_curr(p) || !signal_pending(p)))

staticvoid __group_complete_signal(int sig, struct task_struct *p)
{
unsignedint mask;
struct task_struct *t;
// p 適合處理該信號(hào)則給 p
if (wants_signal(sig, p, mask))
  t = p;
elseif (thread_group_empty(p))
/*
   * There is just one thread and it does not need to be woken.
   * It will dequeue unblocked signals before it runs again.
   */
return;
else {
/*
   * Otherwise try to find a suitable thread.
   */
    
  t = p->signal->curr_target;
if (t == NULL)
   /* restart balancing at this thread */
   t = p->signal->curr_target = p;
    
    // 遍歷進(jìn)程下的線程看哪個(gè)適合處理
while (!wants_signal(sig, t, mask)) {
   t = next_thread(t);
   if (t == p->signal->curr_target)
    /*
     * No thread needs to be woken.
     * Any eligible threads will see
     * the signal in the queue soon.
     */
    return;
  }
  p->signal->curr_target = t;
 }
// 喚醒選擇的線程處理信號(hào)
  signal_wake_up(t, ....);
return;
}

給線程發(fā)送信號(hào)時(shí),給哪個(gè)線程發(fā)就在哪個(gè)線程的上下文執(zhí)行信號(hào)處理函數(shù),但是給進(jìn)程發(fā)信號(hào)時(shí),Linux 會(huì)根據(jù)情況選擇一個(gè)適合處理該信號(hào)的線程,然后喚醒它處理。

處理信號(hào)

最后再看下信號(hào)處理的過程。

void do_notify_resume(struct pt_regs *regs, sigset_t *oldset,
        __u32 thread_info_flags)
{
if (thread_info_flags & _TIF_SIGPENDING)
  do_signal(regs,oldset);
}

int fastcall do_signal(struct pt_regs *regs, sigset_t *oldset)
{
siginfo_t info;
int signr;
struct k_sigaction ka;
// 獲取一個(gè)信號(hào)
 signr = get_signal_to_deliver(&info, &ka, regs, NULL);
if (signr > 0) {
    // 處理信號(hào)
  handle_signal(signr, &info, &ka, oldset, regs);
return1;
 }

return0;
}

處理信號(hào)的過程和前面的介紹類似,修改棧內(nèi)存布局,注入信號(hào)處理函數(shù),執(zhí)行完 do_notify_resume 后開始執(zhí)行信號(hào)處理函數(shù),最終返回再返回正常流程。這里主要看一下獲取信號(hào)的流程。

int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,
     struct pt_regs *regs, void *cookie)
{
sigset_t *mask = ¤t->blocked;
int signr = 0;

relock:
 spin_lock_irq(¤t->sighand->siglock);
for (;;) {
struct k_sigaction *ka;
    // 獲取一個(gè)信號(hào)
  signr = dequeue_signal(current, mask, info);
    // 設(shè)置信號(hào)處理函數(shù)
  ka = ¤t->sighand->action[signr-1];
  }
return signr;
}

int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
{
// 先從線程自己的信號(hào)字段獲取
int signr = __dequeue_signal(&tsk->pending, mask, info);
// 沒有再?gòu)倪M(jìn)程的信號(hào)獲取
if (!signr)
  signr = __dequeue_signal(&tsk->signal->shared_pending,
      mask, info);
return signr;
}

staticint __dequeue_signal(struct sigpending *pending, sigset_t *mask,
   siginfo_t *info)
{
int sig = 0;
// 獲取下一個(gè)待處理的信息
 sig = next_signal(pending, mask);
if (sig) {
    // 處理了該信號(hào),修改相關(guān)的數(shù)據(jù)結(jié)構(gòu)
if (!collect_signal(sig, pending, info))
   sig = 0;
 }
 recalc_sigpending();

return sig;
}

static inline int collect_signal(int sig, struct sigpending *list, siginfo_t *info)
{
struct sigqueue *q, *first = NULL;
int still_pending = 0;

// 從信號(hào)隊(duì)列中獲取一個(gè)信號(hào),并判斷是否還有該信號(hào)需要處理,因?yàn)橐活愋盘?hào)可能會(huì)收到多個(gè)
 list_for_each_entry(q, &list->list, list) {
if (q->info.si_signo == sig) {
   if (first) {
    still_pending = 1;
    break;
   }
   first = q;
  }
 }
if (first) {
    // 移出隊(duì)列
  list_del_init(&first->list);
  copy_siginfo(info, &first->info);
  __sigqueue_free(first);
    // 如果沒有該類信號(hào)則把信號(hào) bitmap 置 0
if (!still_pending)
   sigdelset(&list->signal, sig);
 }
return1;
}

總結(jié)

通過前面的分析可以看到,在多線程環(huán)境中,哪個(gè)線程設(shè)置處理函數(shù)并不重要,因?yàn)槎际沁M(jìn)程內(nèi)共享的,重要的是給線程還是進(jìn)程發(fā)送信號(hào),當(dāng)給線程發(fā)送信號(hào)時(shí),會(huì)在線程獨(dú)有的信號(hào)字段記錄收到的信號(hào),該線程會(huì)在自己的執(zhí)行上下文調(diào)用信號(hào)處理函數(shù),當(dāng)給進(jìn)程發(fā)送信號(hào)時(shí),會(huì)在所有線程都共享的字段中記錄收到的信號(hào),而這個(gè)信號(hào)給哪個(gè)線程處理是不確定的,操作系統(tǒng)會(huì)根據(jù)情況選擇一個(gè)線程并喚醒它,然后在該線程的執(zhí)行上下文處理信號(hào)時(shí),會(huì)先判斷有沒有收到線程級(jí)的信號(hào),如果沒有的話再判斷是否有進(jìn)程級(jí)的信號(hào),然后進(jìn)行處理。

責(zé)任編輯:武曉燕 來(lái)源: 編程雜技
相關(guān)推薦

2010-01-21 11:27:30

linux多線程機(jī)制線程同步

2011-03-17 09:20:05

異常處理機(jī)制

2009-12-08 12:14:43

2011-07-01 14:20:59

Qt 事件

2011-07-01 14:14:34

Qt 事件

2009-06-02 10:32:30

Oracle并發(fā)處理

2011-04-06 10:27:46

Java異常處理

2024-03-04 10:00:35

數(shù)據(jù)庫(kù)處理機(jī)制

2023-09-27 15:41:32

Linux系統(tǒng)

2020-11-10 15:25:26

SemaphoreLinux翻譯

2011-07-21 15:20:41

java異常處理機(jī)制

2009-06-19 16:20:14

ASP.NET錯(cuò)誤處理

2009-09-02 18:34:28

C#鼠標(biāo)事件

2024-07-05 08:32:36

2021-07-03 17:53:52

Java異常處理機(jī)制

2009-08-05 18:09:17

C#異常處理機(jī)制

2009-07-09 18:15:42

JDBC事務(wù)處理

2010-03-05 15:40:16

Python異常

2023-03-08 08:54:59

SpringMVCJava

2023-11-08 09:49:19

Java實(shí)踐
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 亚洲a在线观看 | 午夜三级在线观看 | 国内精品久久久久 | 国产成人jvid在线播放 | 国产不卡一区在线观看 | 成人性生交大片免费看中文带字幕 | 亚洲欧美国产精品久久 | 日韩色视频 | 国产色爽 | 国产精品污www一区二区三区 | 亚洲人成免费 | 99精品久久久久久中文字幕 | 精品国产乱码久久久久久闺蜜 | 一区二区在线 | 午夜爽爽男女免费观看hd | av在线一区二区三区 | 99久久婷婷国产综合精品电影 | 在线观看中文字幕 | 欧美精品一区二区三区在线四季 | 欧美精品一区二区三区四区 | 亚洲最新在线视频 | 一级毛片免费视频 | 国产精品久久一区二区三区 | 最新日韩欧美 | 黄色毛片视频 | 亚洲精品丝袜日韩 | 高清欧美性猛交xxxx黑人猛交 | 日日操日日干 | 国产午夜精品一区二区三区四区 | 一区二区三区免费看 | 国产毛片av | 超碰欧美 | 91资源在线 | 少妇精品久久久久久久久久 | 精品一区电影 | 国精产品一区二区三区 | 国产精品成人一区二区三区 | 自拍偷拍欧美 | 午夜一级做a爰片久久毛片 精品综合 | 日韩在线视频精品 | 欧美精品一区二区三区在线播放 |