-
前端页面中的common.js工具类部分方法解释(axios异步查询,formatPrice,formatDate,stringify)
2020-11-12 21:33:04在前端这里,还有我们自己封装的通用方法,axios是异步查询的意思,什么是异步查询,复习一下,异步查询就是查询等待的过程中浏览器页面返回一个进度条给访问者。红框加上旁边的两个方法的意思就是,配置基础路径,...在前端这里,还有我们自己封装的通用方法,axios是异步查询的意思,什么是异步查询,复习一下,异步查询就是查询等待的过程中浏览器页面返回一个进度条给访问者。红框加上旁边的两个方法的意思就是,配置基础路径,加上网关后缀,超时时间,还有是否保存cookie
http的作用是可以直接ly.http.get或者post就可以直接用axios异步查询。store是购物车方法。
-
Linux设备驱动中的异步通知与异步I/O
2018-08-14 21:11:28异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。信号是在软件层次上对中断机制的一...异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O中使用poll()意味着查询设备是否可访问,而异步通知则意味着设备通知用户自身可访问,之后用户再进行I/O处理。由此可见,这几种I/O方式可以相互补充。
一个信号被捕获的意思是当一个信号到达时有相应的代码处理它。如果一个信号没有被这个进程所捕获,内核将采用默认行为处理。
信号的接收
在用户程序中,为了捕获信号,可以使用signal()函数来设置对应的信号处理函数:
void (*signal (int signum, void (*handler))(int ))) (int);
可以分解为:
typedef void (*signadler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
第一个参数signum指定信号的值,第二个函数指针参数指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认的方式处理信号;若为用户自定义的函数,则信号被捕获后,该函数将被执行。
如果signal()调用成功,它返回最后一次为信号signum绑定的处理函数的handler值,失败则返回SIG_ERR。在进程执行时,按下“Ctrl+C”将向其发出SIGINT信号,正在运行kill的进程将向其发出SIGTERM信
号,以下代码中的进程可捕获这两个信号并输出信号值。
void sigterm_handler(int signo)
{
printf(“Have caught sig N.O. %d\n”, signo);
exit(0);}
int main(void)
{
signal(SIGINT, sigterm_handler);
signal(SIGTERM, sigterm_handler);
while(1);
return 0;
}除了signal()函数外,sigaction()函数可用于改变进程接收到特定信号后的行为,它的原型为:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
该函数的第一个参数为信号的值,可以是除SIGKILL及SIGSTOP外的任何一个特定有效的信号。第二个参数是指向结构体sigaction的一个实例的指针,在结构体sigaction的实例中,指定了对特定信号的处理函数,若为空,则进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理函数,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性.先来看一个使用信号实现异步通知的例子,它通过signal(SIGIO,input_handler)对标准输入文件描述符STDIN_FILENO启动信号机制。用户输入后,应用程序将接收到SIGIO信号,其处理函数input_handler()将被调用.
include
include
include
include
include
include
define MAX_LEX 100
void input_handler(int num)
{
char data[MAX_LEN];
int len;
// 读取并输出STDIN_FILENO上的输入
len = read(STDIN_FILENO, &data, MAX_LEN);
data[len] = 0;
printf(“input available: %d\n”, data);
}void main()
{
int oflags;
// 启动信号驱动机制
signal(SIGIO, input_handler); // 将SIGIO信号同input_handler函数关联起来,一旦产生SIGIO信号,就会执行input_handler函数
// STDIN_FILENO是打开的设备文件描述符,F_SETOWN用来决定操作是干什么的,getpid()是个系统调用,功能是返回当前进程
//的进程号,整个函数的功能是STDIN_FILENO设置这个设备文件拥有者为当前进程
fcntl(STDIN_FILENO, F_SETOWN, getpid());
oflags = fcntl(STDIN_FILENO, F_SETFL, ofags | FASYNC); // 得到打开文件描述符的状态
// 设置文件描述符的状态为oflags | FASYNC属性,一旦文件描述符被设置程具有FASYNC属性的状态,也就是将设备文件切换到
// 异步操作模式,这时系统会自动调用驱动程序的fasync方法。
fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);
// 最后进入一个死循环,仅为保持进程不终止,如果程序中没有这个死循环会立即执行完毕
while(1);
}ssize_t (read)(struct file *filp, char __user *buffer, size_t size, loff_t p);
filp:为进行读取信息的目标文件;
buffer:为放置信息的缓冲区(即用户空间地址);
size:为要读取的信息长度;
p:为要读取的位置相对文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取的信息的长度;ssize_t (write) (struct file *filp, char __user buffer, size_t count , loff_t *ppos);
filp :为目标文件结构体指针;
buffer :为要写入文件的信息缓冲区;
count :为要写入信息的长度;
ppos :为当前的偏移位置,这个值通常是用来判断写文件是否越界fcntl()
include
include
include
include
include
include
-
Linux异步机制
2016-05-10 18:24:09什么是异步通知:很简单,一旦设备准备好,就主动通知应用程序,这种情况下应用程序就不需要查询设备状态, 特像硬件上常提的“中断的概念”。 比较准确的说法其实应该叫做“信号驱动的异步I/O”,信号是在软件层次上...什么是异步通知:很简单,一旦设备准备好,就主动通知应用程序,这种情况下应用程序就不需要查询设备状态, 特像硬件上常提的“中断的概念”。 比较准确的说法其实应该叫做“信号驱动的异步I/O”,信号是在软件层次上对中断机制的一种模拟。阻塞I/O意味着一直等待设备可访问再访问,非阻塞I/O意味着使用poll()来查询是否可访问,而异步通知则意味着设备通知应用程序自身可访问。(希望用这么一句话能表达我的意思)
一、系统中存在的异步机制
我认为异步机制是一种理念,并不是某一种具体实现,同步/异步的核心理解应该是如何获取消息的问题,你自身(在计算机中当然是进程本身了)亲自去获取消息,那么就是同步机制,但是如果别人使用某种方式通知你某一个消息,那么你采用的就是异步机制。内核中使用到异步机制的大概有:信号,这是一种进程间通信的异步机制;epoll,这是一种高效处理IO的异步通信机制。也就是从通信和IO两个方面通过不同的方式使用了异步机制。(可能还有别的,暂时不知道)
下面进入正题:
二、信号的基本概念
1)信号的本质
软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。
收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:
第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。
第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。
第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。
在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号,当有信号发送给进程时,对应位置位。由此可以看出,进程对不同的信号可以同时保留,但对于同一个信号,进程并不知道在处理之前来过多少个。
2)信号的种类
可以从两个不同的分类角度对信号进行分类:
可靠性方面:可靠信号与不可靠信号;
与时间的关系上:实时信号与非实时信号。
3)可靠信号与不可靠信号
Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是信号可能丢失。
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
4)实时信号与非实时信号
早期Unix系统只定义了32种信号,前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
5)linux 下信号的生命周期如下:
在目的进程中安装该信号。即是设置捕获该信号时进程进程该执行的操作码。采用signal();sigaction()系统调用来实现。
信号被某个进程产生,同时设置该信号的目的进程(使用pid),之后交给操作系统进行管理。采用kill()、arise()、alarm()等系统调用来实现。
信号在目的进程被注册。信号被添加进进程的PCB(task_struct)中相关的数据结构里——未决信号的数据成员。信号在进程中注册就是把信号值加入到进程的未决信号集里。
并且,信号携带的其他信息被保留到未决信的队列的某个sigqueue结构中。
信号在进程中注销。在执行信号处理函数前,要把信号在进程中注销。对于非实时信号(不可靠信号),其在信号未决信号信息链中最多只有一个sigqueue结构,因此该结构被释放后,相应的信号要在未决信号集删除。而实时信号(可靠信号),如果有多个sigqueue,则不会把信号从进程的未决信号集中删除。
信号生命的终结。进程终止当前的工作,保护上下文,执行信号处理函数,之后回复。如果内核是可抢占的,那么还需要调度。三、信 号 机 制
上 一节中介绍了信号的基本概念,在这一节中,我们将介绍内核如何实现信号机制。即内核如何向一个进程发送信号、进程如何接收一个信号、进程怎样控制自己对信 号的反应、内核在什么时机处理和怎样处理进程收到的信号。还要介绍一下setjmp和longjmp在信号中起到的作用。
1、内核对信号的基本处理方法内 核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。这里要补充的是,如果信号发送给一个正在睡眠的进程,那么要看 该进程进入睡眠的优先级,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。这一点比较重要,因为进程检 查是否收到信号的时机是:一个进程在即将从内核态返回到用户态时;或者,在一个进程要进入或离开一个适当的低调度优先级睡眠状态时。
进程的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;
}
信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册)。
当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册(通过sigset_t signal指示),则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构。
总之信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)
内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。
内 核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。前面介绍概念的时候讲过,处理信号有三种类型:进程接收到信号后退 出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似 的继续运行。如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创 建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时, 才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权 限)。
对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则执行完相应的处理函数后应该把信号在进程的未决信号集中删除(信号注销完毕)。否则待该信号的所有sigqueue处理完毕后再在进程的未决信号集中删除该信号。
当所有未被屏蔽的信号都处理完毕后,即可返回用户空间。对于被屏蔽的信号,当取消屏蔽后,在返回到用户空间时会再次执行上述检查处理的一套流程。
在信号的处理方法中有几点特别要引起注意。第一,在一些系统中,当一个进程处理完中断信号返回用户态之前,内核清除用户区中设 定的对该信号的处理例程的地址,即下一次进程对该信号的处理方法又改为默认值,除非在下一次信号到来之前再次使用signal系统调用。这可能会使得进程 在调用signal之前又得到该信号而导致退出。在BSD中,内核不再清除该地址。但不清除该地址可能使得进程因为过多过快的得到某个信号而导致堆栈溢 出。为了避免出现上述情况。在BSD系统中,内核模拟了对硬件中断的处理方法,即在处理某个中断时,阻止接收新的该类中断。
第二个要 引起注意的是,如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断的优先级上,这时该信号引起进程作一次longjmp,跳出睡眠 状态,返回用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回一样,但返回了一个错误代码,指出该次系统调用曾经被中断。这要注 意的是,BSD系统中内核可以自动地重新开始系统调用。
第三个要注意的地方:若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒,但不做longjmp,一般是继续睡眠。但用户感觉不到进程曾经被唤醒,而是象没有发生过该信号一样。
第 四个要注意的地方:内核对子进程终止(SIGCLD)信号的处理方法与其他信号有所区别。当进程检查出收到了一个子进程终止的信号时,缺省情况下,该进程 就象没有收到该信号似的,如果父进程执行了系统调用wait,进程将从系统调用wait中醒来并返回wait调用,执行一系列wait调用的后续操作(找 出僵死的子进程,释放子进程的进程表项),然后从wait中返回。SIGCLD信号的作用是唤醒一个睡眠在可被中断优先级上的进程。如果该进程捕捉了这个 信号,就象普通信号处理一样转到处理例程。如果进程忽略该信号,那么系统调用wait的动作就有所不同,因为SIGCLD的作用仅仅是唤醒一个睡眠在可被 中断优先级上的进程,那么执行wait调用的父进程被唤醒继续执行wait调用的后续操作,然后等待其他的子进程。
如果一个进程调用signal系统调用,并设置了SIGCLD的处理方法,并且该进程有子进程处于僵死状态,则内核将向该进程发一个SIGCLD信号。 2、setjmp和longjmp的作用
前面在介绍信号处理机制时,多次提到了setjmp和longjmp,但没有仔细说明它们的作用和实现方法。这里就此作一个简单的介绍。
在 介绍信号的时候,我们看到多个地方要求进程在检查收到信号后,从原来的系统调用中直接返回,而不是等到该调用完成。这种进程突然改变其上下文的情况,就是 使用setjmp和longjmp的结果。setjmp将保存的上下文存入用户区,并继续在旧的上下文中执行。这就是说,进程执行一个系统调用,当因为资 源或其他原因要去睡眠时,内核为进程作了一次setjmp,如果在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用longjmp,该操作是内核 为进程将原先setjmp调用保存在进程用户区的上下文恢复成现在的上下文,这样就使得进程可以恢复等待资源前的状态,而且内核为setjmp返回1,使 得进程知道该次系统调用失败。这就是它们的作用。
同步和异步:与消息的通知机制有关。
本质区别
现实例子
同步模式
由处理消息者自己去等待消息是否被触发
我去银行办理业务,选择排队等,排到头了就办理。
异步模式
由触发机制来通知处理消息者
我去银行办理业务,取一个小纸条上面有我的号码,等到排到我这一号时由柜台的人通知我轮到我去办理业务。
阻塞与非阻塞:与线程等待消息(无所谓同步或者异步)时的状态有关。
本质区别
现实例子
阻塞调用
线程挂起,不能做其他事。
上面的那个例子,不论是排队还是使用号码等待通知,如果在这个等待的过程中,等待者除了等待消息之外不能做其它的事情,那么该机制就是阻塞的。
非阻塞调用
线程活跃,能处理其他事。
在银行办理这些业务的时候一边打打电话发发短信一边等待,这样的状态就是非阻塞的。
它们之间的组合应用举例:
阻塞调用
非阻塞调用
同步模式
read/write
read/write
O_NONBLOCK
异步模式
IO复用:select/poll,epoll(LT模式)
AIO系列:aio_read,aio_write等;epoll(ET模式)
同步阻塞IO
最常用的一个模型是同步阻塞 I/O 模型。在这个模型中,用户空间的应用程序执行一个系统调用,这会导致应用程序阻塞。这意味着应用程序会一直阻塞,直到系统调用完成为止(数据传输完成或发生错误)。调用应用程序处于一种不再消费 CPU 而只是简单等待响应的状态,因此从处理的角度来看,这是非常有效的。
图 1 给出了传统的阻塞 I/O 模型,这也是目前应用程序中最为常用的一种模型。其行为非常容易理解,其用法对于典型的应用程序来说都非常有效。在调用
read
系统调用时,应用程序会阻塞并对内核进行上下文切换。然后会触发读操作,当响应返回时(从我们正在从中读取的设备中返回),数据就被移动到用户空间的缓冲区中。然后应用程序就会解除阻塞(read
调用返回)。图1. 同步阻塞 I/O 模型的典型流程
从应用程序的角度来说,read 调用会延续很长时间。实际上,在内核执行读操作和其他工作时,应用程序的确会被阻塞。
同步非阻塞I/O
同步阻塞 I/O 的一种效率稍低的变种是同步非阻塞 I/O。在这种模型中,设备是以非阻塞的形式打开的。这意味着 I/O 操作不会立即完成,read 操作可能会返回一个错误代码,说明这个命令不能立即满足(EAGAIN 或EWOULDBLOCK),如图 2 所示。
图2. 同步非阻塞 I/O 模型的典型流程
非阻塞的实现是 I/O 命令可能并不会立即满足,需要应用程序调用许多次来等待操作完成(轮询)。这可能效率不高,因为在很多情况下,当内核执行这个命令时,应用程序必须要进行忙碌等待,直到数据可用为止,或者试图执行其他工作。正如图 2 所示的一样,这个方法可以引入 I/O 操作的延时,因为数据在内核中变为可用到用户调用
read
返回数据之间存在一定的间隔,这会导致整体数据吞吐量的降低。异步阻塞IO
另外一个阻塞解决方案是带有阻塞通知的非阻塞 I/O。在这种模型中,配置的是非阻塞 I/O,然后使用阻塞
select
系统调用来确定一个 I/O 描述符何时有操作。使select
调用非常有趣的是它可以用来为多个描述符提供通知,而不仅仅为一个描述符提供通知。对于每个提示符来说,我们可以请求这个描述符可以写数据、有读数据可用以及是否发生错误的通知。图3 异步阻塞IO模型的典型流程(select)
select
调用的主要问题是它的效率不是非常高。尽管这是异步通知使用的一种方便模型,但是对于高性能的I/O 操作来说不建议使用。异步非阻塞IO
最后,异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会立即返回,说明
read
请求已经成功发起了。在后台完成读操作时,应用程序然后会执行其他处理操作。当read
的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。图4 异步非阻塞IO模型的典型流程
在一个进程中为了执行多个 I/O 请求而对计算操作和 I/O 处理进行重叠处理的能力利用了处理速度与 I/O 速度之间的差异。当一个或多个 I/O 请求挂起时,CPU 可以执行其他任务;或者更为常见的是,在发起其他 I/O 的同时对已经完成的 I/O 进行操作。
异步IO的动机
从前面 I/O 模型的分类中,我们可以看出 AIO 的动机。这种阻塞模型需要在 I/O 操作开始时阻塞应用程序。这意味着不可能同时重叠进行处理和 I/O 操作。同步非阻塞模型允许处理和 I/O 操作重叠进行,但是这需要应用程序根据重现的规则来检查 I/O 操作的状态。这样就剩下异步非阻塞 I/O 了,它允许处理和 I/O 操作重叠进行,包括 I/O 操作完成的通知。
除了需要阻塞之外,
select
函数所提供的功能(异步阻塞 I/O)与 AIO 类似。不过,它是对通知事件进行阻塞,而不是对 I/O 调用进行阻塞。转载:
http://blog.chinaunix.net/u3/99982/showart_1996295.html
https://www.ibm.com/developerworks/cn/linux/l-async/ -
从零开始之驱动发开、linux驱动(十八、异步通知机制)
2018-09-21 00:11:41异步通知的意思是: 一旦设备就绪, 则驱动主动通知应用程序, 这样应用程序根本就不需要查询设备状态, 这一点非常类似于硬件上“中断”的概念, 比较准确的称谓是“信号驱动的异步I/O”。 信号是在软件层次上对...异步通知的意思是: 一旦设备就绪, 则驱动主动通知应用程序, 这样应用程序根本就不需要查询设备状态, 这一点非常类似于硬件上“中断”的概念, 比较准确的称谓是“信号驱动的异步I/O”。 信号是在软件层次上对中断机制的一种模拟, 在原理上, 一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。 信号是异步的, 一个进程不必通过任何操作来等待信号的到达, 事实上, 进程也不知道信号到底什么时候到达。
linux中的信号:
使用信号进行进程间通信(IPC) 是UNIX中的一种传统机制, Linux也支持这种机制。 在Linux中, 异步通知使用信号来实现(可以理解为异步通知是信号的一种情况), Linux中可用的信号及其定义如表:
除了SIGSTOP和SIGKILL两个信号外, 进程能够忽略或捕获其他的全部信号。 一个信号被捕获的意思
是当一个信号到达时有相应的代码处理它。 如果一个信号没有被这个进程所捕获, 内核将采用默认行为处
理。
信号的接受(应用层)
typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler)
第一个参数指定信号的值。
第二个参数指定针对前面信号值的处理函数, 若为SIG_IGN, 表示忽略该信号; 若为SIG_DFL, 表示采用系统默认方式处理信号; 若为用户自定义的函数, 则信号被捕获到后, 该函数将被执行。
如果signal() 调用成功, 它返回最后一次为信号signum绑定的处理函数的handler值, 失败则返回SIG_ERR。
在进程执行时, 按下“Ctrl+C”将向其发出SIGINT信号, 正在运行kill的进程将向其发出SIGTERM信号。
这里先给出信号接收函数。(应用层)
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <poll.h> #include <signal.h> int fd; void signal_handler(int signo) { char buf[2]; read(fd, buf, 1); printf("buf = %d\n", buf[0]); } int main(int argc,char *argv[]) { int flag; fd = open("/dev/button", O_RDWR); if(fd < 0) { printf("open /dev/%s fail\n",argv[1]); return -1; } /* 设置信号处理函数 */ signal(SIGIO, signal_handler); /* 设置本进程为fd文件的拥有者 */ fcntl(fd , F_SETOWN, getpid()); /* 得到本文件的默认flag */ flag = fcntl(fd, F_GETFL); /* 启动异步通知fd */ fcntl(fd ,F_SETFL, flag | FASYNC); while(1) { sleep(1); } close(fd); return 0; }
1.通过signal(SIGIO,signal_handler) 设置对应IO信号的处理函数。
signal(SIGIO, signal_handler);
2.设置本进程为fd文件的拥有者,没有这一步, 内核不会知道应该将信号发给哪个进程。
fcntl(fd , F_SETOWN, getpid());
3.给本文件的flag增加一个异步通知功能。
flag = fcntl(fd, F_GETFL); fcntl(fd ,F_SETFL, flag | FASYNC);
上面设置的其实是下面标记的两个文件相关的参数
struct file { union { struct llist_node fu_llist; struct rcu_head fu_rcuhead; } f_u; struct path f_path; #define f_dentry f_path.dentry struct inode *f_inode; /* cached value */ const struct file_operations *f_op; /* * Protects f_ep_links, f_flags. * Must not be taken from IRQ context. */ spinlock_t f_lock; atomic_long_t f_count; unsigned int f_flags; /* 存放文件的flag */ fmode_t f_mode; struct mutex f_pos_lock; loff_t f_pos; struct fown_struct f_owner; /* 表明该文件当前属于那个进程 */ const struct cred *f_cred; struct file_ra_state f_ra; u64 f_version; #ifdef CONFIG_SECURITY void *f_security; #endif /* needed for tty driver, and maybe others */ void *private_data; #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct list_head f_ep_links; struct list_head f_tfile_llink; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */ /* 描述文件的从属关系 */ struct fown_struct { rwlock_t lock; /* protects pid, uid, euid fields */ struct pid *pid; /* pid or -pgrp where SIGIO should be sent 进行号 */ enum pid_type pid_type; /* Kind of process group SIGIO should be sent to */ kuid_t uid, euid; /* uid/euid of process setting the owner */ int signum; /* posix.1b rt signal to be delivered on IO */ };
为了使设备支持异步通知机制, 驱动程序中涉及3项工作。
1) 支持F_SETOWN命令, 能在这个控制命令处理中设置filp->f_owner为对应进程ID。 不过此项工作已由内核完成, 设备驱动无须处理。
2) 支持F_SETFL命令的处理, 每当FASYNC标志改变时, 驱动程序中的fasync() 函数将得以执行。因此, 驱动中应该实现fasync() 函数。
3) 在设备资源可获得时, 调用kill_fasync() 函数激发相应的信号。驱动中的上述3项工作和应用程序中的3项工作是一一对应的。
其中fcntl函数我们这里不详细分析,这里只列出和上面有关的部分。
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg) { struct fd f = fdget_raw(fd); long err = -EBADF; if (!f.file) goto out; if (unlikely(f.file->f_mode & FMODE_PATH)) { if (!check_fcntl_cmd(cmd)) goto out1; } err = security_file_fcntl(f.file, cmd, arg); if (!err) err = do_fcntl(fd, cmd, arg, f.file); /* 执行这个 */ out1: fdput(f); out: return err; } static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, struct file *filp) { long err = -EINVAL; switch (cmd) { ...... case F_GETFL: err = filp->f_flags; /* 得到标志 */ break; case F_SETFL: err = setfl(fd, filp, arg); /* 设置flag */ break; ...... case F_GETOWN: /* * XXX If f_owner is a process group, the * negative return value will get converted * into an error. Oops. If we keep the * current syscall conventions, the only way * to fix this will be in libc. */ err = f_getown(filp); /**/ force_successful_syscall_return(); break; case F_SETOWN: err = f_setown(filp, arg, 1); /* 设置拥有者 */ break; ...... default: break; } return err; } static int setfl(int fd, struct file * filp, unsigned long arg) { struct inode * inode = file_inode(filp); int error = 0; /* * O_APPEND cannot be cleared if the file is marked as append-only * and the file is open for write. */ if (((arg ^ filp->f_flags) & O_APPEND) && IS_APPEND(inode)) return -EPERM; /* O_NOATIME can only be set by the owner or superuser */ if ((arg & O_NOATIME) && !(filp->f_flags & O_NOATIME)) if (!inode_owner_or_capable(inode)) return -EPERM; /* required for strict SunOS emulation */ if (O_NONBLOCK != O_NDELAY) if (arg & O_NDELAY) arg |= O_NONBLOCK; if (arg & O_DIRECT) { if (!filp->f_mapping || !filp->f_mapping->a_ops || !filp->f_mapping->a_ops->direct_IO) return -EINVAL; } if (filp->f_op->check_flags) error = filp->f_op->check_flags(arg); if (error) return error; /* * ->fasync() is responsible for setting the FASYNC bit. */ if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) { error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0); /* 在这里调用我们的驱动里面的函数 */ if (error < 0) goto out; if (error > 0) error = 0; } spin_lock(&filp->f_lock); filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK); spin_unlock(&filp->f_lock); out: return error; }
信号发送函数(异步通知的在驱动中发送)
#include <linux/fs.h> /* 包含file_operation结构体 */ #include <linux/init.h> /* 包含module_init module_exit */ #include <linux/module.h> /* 包含LICENSE的宏 */ #include <linux/io.h> #include <linux/delay.h> #include <linux/wait.h> #include <linux/gfp.h> #include <linux/interrupt.h> #include <linux/device.h> #include <linux/gpio.h> #include <linux/kernel.h> #include <linux/highmem.h> /* For wait_event_interruptible */ #include <linux/poll.h> #include <asm/gpio.h> #include <asm/uaccess.h> #include <linux/sysctl.h> static unsigned int major; static struct class *button_class; static struct device *button_dev; static unsigned char key_val; static struct fasync_struct *button_fasync = NULL; struct pin_desc { unsigned int pin; unsigned int key_val; }; /* 按下时 值分别是 0x01 , 0x02 */ /* 松开时 值分别是 0x00 , 0x00 */ static struct pin_desc pins_desc[] = { {S5PV210_GPH0(2), 0x01}, {S5PV210_GPH0(3), 0x02}, }; static irqreturn_t irq_handler(int irq, void *dev_id) { struct pin_desc *p = dev_id; int pin_val; pin_val = gpio_get_value(p->pin); /* 得到键值,判断时按下还是松开 */ if(pin_val) { /* 松开 */ key_val &= ~p->key_val; } else { /* 按下 */ key_val |= p->key_val; } /* 产生一个异步读信号 */ kill_fasync(&button_fasync, SIGIO, POLL_IN); return IRQ_HANDLED; } /* open函数 */ static int button_drv_open(struct inode *inode, struct file *file) { int ret = 0; ret = request_irq(IRQ_EINT(2), irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "irq-eint2",&pins_desc[0]); if(ret) { printk(KERN_ERR"request_irq IRQ_EINT(2) fail"); } ret = request_irq(IRQ_EINT(3), irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "irq-eint3",&pins_desc[1]); if(ret) { printk(KERN_ERR"request_irq IRQ_EINT(3) fail"); } return 0; } static ssize_t button_drv_read(struct file *file, char __user *array, size_t size, loff_t *ppos) { int len; if(size < 1) { return -EINVAL; } /* 赋值只是为了消除告警 */ len = copy_to_user(array , &key_val, 1); return 1; } int button_drv_fasync (int fd, struct file *file, int on) { printk(KERN_INFO"button_drv_fasync\n"); /* 增加一个异步通知到到本设备的异步通知队列中 */ return fasync_helper(fd , file, on, &button_fasync); } static int button_drv_close(struct inode *inode, struct file *file) { free_irq(IRQ_EINT(2), &pins_desc[0]); free_irq(IRQ_EINT(3), &pins_desc[1]); /* 移除一个async */ fasync_helper(-1, file, 0, &button_fasync); return 0; } static const struct file_operations button_drv_file_operation = { .owner = THIS_MODULE, .open = button_drv_open, .read = button_drv_read, .fasync = button_drv_fasync, .release = button_drv_close, }; static int __init button_drv_init(void) { /* 获取一个自动的主设备号 */ major = register_chrdev(0,"button_drv",&button_drv_file_operation); if(major < 0) { printk(KERN_ERR"register_chrdev button_drv fail \n"); goto err_register_chrdev; } /* 创建一个类 */ button_class = class_create(THIS_MODULE, "button_class"); if(!button_class) { printk(KERN_ERR"class_create button_class fail\n"); goto err_class_create; } /* 创建从属这个类的设备 */ button_dev = device_create(button_class,NULL,MKDEV(major, 0), NULL, "button"); if(!button_dev) { printk(KERN_ERR"device_create button_dev fail \n"); goto err_device_create; } return 0; /* 倒影式错误处理机制 */ err_device_create: class_destroy(button_class); err_class_create: unregister_chrdev(major,"button_drv"); err_register_chrdev: return -EIO; } static void __exit button_drv_exit(void) { /* 注销类里面的设备 */ device_unregister(button_dev); /* 注销类 */ class_destroy(button_class); /* 注销字符设备 */ unregister_chrdev(major,"button_drv"); } module_init(button_drv_init); module_exit(button_drv_exit); MODULE_LICENSE("GPL");
我们上面程序中,先是应用程序设置当前设备的FASYNC标志,他会调用驱动里面的fasync函数,而驱动中的fasync则继续调用fasync_helper函数,里面申请一个struct fasync_struct,并把申请的struct fasync_struct加入到设备驱动中定义的struct fasync_struct做为链表头的一个链表上,其中在里面会初始化和绑定好要异步通知的进程等信息。
其中
/* * fasync_helper() is used by almost all character device drivers * to set up the fasync queue, and for regular files by the file * lease code. It returns negative on error, 0 if it did no changes * and positive if it added/deleted the entry. * 加入或移除异步通知对于该设备的异步通知链表 */ int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) { if (!on) return fasync_remove_entry(filp, fapp); return fasync_add_entry(fd, filp, fapp); } 其中可以看到在一个文件支持异步通知时on传的是非0值,而移除异步通知功能时,是要传入0 /* * Add a fasync entry. Return negative on error, positive if * added, and zero if did nothing but change an existing one. */ static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp) { struct fasync_struct *new; new = fasync_alloc(); /* 申请空间 */ if (!new) return -ENOMEM; /* * fasync_insert_entry() returns the old (update) entry if * it existed. * * So free the (unused) new entry and return 0 to let the * caller know that we didn't add any new fasync entries. * 如果已经添加过,则返回1,释放掉新申请的 */ if (fasync_insert_entry(fd, filp, fapp, new)) { fasync_free(new); return 0; } return 1; } /* * Insert a new entry into the fasync list. Return the pointer to the * old one if we didn't use the new one. * * NOTE! It is very important that the FASYNC flag always * match the state "is the filp on a fasync list". */ struct fasync_struct *fasync_insert_entry(int fd, struct file *filp, struct fasync_struct **fapp, struct fasync_struct *new) { struct fasync_struct *fa, **fp; spin_lock(&filp->f_lock); spin_lock(&fasync_lock); for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) { if (fa->fa_file != filp) /* 判断有没有已经添加过 */ continue; spin_lock_irq(&fa->fa_lock); fa->fa_fd = fd; spin_unlock_irq(&fa->fa_lock); goto out; /* 已经添加过,则不用再添加,直接退出 */ } /* 初始化并绑定到链表上 */ spin_lock_init(&new->fa_lock); new->magic = FASYNC_MAGIC; new->fa_file = filp; new->fa_fd = fd; new->fa_next = *fapp; rcu_assign_pointer(*fapp, new); / filp->f_flags |= FASYNC; /* 该文件,增加异步通知标志 */ out: spin_unlock(&fasync_lock); spin_unlock(&filp->f_lock); return fa; } int fasync_remove_entry(struct file *filp, struct fasync_struct **fapp) { struct fasync_struct *fa, **fp; int result = 0; spin_lock(&filp->f_lock); spin_lock(&fasync_lock); for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) { if (fa->fa_file != filp) continue; spin_lock_irq(&fa->fa_lock); fa->fa_file = NULL; /* 异步通知链表中的该项先取消和文件关联 */ spin_unlock_irq(&fa->fa_lock); *fp = fa->fa_next; /* 绑定回掉函数 */ call_rcu(&fa->fa_rcu, fasync_free_rcu); filp->f_flags &= ~FASYNC; /* 取消该文件对应用进程的的异步通知功能 */ result = 1; break; } spin_unlock(&fasync_lock); spin_unlock(&filp->f_lock); return result; } /** * struct callback_head - callback structure for use with RCU and task_work * @next: next update requests in a list * @func: actual update function to call after the grace period. */ struct callback_head { struct callback_head *next; void (*func)(struct callback_head *head); }; #define rcu_head callback_head struct fasync_struct { spinlock_t fa_lock; int magic; int fa_fd; struct fasync_struct *fa_next; /* singly linked list */ struct file *fa_file; struct rcu_head fa_rcu; /* 回调函数 */ }; /* 通知该设备的异步通知链表上绑定的进程(设置时在用户空间也设置了fd,通过fd找到用户具体信号处理函数) */ void kill_fasync(struct fasync_struct **fp, int sig, int band) { /* First a quick test without locking: usually * the list is empty. */ if (*fp) { rcu_read_lock(); kill_fasync_rcu(rcu_dereference(*fp), sig, band); rcu_read_unlock(); } }
通过异步通知我们可以时应用程序不用阻塞,不用查询,得到按键数据。
-
六、Linux驱动之异步通知
2018-11-22 15:17:04异步通知的意思是: 一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。信号是在软件层次上对中断机制的... -
linux设备驱动中的异步通知机制
2020-09-05 11:47:15异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”.信号是在软件层次上对中断机制的一种模拟... -
关于网络模型中的同步异步的思考
2019-02-18 01:14:00最近写毕设的时候,写到了数据库部分,想要异步操作mysql,发现mysql并未提供对应异步... 对于(1)不必所说,(2)的意思是每条消息发出去后,异步callback的时候你要知道这是哪条信息的回复。举个例子,你分别向... -
Web SQL Database的异步机制
2017-03-16 01:46:17概述 Web SQL Database是一个主要基于异步的实现。其原理是, 发起SQL语句命令后, 不待结果传回, 立即将...什么意思呢? 看看下面的伪码: var id = executeSql("SELECT ID FROM tableA"); executeSql("SELECT * FROM -
Spring Boot系列之@Async异步调用
2020-11-11 22:51:28什么意思? 从接口请求到的数据,更新到本地数据库,这里有一个策略,先将数据放到Redis中,然后进行对比,如果不一致,再更新。Redis不可用,那么都查询数据库,就会很慢,前端请求接口一般是5s超时。 -
Linux设备驱动开发-linux驱动中的异步通知
2013-07-28 15:57:23异步通知的意思就是,一旦设备就绪,则主动通知应用程序,应用程序根本就不需要查询设备状态,类似于中断的概念,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何... -
linux内核中异步通知机制--信号处理机制
2014-04-08 11:09:44什么是异步通知:很简单,一旦设备准备好,就主动通知应用程序,这种情况下应用程序就不需要查询设备状态, 特像硬件上常提的“中断的概念”。 比较准确的说法其实应该叫做“信号驱动的异步I/O”,信号是在软件层次上... -
我的AJAX第四步
2009-02-17 11:56:00从前三步走过来以后,个人...比如实现一个异步查询,那么我需要接受查询结果吧,这个结果该如何接收呢,Javascript它貌似不认识datatable啊什么的东西吧。 恩,不能得意,还要继续啊。首先来试着做一个查询,先写... -
RxAndroid学习笔记<一>
2017-06-20 20:21:191.Rx的基本介绍:Rx是响应式编程的意思,本质是观察者模式,是以观察者(Observer)和订阅者(Subscriber)为基础的异步响应方式。 (1)缩写:ReactiveX时Reactive Extensions的缩写,一般简写为Rx (2)定义:Rx = ... -
线程线程杂谈(1)
2015-06-26 09:17:00讨论的问题: 网络编程中模型在所难免会遇到这样情况:在线程中需要不断的收取数据并进行处理,对于... (1)没有阻塞的意思是把阻塞函数设置成为了非阻塞; (2)线程中也没有加入延时操作函数进行阻塞; 下面以... -
java面试宝典
2013-02-28 16:04:0147、在java 中一个类被声明为final 类型,表示了什么意思? 12 48、下面哪些类可以被继承? 12 49、指出下面程序的运行结果: 【基础】 13 52、关于内部类: 13 53、数据类型之间的转换: 14 54、字符串操作:如何实现... -
千方百计笔试题大全
2011-11-30 21:58:3347、在java 中一个类被声明为final 类型,表示了什么意思? 12 48、下面哪些类可以被继承? 12 49、指出下面程序的运行结果: 【基础】 13 52、关于内部类: 13 53、数据类型之间的转换: 14 54、字符串操作:如何实现... -
最新Java面试宝典pdf版
2011-08-31 11:29:2218、Spring 的依赖注入是什么意思? 给一个 Bean 的 message 属性, 字符串类型, 注入值为 "Hello" 的 XML 配置文件该怎么写? 125 19、Jdo是什么? 125 20、什么是spring的IOC AOP 126 21、STRUTS的工作流程! 126 22、... -
Java面试宝典2010版
2011-06-27 09:48:2718、Spring 的依赖注入是什么意思? 给一个 Bean 的 message 属性, 字符串类型, 注入值为 "Hello" 的 XML 配置文件该怎么写? 19、Jdo是什么? 20、什么是spring的IOC AOP 21、STRUTS的工作流程! 22、spring 与EJB... -
java面试宝典2011整理有答案
2011-11-09 13:36:0618、Spring 的依赖注入是什么意思? 给一个 Bean 的 message 属性, 字符串类型, 注入值为 "Hello" 的 XML 配置文件该怎么写? 125 19、Jdo是什么? 125 20、什么是spring的IOC AOP 126 21、STRUTS的工作流程! 126 22、... -
ssm 框架已经实现,但是ajax返回验证有毛病
2018-03-20 15:02:48//还有一个参数表示的是异步还是同步,一般不写直接就是true,异步的 req.open("GET",url,true); //3333333333这个就是回调函数 req.onreadystatechange=callback; //这个send是在从servlet... -
超级有影响力霸气的Java面试题大全文档
2012-07-18 09:47:0427、GC是什么? 为什么要有GC? GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象... -
java 面试题 总结
2009-09-16 08:45:3424、GC是什么? 为什么要有GC? GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象... -
7.1 什么是Lambda表达式: Java 8 的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。 当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。 -> 是Java 8新增的Lambda表达式中,变量和...
-
"cm是什么意思单位", "cm是什么单位的名称" ] } 对于select多选类型表单,默认数据库保存值为半角逗号分隔的多个选择值。当你设置字段类型为无符号整型时,数据库会保存多个选择值的求和值(当然前提是选择值都是...
-
spring security 参考手册中文版
2018-02-01 17:05:182.1什么是Spring Security? 17 2.2历史 19 2.3版本编号 20 2.4获得Spring安全 21 2.4.1使用Maven 21 Maven仓库 21 Spring框架 22 2.4.2 Gradle 23 Gradle存储库 23 使用Spring 4.0.x和Gradle 24 2.4.3项目模块 25 ... -
Java 面试宝典
2013-02-01 10:02:0821、写 clone()方法时,通常都有一行代码,是什么? ............................................. 16 22、面向对象的特征有哪些方面 ........................................................................... -
MAPGIS地质制图工具
2013-05-06 16:15:30符号 代表意思 示例 符号 代表意思 示例 + 加 5+8=13 小于等于 ID — 减 8-5=3 == 等于 ID==85 × 乘 5×8=40 != 不等于 ID!=66 / 除 8/5=1.6 && 与 4&&8 % 求余 8%5=3 || 或 3||5 > 大于 ID>5 ^ 次方 ...