`
kenby
  • 浏览: 728543 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Linux信号详解

阅读更多

一 信号的种类

可靠信号与不可靠信号, 实时信号与非实时信号

可靠信号就是实时信号, 那些从UNIX系统继承过来的信号都是非可靠信号, 表现在信号

不支持排队,信号可能会丢失, 比如发送多次相同的信号, 进程只能收到一次. 信号值小于

SIGRTMIN的都是非可靠信号.

非可靠信号就是非实时信号, 后来, Linux改进了信号机制, 增加了32种新的信号, 这些信

号都是可靠信号, 表现在信号支持排队, 不会丢失, 发多少次, 就可以收到多少次. 信号值

位于 [SIGRTMIN, SIGRTMAX] 区间的都是可靠信号.

 

关于可靠信号, 还可以参考WIKI的一段话:

 

The real-time signals, ranging from SIGRTMIN to SIGRTMAX, are a set of signals that can be used for application-defined purposes.
Because SIGRTMIN may have different values on different Unix-like systems, applications should always refer to the signals in the form SIGRTMIN+n, where n is a constant integer expression.
The real-time signals have a number of properties that differentiate them from other signals and make them suitable for application-defined purposes:
* Multiple instances of a real-time signal can be sent to a process and all will be delivered.
* Real-time signals can be accompanied by an integer or pointer value (see sigqueue[2]).
* Real-time signals are guaranteed to be delivered in the order they were emitted.
 


命令行输入 kill -l, 可以列出系统支持的所有信号:

 

~> kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

非可靠信号一般都有确定的用途及含义,  可靠信号则可以让用户自定义使用

 

二 信号的安装

 

早期的Linux使用系统调用 signal 来安装信号


#include <signal.h>

void (*signal(int signum, void (*handler))(int)))(int); 

该函数有两个参数, signum指定要安装的信号, handler指定信号的处理函数.

该函数的返回值是一个函数指针, 指向上次安装的handler


经典安装方式:

if (signal(SIGINT, SIG_IGN) != SIG_IGN) {

    signal(SIGINT, sig_handler);

}

先获得上次的handler, 如果不是忽略信号, 就安装此信号的handler


由于信号被交付后, 系统自动的重置handler为默认动作, 为了使信号在handler

处理期间, 仍能对后继信号做出反应, 往往在handler的第一条语句再次调用 signal


sig_handler(ing signum)

{

    /* 重新安装信号 */

    signal(signum, sig_handler);

    ......

}

我们知道在程序的任意执行点上, 信号随时可能发生, 如果信号在sig_handler重新安装

信号之前产生, 这次信号就会执行默认动作, 而不是sig_handler. 这种问题是不可预料的.


使用库函数 sigaction  来安装信号

为了克服非可靠信号并同一SVR4和BSD之间的差异, 产生了 POSIX 信号安装方式, 使用

sigaction安装信号的动作后, 该动作就一直保持, 直到另一次调用 sigaction建立另一个

动作为止. 这就克服了古老的 signal 调用存在的问题


#include <signal.h> 
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));


经典安装方式:


struct sigaction action, old_action;



/* 设置SIGINT */

action.sa_handler = sig_handler;

sigemptyset(&action.sa_mask);

sigaddset(&action.sa_mask, SIGTERM);

action.sa_flags = 0;


/* 获取上次的handler, 如果不是忽略动作, 则安装信号 */

sigaction(SIGINT, NULL, &old_action);

if (old_action.sa_handler != SIG_IGN) {

    sigaction(SIGINT, &action, NULL);

}


基于 sigaction 实现的库函数: signal

sigaction 自然强大, 但安装信号很繁琐, 目前linux中的signal()是通过sigation()函数

实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必

再调用一次信号安装函数。

 

三 如何屏蔽信号

 

所谓屏蔽, 并不是禁止递送信号, 而是暂时阻塞信号的递送, 

解除屏蔽后, 信号将被递送, 不会丢失. 相关API为


int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signum);

int sigdelset(sigset_t *set, int signum);

int sigismember(const sigset_t *set, int signum);

int sigsuspend(const sigset_t *mask);

int sigpending(sigset_t *set);

-----------------------------------------------------------------

int  sigprocmask(int  how,  const  sigset_t *set, sigset_t *oldset));

sigprocmask()函数能够根据参数how来实现对信号集的操作,操作主要有三种:

* SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号

* SIG_UNBLOCK 如果进程阻塞信号集中包含set指向信号集中的信号,则解除

   对该信号的阻塞

* SIG_SETMASK 更新进程阻塞信号集为set指向的信号集


屏蔽整个进程的信号:

 

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>

void sig_handler(int signum)
{
    printf("catch SIGINT\n");
}

int main(int argc, char **argv)
{
    sigset_t block;
    struct sigaction action, old_action;

    /* 安装信号 */
    action.sa_handler = sig_handler;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;

    sigaction(SIGINT, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN) {
        sigaction(SIGINT, &action, NULL);
    }

    /* 屏蔽信号 */
    sigemptyset(&block);
    sigaddset(&block, SIGINT);

    printf("block SIGINT\n");
    sigprocmask(SIG_BLOCK, &block, NULL);

    printf("--> send SIGINT -->\n");
    kill(getpid(), SIGINT);
    printf("--> send SIGINT -->\n");
    kill(getpid(), SIGINT);
    sleep(1);

    /* 解除信号后, 之前触发的信号将被递送,
     * 但SIGINT是非可靠信号, 只会递送一次
     */
    printf("unblock SIGINT\n");
    sigprocmask(SIG_UNBLOCK, &block, NULL);

    sleep(2);

    return 0;
}

 运行结果:

 

work> ./a.out 
block SIGINT
--> send SIGINT -->
--> send SIGINT -->
unblock SIGINT
catch SIGINT

这里发送了两次SIGINT信号 可以看到, 屏蔽掉SIGINT后,

信号无法递送, 解除屏蔽后, 才递送信号, 但只被递送一次,

因为SIGINT是非可靠信号, 不支持排队.


只在信号处理期间, 屏蔽其它信号

在信号的handler执行期间, 系统将自动屏蔽此信号, 但如果

还想屏蔽其它信号怎么办?  可以利用 struct sigaction 结构体

的 sa_mask 属性.

 

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>

void sig_handler(int signum)
{
    printf("in handle, SIGTERM is blocked\n");
    /* 在此handler内将屏蔽掉SIGTERM, 直到此handler返回 */
    printf("--> send SIGTERM -->\n");
    kill(getpid(), SIGTERM);
    sleep(5);
    printf("handle done\n");
}

void handle_term(int signum)
{
    printf("catch sigterm and exit..\n");
    exit(0);
}

int main(int argc, char **argv)
{
    struct sigaction action, old_action;

    /* 设置SIGINT */
    action.sa_handler = sig_handler;
    sigemptyset(&action.sa_mask);
    /* 安装handler的时候, 设置在handler
     * 执行期间, 屏蔽掉SIGTERM信号 */
    sigaddset(&action.sa_mask, SIGTERM);
    action.sa_flags = 0;

    sigaction(SIGINT, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN) {
        sigaction(SIGINT, &action, NULL);
    }

    /* 设置SIGTERM */
    action.sa_handler = handle_term;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;

    sigaction(SIGTERM, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN) {
        sigaction(SIGTERM, &action, NULL);
    }

    printf("--> send SIGINT -->\n");
    kill(getpid(), SIGINT);

    while (1) {
        sleep(1);
    }

    return 0;
}

 

运行结果:

 

work> ./a.out
--> send SIGINT -->
in handle, SIGTERM is blocked
--> send SIGTERM -->
handle done
catch sigterm and exit..

 收到SIGINT后, 进入sig_handler,此时发送SIGTERM信号将被屏蔽,

 等sig_handler返回后, 才收到SIGTERM信号, 然后退出程序

 

 

四 如何获取未决信号

所谓未决信号, 是指被阻塞的信号, 等待被递送的信号. 


int sigsuspend(const sigset_t *mask));

sigpending(sigset_t *set))获得当前已递送到进程,

却被阻塞的所有信号,在set指向的信号集中返回结果。

 

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>

/* 版本1, 可靠信号将被递送多次 */
//#define MYSIGNAL SIGRTMIN+5
/* 版本2, 不可靠信号只被递送一次 */
#define MYSIGNAL SIGTERM

void sig_handler(int signum)
{
    psignal(signum, "catch a signal");
}

int main(int argc, char **argv)
{
    sigset_t block, pending;
    int sig, flag;

    /* 设置信号的handler */
    signal(MYSIGNAL, sig_handler);

    /* 屏蔽此信号 */
    sigemptyset(&block);
    sigaddset(&block, MYSIGNAL);
    printf("block signal\n");
    sigprocmask(SIG_BLOCK, &block, NULL);

    /* 发两次信号, 看信号将会被触发多少次 */
    printf("---> send a signal --->\n");
    kill(getpid(), MYSIGNAL);
    printf("---> send a signal --->\n");
    kill(getpid(), MYSIGNAL);

    /* 检查当前的未决信号 */
    flag = 0;
    sigpending(&pending);
    for (sig = 1; sig < NSIG; sig++) {
        if (sigismember(&pending, sig)) {
            flag = 1;
            psignal(sig, "this signal is pending");
        } 
    }
    if (flag == 0) {
        printf("no pending signal\n");
    }

    /* 解除此信号的屏蔽, 未决信号将被递送 */
    printf("unblock signal\n");
    sigprocmask(SIG_UNBLOCK, &block, NULL);

    /* 再次检查未决信号 */
    flag = 0;
    sigpending(&pending);
    for (sig = 1; sig < NSIG; sig++) {
        if (sigismember(&pending, sig)) {
            flag = 1;
            psignal(sig, "a pending signal");
        } 
    }
    if (flag == 0) {
        printf("no pending signal\n");
    }

    return 0;
}

  这个程序有两个版本:

可靠信号版本, 运行结果:

work> ./a.out 
block signal
---> send a signal --->
---> send a signal --->
this signal is pending: Unknown signal 39
unblock signal
catch a signal: Unknown signal 39
catch a signal: Unknown signal 39
no pending signal

发送两次可靠信号, 最终收到两次信号


非可靠信号版本, 运行结果:

 

work> ./a.out 
block signal
---> send a signal --->
---> send a signal --->
this signal is pending: Terminated
unblock signal
catch a signal: Terminated
no pending signal

  发送两次非可靠信号, 最终只收到一次

 

五 被中断了的系统调用

一些IO系统调用执行时, 如 read 等待输入期间, 如果收到一个信号,

系统将中断read, 转而执行信号处理函数. 当信号处理返回后, 系统

遇到了一个问题: 是重新开始这个系统调用, 还是让系统调用失败?


早期UNIX系统的做法是, 中断系统调用, 并让系统调用失败, 比如read

返回 -1, 同时设置 errno 为 EINTR


中断了的系统调用是没有完成的调用, 它的失败是临时性的, 如果再次调用

则可能成功, 这并不是真正的失败, 所以要对这种情况进行处理, 典型的方式为:


while (1) {

    n = read(fd, buf, BUFSIZ);

    if (n == -1 && errno != EINTR) {

        printf("read error\n");

        break;

    }

    if (n == 0) {

        printf("read done\n");

        break;

    }

}


这样做逻辑比较繁琐, 事实上, 我们可以从信号的角度

来解决这个问题,  安装信号的时候, 设置 SA_RESTART

属性, 那么当信号处理函数返回后, 被该信号中断的系统

调用将自动恢复. 

 

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>

void sig_handler(int signum)
{
    printf("in handler\n");
    sleep(1);
    printf("handler return\n");
}

int main(int argc, char **argv)
{
    char buf[100];
    int ret;
    struct sigaction action, old_action;

    action.sa_handler = sig_handler;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    /* 版本1:不设置SA_RESTART属性
     * 版本2:设置SA_RESTART属性 */
    //action.sa_flags |= SA_RESTART;

    sigaction(SIGINT, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN) {
        sigaction(SIGINT, &action, NULL);
    }

    bzero(buf, 100);

    ret = read(0, buf, 100);
    if (ret == -1) {
        perror("read");
    }

    printf("read %d bytes:\n", ret);
    printf("%s\n", buf);

    return 0;
}

 

版本1, 不设置 SA_RESTART 属性 :

 

work> gcc signal.c 
work> ./a.out 
^Cin handler
handler return
read: Interrupted system call
read -1 bytes:

在 read 等待数据期间, 按下ctrl + c, 触发 SIGINT 信号, 

handler 返回后, read 被中断, 返回 -1


版本2, 设置 SA_RESTART 属性:

 

work> gcc signal.c 
work> ./a.out 
^Cin handler
handler return
hello, world
read 13 bytes:
hello, world

 handler 返回后, read 系统调用被恢复执行, 继续等待数据.

 

 

六 非局部控制转移


int setjmp(jmp_buf env);

int sigsetjmp(sigjmp_buf env, int savesigs);

void longjmp(jmp_buf env, int val);

void siglongjmp(sigjmp_buf env, int val);

--------------------------------------------------------

setjmp()会保存目前堆栈环境,然后将目前的地址作一个记号,

而在程序其他地方调用 longjmp 时便会直接跳到这个记号位置,

然后还原堆栈,继续程序好执行。 


setjmp调用有点fork的味道, setjmp()return 0 if returning directly, 

and non-zero when returning from longjmp using  the saved context.


if (setjmp(jmpbuf)) {

   printf("return from jmp\n");

} else {

   printf("return directly\n");

}


setjmp 和 sigsetjmp 的唯一区别是: setjmp 不一定会恢复信号集合,

而sigsetjmp可以保证恢复信号集合

 

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <setjmp.h>

void sig_alrm(int signum);
void sig_usr1(int signum);
void print_mask(const char *str);

static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjmp;
static int sigalrm_appear;

int main(int argc, char **argv)
{
    struct sigaction action, old_action;

    /* 设置SIGUSR1 */
    action.sa_handler = sig_usr1;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;

    sigaction(SIGUSR1, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN) {
        sigaction(SIGUSR1, &action, NULL);
    }

    /* 设置SIGALRM */
    action.sa_handler = sig_alrm;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;

    sigaction(SIGALRM, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN) {
        sigaction(SIGALRM, &action, NULL);
    }

    print_mask("starting main:");

    if (sigsetjmp(jmpbuf, 1) != 0) {
        print_mask("exiting main:");
    } else {
        printf("sigsetjmp return directly\n");
        canjmp = 1;
        while (1) {
            sleep(1);
        }
    }

    return 0;
}

void sig_usr1(int signum)
{
    time_t starttime;
    if (canjmp == 0) {
        printf("please set jmp first\n");
        return;
    }

    print_mask("in sig_usr1:");

    alarm(1);
    while (!sigalrm_appear);
    canjmp = 0;
    siglongjmp(jmpbuf, 1);
}

void sig_alrm(int signum)
{
    print_mask("in sig_alrm:");
    sigalrm_appear = 1;

    return;
}

void print_mask(const char *str) 
{
    sigset_t sigset;
    int i, errno_save, flag = 0;

    errno_save = errno;

    if (sigprocmask(0, NULL, &sigset) < 0) {
        printf("sigprocmask error\n");
        exit(0);
    }

    printf("%s\n", str);
    fflush(stdout);

    for (i = 1; i < NSIG; i++) {
        if (sigismember(&sigset, i)) {
            flag = 1;
            psignal(i, "a blocked signal");
        }
    }

    if (!flag) {
        printf("no blocked signal\n");
    }

    printf("\n");
    errno = errno_save;
}
 

运行结果: 

 

work> ./a.out &
[4] 28483
starting main:
no blocked signal

sigsetjmp return directly

kill -USR1 28483

in sig_usr1:
a blocked signal: User defined signal 1

in sig_alrm:
a blocked signal: User defined signal 1
a blocked signal: Alarm clock

exiting main:
no blocked signal
 

 

七 信号的生命周期

 

从信号发送到信号处理函数的执行完毕

对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,

可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:

信号诞生;信号在进程中注册完毕;信号在进程中的注销完毕;信号处理函数执行完毕。


下面阐述四个事件的实际意义:

信号"诞生"。信号的诞生指的是触发信号的事件发生

(如检测到硬件异常、定时器超时以及调用信号发送函数

kill()或sigqueue()等)。信号在目标进程中"注册";

进程的task_struct结构中有关于本进程中未决信号的数据成员:

struct sigpending pending:

struct sigpending{

struct sigqueue *head, **tail;

sigset_t signal;

};


第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个

sigqueue类型的结构链(称之为"未决信号链表")的首尾,链表中

的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个

sigqueue结构:

struct sigqueue{

struct sigqueue *next;

siginfo_t info;

}


信号的注册

信号在进程中注册指的就是信号值加入到进程的未决信号集中

(sigpending结构的第二个成员sigset_t signal),

并且加入未决信号链表的末尾。 只要信号在进程的未决信号集中,

表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。

当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,

都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。

这意味着同一个实时信号可以在同一个进程的未决信号链表中添加多次. 

当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册,

则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。

这意味着同一个非实时信号在进程的未决信号链表中,至多占有一个sigqueue结构.

一个非实时信号诞生后,

(1)、如果发现相同的信号已经在目标结构中注册,则不再注册,对于进程来说,

相当于不知道本次信号发生,信号丢失.

(2)、如果进程的未决信号中没有相同信号,则在进程中注册自己。


信号的注销。

在进程执行过程中,会检测是否有信号等待处理

(每次从系统空间返回到用户空间时都做这样的检查)。如果存在未决

信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,

进程会把信号在未决信号链中占有的结构卸掉。是否将信号从进程未决信号集

中删除对于实时与非实时信号是不同的。对于非实时信号来说,由于在未决信

号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信

号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在

未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构

的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),

则应该把信号在进程的未决信号集中删除(信号注销完毕)。否则,不应该在进程

的未决信号集中删除该信号(信号注销完毕)。 

进程在执行信号相应处理函数之前,首先要把信号在进程中注销。


信号生命终止。

进程注销信号后,立即执行相应的信号处理函数,执行完毕后,

信号的本次发送对进程的影响彻底结束。

 

八 关于可重入函数

 

在信号处理函数中应使用可重入函数。

信号处理程序中应当使用可重入函数

(注:所谓可重入函数是指一个可以被多个任务调用的过程,

任务在调用时不必担心数据是否会出错)。因为进程在收到信号

后,就将跳转到信号处理函数去接着执行。如果信号处理函数中

使用了不可重入函数,那么信号处理函数可能会修改原来进程中

不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,

可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为

不安全函数。满足下列条件的函数多数是不可再入的:

(1)使用静态的数据结构,如getlogin(),gmtime(),getgrgid(),

    getgrnam(),getpwuid()以及getpwnam()等等;

(2)函数实现时,调用了malloc()或者free()函数;

(3)实现时使用了标准I/O函数的。The Open Group视下列函数为可再入的:

    _exit()、access()、alarm()、cfgetispeed()、cfgetospeed()、

    cfsetispeed()、cfsetospeed()、chdir()、chmod()、chown() 、

    close()、creat()、dup()、dup2()、execle()、execve()、

    fcntl()、fork()、fpathconf()、fstat()、fsync()、getegid()、

    geteuid()、getgid()、getgroups()、getpgrp()、getpid()、

    getppid()、getuid()、kill()、link()、lseek()、mkdir()、

    mkfifo()、 open()、pathconf()、pause()、pipe()、raise()、

    read()、rename()、rmdir()、setgid()、setpgid()、setsid()、

    setuid()、 sigaction()、sigaddset()、sigdelset()、sigemptyset()、

    sigfillset()、sigismember()、signal()、sigpending()、     

    sigprocmask()、sigsuspend()、sleep()、stat()、sysconf()、      

    tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、

    tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、

    umask()、uname()、unlink()、utime()、wait()、waitpid()、

    write()。


即使信号处理函数使用的都是"安全函数",同样要注意进入处理函数时,

首先要保存errno的值,结束时,再恢复原值。因为,信号处理过程中,

errno值随时可能被改变。另外,longjmp()以及siglongjmp()没有被列为可重入函数,

因为不能保证紧接着两个函数的其它调用是安全的。

分享到:
评论
1 楼 bzhao 2015-08-29  
好,赞扬!

相关推荐

    计算机二级公共基础知识模 拟试题及答案详解.pdf

    计算机二级公共基础知识模 拟试题及答案详解.pdf

    电子工程领域的语音发射机电路设计与实现

    内容概要:本文档详细介绍了语音发射机的设计与实现,涵盖了从硬件电路到具体元件的选择和连接方式。文档提供了详细的电路图,包括电源管理、信号处理、音频输入输出接口以及射频模块等关键部分。此外,还展示了各个引脚的功能定义及其与其他组件的连接关系,确保了系统的稳定性和高效性能。通过这份文档,读者可以全面了解语音发射机的工作原理和技术细节。 适合人群:对电子工程感兴趣的初学者、从事嵌入式系统开发的技术人员以及需要深入了解语音发射机制的专业人士。 使用场景及目标:适用于希望构建自己的语音发射设备的研究人员或爱好者,帮助他们掌握相关技术和实际操作技能。同时,也为教学机构提供了一个很好的案例研究材料。 其他说明:文档不仅限于理论讲解,还包括具体的实施步骤,使读者能够动手实践并验证所学知识。

    易语言注册机源码详解:单线程架构下的接码、滑块验证与IP代理实现

    内容概要:本文详细介绍了用易语言编写的单线程全功能注册机源码,涵盖了接码平台对接、滑块验证处理、IP代理管理以及料子导入等多个核心功能。文章首先展示了主框架的初始化配置和事件驱动逻辑,随后深入探讨了接码平台(如打码兔)的API调用及其返回数据的处理方法。对于滑块验证部分,作者分享了如何利用易语言的绘图功能模拟真实用户的操作轨迹,并提高了验证通过率。IP代理模块则实现了智能切换策略,确保代理的有效性和稳定性。此外,料子导入功能支持多种格式的数据解析和去重校验,防止脏数据污染。最后,文章提到了状态机设计用于控制注册流程的状态持久化。 适合人群:有一定编程基础,尤其是熟悉易语言的开发者和技术爱好者。 使用场景及目标:适用于希望深入了解易语言注册机开发的技术细节,掌握接码、滑块验证、IP代理等关键技术的应用场景。目标是帮助读者理解并优化现有注册机的功能,提高其稳定性和效率。 其他说明:文中提到的部分技术和实现方式可能存在一定的风险,请谨慎使用。同时,建议读者在合法合规的前提下进行相关开发和测试。

    计算机绘图实用教程 第三章.pdf

    计算机绘图实用教程 第三章.pdf

    计算机辅助设计—AutoCAD 2018中文版基础教程 各章CAD图纸及相关说明汇总.pdf

    计算机辅助设计—AutoCAD 2018中文版基础教程 各章CAD图纸及相关说明汇总.pdf

    计算机类电子书集合PDF

    C++相关书籍,计算机相关书籍,linux相关及http等计算机学习、面试书籍。

    计算机二级mysql数据库程序设计练习题(一).pdf

    计算机二级mysql数据库程序设计练习题(一).pdf

    计算机发展史.pdf

    计算机发展史.pdf

    计算机二级课件.pdf

    计算机二级课件.pdf

    计算机概论第三讲:计算机组成.pdf

    计算机概论第三讲:计算机组成.pdf

    端侧算力网络白皮书:6G时代终端算力资源高效利用与应用场景解析

    内容概要:本文档由中国移动通信集团终端有限公司、北京邮电大学、中国信息通信研究院和中国通信学会共同发布,旨在探讨端侧算力网络(TCAN)的概念、架构、关键技术及其应用场景。文中详细分析了终端的发展现状、基本特征和发展趋势,阐述了端侧算力网络的定义、体系架构、功能架构及其主要特征。端侧算力网络通过整合海量泛在异构终端的算力资源,实现分布式多级端侧算力资源的高效利用,提升网络整体资源利用率和服务质量。关键技术涵盖层次化端算力感知图模型、资源虚拟化、数据压缩、多粒度多层次算力调度、现场级AI推理和算力定价机制。此外,还探讨了端侧算力网络在智能家居、智能医疗、车联网、智慧教育和智慧农业等领域的潜在应用场景。 适合人群:从事通信网络、物联网、边缘计算等领域研究和开发的专业人士,以及对6G网络和端侧算力网络感兴趣的学者和从业者。 使用场景及目标:适用于希望深入了解端侧算力网络技术原理、架构设计和应用场景的读者。目标是帮助读者掌握端侧算力网络的核心技术,理解其在不同行业的应用潜力,推动端侧算力网络技术的商业化和产业化。 其他说明:本文档不仅提供了端侧算力网络的技术细节,还对其隐私与安全进行了深入探讨

    学习java的心得体会.docx

    学习java的心得体会.docx

    计算机二级考试(南开100题齐全).pdf

    计算机二级考试(南开100题齐全).pdf

    计算机二级C语言考试通关宝典:全面解析核心知识点与解题技巧

    内容概要:本文详细介绍了计算机二级C语言考试的内容和备考方法。首先概述了计算机二级考试的意义及其在计算机技能认证中的重要性,重点讲解了C语言的基础语法,包括程序结构、数据类型、运算符和表达式等。接着深入探讨了进阶知识,如函数、数组、指针、结构体和共用体的应用。最后分享了针对选择题、填空题和编程题的具体解题技巧,强调了复习方法和实战演练的重要性。 适合人群:准备参加计算机二级C语言考试的学生和技术爱好者。 使用场景及目标:①帮助考生系统地掌握C语言的核心知识点;②提供有效的解题策略,提高应试能力;③指导考生制定合理的复习计划,增强实战经验。 其他说明:本文不仅涵盖了理论知识,还提供了大量实例代码和详细的解释,有助于读者更好地理解和应用所学内容。此外,文中提到的解题技巧和复习建议对实际编程也有很大帮助。

    论文格式及要求.doc

    论文格式及要求.doc

    三菱FX3U与台达变频器RS485通信程序设置及应用实例

    内容概要:本文详细介绍了如何使用三菱FX3U PLC及其485BD通信板与四台台达VFD-M系列变频器进行通信的设置与应用。主要内容涵盖硬件连接注意事项、通信参数配置、RS指令的应用、CRC校验算法的实现以及频率给定和状态读取的具体方法。文中提供了多个实用的编程示例,展示了如何通过梯形图和结构化文本编写通信程序,并讨论了常见的调试技巧和优化建议。此外,还提到了系统的扩展性和稳定性措施,如增加温度传感器通信功能和应对电磁干扰的方法。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是那些熟悉三菱PLC和台达变频器的使用者。 使用场景及目标:适用于需要实现多台变频器联动控制的工业应用场景,旨在提高生产效率和系统可靠性。通过学习本文,读者可以掌握如何构建稳定的RS485通信网络,确保变频器之间的高效协同工作。 其他说明:本文不仅提供了详细的理论指导,还包括了许多来自实际项目的经验教训,帮助读者避免常见错误并提升编程技能。

    计算机服务规范.pdf

    计算机服务规范.pdf

    Discuz-X3.2-TC-UTF8.zip

    Discuz_X3.2_TC_UTF8.zip LNMP搭建安装包

    2023年房地产行业研究报告:缓解竣工下行加速的两大改革.pdf

    2023年房地产行业研究报告:缓解竣工下行加速的两大改革

    win32汇编环境,网络编程入门之十五

    win32汇编环境,网络编程入门之十五

Global site tag (gtag.js) - Google Analytics