BEV_EVENT_ERROR
表示bufferevent在操作时有错误发生。需要更多关于错误信息,调用EVUTIL_SOCKET_ERROR()。
BEV_EVENT_TIMEOUT

if(events & BEV_EVENT_EOF)
{
    printf("connection closed \n");
}
else if(events & BEV_EVENT_ERROR)
{
    printf("err = %s\n", strerror(EVUTIL_SOCKET_ERROR()));
}
else if(events & BEV_EVENT_CONNECTED)
{
    printf("已经连接了服务器.....\n");
}

示例:
err = Connection refused

bufferevent基础和概念 | popozhu update:2021-10-2 很多时候,
一个程序除了响应事件之外还想缓存一些数据。比如当我们想写数据,通常会用这样的模式:

向一个连接写一些数据,先把数据存放到一个buffer里 等待连接变成可写状态 尽可能写入数据
记住我们写了多少数据,以及还有多少数据未写,然后等待下次连接再变成可写状态。
Libevent为这种带缓存的IO模式提供了一个通用的机制。一个”bufferevent”包含一个底层传输(比如socket),一个读buffer和一个写buffer。当底层传输可读写时就调用回调函数,这是普通事件的处理,而bufferevent是在读或写足够的数据时才调用用户指定的回调函数。

有多种类型的bufferevent使用相同的接口,比如下面的类型:

socket-based bufferevents 这种bufferevent的底层传输为stream
socket,使用event_*这种接口作为它的后端。 asynchronous-IO bufferevents
限于Windows,实验中。这种类型的bufferevent使用Windows IOCP接口来读写数据。 filtering
bufferevents
在数据被传送到bufferevent事件对象之前,这种buffevent会对将要读或写的数据进行预处理,比如压缩或转换数据。 paired
bufferevents 相互之间传输数据的一对bufferevent。 注意: 在Libevent
2.0.2-alpha这个版本,bufferevent的接口还不能通用于上面列出的bufferevent类型,也就是说,下面列出的每个接口不一定适用于所有bufferevent类型。未来版本中可能会改善。

再次注意: bufferevent当前只能适用于面向流的协议,比如TCP,而面向数据报文的协议,比如UDP,未来可能也会支持。

bufferevent和缓存 每个bufferevent都有一个输入缓存和一个输出缓存,对应的数据结构为struct
evbuffer。当你对一个bufferevent写数据,数据写入到输出缓存;当有数据可读时,你是从输入缓存中提取数据。

evbuffer的接口支持多种操作,后一节中会讨论到。

回调函数和水位标志(watermark)
每个bufferevent有2个相关连的回调函数:读回调和写回调。默认地,当从底层传输读到数据,读回调就被调用,当输出缓存的数据被清空,写回调就调用。但你可以调整bufferevent的“水位标志”来覆盖这些函数的默认行为。

每个bufferevent有4个水位标志:

读低水位Read low-water mark
当一个读操作让bufferevent的输入缓存达到或超出这个水位,读回调就被调用。默认为0,所以每个读操作都会导致读回调被执行。
读高水位Read high-water mark
当bufferevent的输入缓存达到这个水位,bufferevent停止读取数据,直到数据被取出,再次低于这个水位为止。
默认是不做限制,所以会不停地读取数据到输入缓存里。 写低水位Write low-water mark
当一个输出缓存达到或低于这个水位,写回调就被调用。默认为0,所以只有当输出缓存的数据全部发送出去之后,写回调才被调用。 写高水位Write
high-water mark
bufferevent不直接使用这个水位,当一个bufferevent被用于另一个bufferevent的地层传输时,这个标志可以有特殊的含义。见下面的filtering
bufferevents。
一个bufferevent也有一个error和event回调函数,这2个函数与数据无关,当一个连接被关闭或出现错误,它们会被调用来通知到应用程序。定义了如下的事件标志:

BEV_EVENT_READING 表示当bufferevent在读操作时有一个事件到来。 BEV_EVENT_WRITING
表示当bufferevent在写操作时有一个事件到来。 BEV_EVENT_ERROR
表示bufferevent在操作时有错误发生。需要更多关于错误信息,调用EVUTIL_SOCKET_ERROR()。
BEV_EVENT_TIMEOUT bufferevent的超时事件。 BEV_EVENT_EOF 到达文件末尾。
BEV_EVENT_CONNECTED bufferevent完成一个另一端发起请求的连接。

延迟的回调
默认上,当对应的情况发生,buffevent回调函数会马上被执行。(对于evbuffer的回调函数也是如此)当存在复杂依赖时这个立刻调用却反而带来麻烦。比如有一个回调函数,当evbuffer
A里的数据为空时执行,把数据写到buffer里,另有一个回调函数,当evbuffer
A的buffer数据满了时实行,把数据从buffer里读出。由于这些回调都在栈上执行,如果依赖足够冗长,则可能会出现栈溢出。

为了解决这个问题,你可以告诉bufferevent(或者evbuffer)应该推迟执行它的回调函数。这样当条件满足后回调不会立刻执行,而是进入event_loop()的队列里,当队列里的常规回调函数执行完毕后,这个延迟的回调函数才被调用。

bufferevent可选的标志 在创建一个bufferevent时你可以使用一个或多个标志。可用的标志有:

BEV_OPT_CLOSE_ON_FREE
当bufferevent被释放时,关闭底层传输。这会关闭一个底层的socker,释放一个底层的bufferevent等。
BEV_OPT_THREADSAFE 自动为bufferevent分配锁,所以可安全用于多线程。
BEV_OPT_DEFER_CALLBACKS 如果设置了这个标志,bufferevent推迟所有回调的执行,如上所述。
BEV_OPT_UNLOCK_CALLBACKS
默认上若一个bufferevent设置成线程安全,则当用户提供的回调调用时会获取该bufferevent的锁。设置这个标志来让Libevent在完成你的回调函数后释放bufferevent的锁。

使用基于socket的bufferevent
基于socket的bufferevent使用起来最简单。基于socket的bufferevent使用Libevent的底层事件机制来检测底层网络socket是否可读写,也使用底层网络调用(比如readv,writev,WSASend,或者WSARecv)来传输和接收数据。

创建一个基于socket的bufferevent 使用bufferevent_socket_new()来创建。接口如下:

struct bufferevent *bufferevent_socket_new(

struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options); base是event_base,options是bufferevent标志的位掩码(BEV_OPT_CLOSE_ON_FREE等)。而fd则是可选的socket fd,如果你想稍后再设置fd,可以传-1。

提示:
确保你提供的socket是非阻塞模式的,Libevent提供了一个便捷的函数evutil_make_socket_nonblocking()来设置一个socket为非阻塞。

如果成功,这个函数返回一个bufferevent,如果失败,返回NULL。

在bufferevent上启用连接 如果bufferevent的socket还没连接,你可以创建一个连接,接口:

int bufferevent_socket_connect(struct bufferevent *bev,

struct sockaddr *address, int addrlen); 参数address和addrlen跟标准的connect()一样。如果bufferevent还没有一个socker,调用这个函数会为它创建一个新的socket,并设置socket为非阻塞。

如果bufferevent已经有一个socket,调用bufferevent_socket_connect()告诉Libevent该socket未连接,在连接成功之前不会有读或写操作返回。

在连接返回前向输出buffer添加数据则是可以的。

如果连接成功建立,这个函数返回0,如果出现错误,返回-1。

Example

include <event2/event.h>

include <event2/bufferevent.h>

include <sys/socket.h>

include <string.h>

void eventcb(struct bufferevent bev, short events, void ptr) {

if (events & BEV_EVENT_CONNECTED) {
     /* We're connected to 127.0.0.1:8080.   Ordinarily we'd do
        something here, like start reading or writing. */
} else if (events & BEV_EVENT_ERROR) {
     /* An error occured while connecting. */
} }

int main_loop(void) {

struct event_base *base;
struct bufferevent *bev;
struct sockaddr_in sin;

base = event_base_new();

memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
sin.sin_port = htons(8080); /* Port 8080 */

bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);

bufferevent_setcb(bev, NULL, NULL, eventcb, NULL);

if (bufferevent_socket_connect(bev,
    (struct sockaddr *)&sin, sizeof(sin)) < 0) {
    /* Error starting connection */
    bufferevent_free(bev);
    return -1;
}

event_base_dispatch(base);
return 0; } 注意如果你尝试使用bufferevent_socket_connect()来触发调用connect(),你只会有一个BEV_EVENT_CONNECTED事件。如果你自己调用connect(),该连接触发的是一个写事件。

使用hostname来建立连接 更普遍的做法是把解析host和创建连接合并为一个操作,这里有一个接口:

int bufferevent_socket_connect_hostname(struct bufferevent *bev,

struct evdns_base *dns_base, int family, const char *hostname,
int port); int bufferevent_socket_get_dns_error(struct bufferevent *bev); 这个函数解析DNS名字hostname,查找地址的类属(AF_INET,AF_INET6和AF_UNSPEC)。如果解析失败则调用错误回调函数,如果成功则创建一个连接。

参数dns_base可选,如果为NULL,Libevent会阻塞直到完成域名查找,而这一般非你所愿。如果不为NULL,Libevent会使用它来异步查找域名。

跟bufferevent_socket_connect()一样,这个函数告诉Libevent任何已有的socket是未连接的,在完成域名解析且连接成功创建和返回前,该连接的读或写操作不应返回。

如果出现错误,可能是DNS域名查找出错,你可以调用bufferevent_socket_get_dns_error()来查看最近的错误。如果让会的error为0,则检测不到DNS错误。

Example: Trivial HTTP v0 client /* Don't actually copy this code: it
is a poor way to implement an HTTP client. Have a look at evhttp
instead.
*/

include <event2/dns.h>

include <event2/bufferevent.h>

include <event2/buffer.h>

include <event2/util.h>

include <event2/event.h>

include <stdio.h>

void readcb(struct bufferevent bev, void ptr) {

char buf[1024];
int n;
struct evbuffer *input = bufferevent_get_input(bev);
while ((n = evbuffer_remove(input, buf, sizeof(buf))) > 0) {
    fwrite(buf, 1, n, stdout);
} }

void eventcb(struct bufferevent bev, short events, void ptr) {

if (events & BEV_EVENT_CONNECTED) {
     printf("Connect okay.\n");
} else if (events & (BEV_EVENT_ERROR|BEV_EVENT_EOF)) {
     struct event_base *base = ptr;
     if (events & BEV_EVENT_ERROR) {
             int err = bufferevent_socket_get_dns_error(bev);
             if (err)
                     printf("DNS error: %s\n", evutil_gai_strerror(err));
     }
     printf("Closing\n");
     bufferevent_free(bev);
     event_base_loopexit(base, NULL);
} }

int main(int argc, char **argv) {

struct event_base *base;
struct evdns_base *dns_base;
struct bufferevent *bev;

if (argc != 3) {
    printf("Trivial HTTP 0.x client\n"
           "Syntax: %s [hostname] [resource]\n"
           "Example: %s www.google.com /\n",argv[0],argv[0]);
    return 1;
}

base = event_base_new();
dns_base = evdns_base_new(base, 1);

bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, readcb, NULL, eventcb, base);
bufferevent_enable(bev, EV_READ|EV_WRITE);
evbuffer_add_printf(bufferevent_get_output(bev), "GET %s\r\n", argv[2]);
bufferevent_socket_connect_hostname(
    bev, dns_base, AF_UNSPEC, argv[1], 80);
event_base_dispatch(base);
return 0; }

bufferevent通用操作 这节的函数适用于多种bufferevent实现。

释放一个bufferevent void bufferevent_free(struct bufferevent *bev);
Bufferevent在内部有引用计数,如果当你释放它时它有延迟的回调函数,则再回调被执行前它不会被释放。

然而bufferevent_free()会尝试尽快释放bufferevent,如果bufferevent上有等待写的数据,在bufferevent被释放前它可能不会刷新缓存。

如果设置了BEV_OPT_CLOSE_ON_FREE标志,且该buffereavent有一个socket或一个底层bufferevent,这个底层传输会在释放时被关闭。

操作回调、设置水位和启用功能 typedef void (*bufferevent_data_cb)(struct bufferevent
bev, void ctx); typedef void (bufferevent_event_cb)(struct bufferevent bev,

short events, void *ctx);

void bufferevent_setcb(struct bufferevent *bufev,

bufferevent_data_cb readcb, bufferevent_data_cb writecb,
bufferevent_event_cb eventcb, void *cbarg);

void bufferevent_getcb(struct bufferevent *bufev,

bufferevent_data_cb *readcb_ptr,
bufferevent_data_cb *writecb_ptr,
bufferevent_event_cb *eventcb_ptr,
void **cbarg_ptr); 函数bufferevent_setcb()改变一个多或多个回调函数。参数readcb, writecb和eventcb在可读、可写或有事件被触发时被调用(各自被调用)。最后一个用户提供的参数cbarg将作为参数传给bufferevent_callcb():你可以使用它来传递数据给你的回调函数。回调函数的参数events则是事件标志的位掩码。

你若给某个回调函数入参传一个NULL指针,则禁用该回调函数。注意到所有的回调函数共享入参cbarg,修改该参数会影响所有回调函数。

启用/禁用 void bufferevent_enable(struct bufferevent *bufev, short
events); void bufferevent_disable(struct bufferevent *bufev, short
events);

short bufferevent_get_enabled(struct bufferevent *bufev);
你可以在一个bufferevent上启用或禁用指定的事件:EV_READ, EV_WRITE,
或EV_READ|EV_WRITE。当读或写被禁用后,bufferevent将不会尝试读或写数据。
当输出缓存为空时没有必要禁用写操作:bufferevent会自动停止写,当有缓存里有数据时会自动重新开始写socket。
类似的,当输入缓存达到最高水位时也没必要禁用读操作,bufferevent会自动停止再读入数据,当输入缓存又有空间时bufferevent会自动重新开始读入数据到缓存。
默认上,一个新创建的bufferevent启用了写操作,但不包括读操作(读写buffer的操作)。
可以调用bufferevent_get_enabled()来查看bufferevent当前启用了哪些事件。

设置水位 void bufferevent_setwatermark(struct bufferevent *bufev, short
events,

size_t lowmark, size_t highmark); 函数bufferevent_setwatermark()调整读、写水位,或都调整。

如果参数events设置了EV_READ,则调整读水位,如果设置EV_WRITE则调整写水位。

高水位如果设置为0,则表示不限制。

Example

include <event2/event.h>

include <event2/bufferevent.h>

include <event2/buffer.h>

include <event2/util.h>

include <stdlib.h>

include <errno.h>

include <string.h>

struct info {

const char *name;
size_t total_drained; };

void read_callback(struct bufferevent bev, void ctx) {

struct info *inf = ctx;
struct evbuffer *input = bufferevent_get_input(bev);
size_t len = evbuffer_get_length(input);
if (len) {
    inf->total_drained += len;
    evbuffer_drain(input, len);
    printf("Drained %lu bytes from %s\n",
         (unsigned long) len, inf->name);
} }

void event_callback(struct bufferevent bev, short events, void ctx)
{

struct info *inf = ctx;
struct evbuffer *input = bufferevent_get_input(bev);
int finished = 0;

if (events & BEV_EVENT_EOF) {
    size_t len = evbuffer_get_length(input);
    printf("Got a close from %s.  We drained %lu bytes from it, "
        "and have %lu left.\n", inf->name,
        (unsigned long)inf->total_drained, (unsigned long)len);
    finished = 1;
}
if (events & BEV_EVENT_ERROR) {
    printf("Got an error from %s: %s\n",
        inf->name, evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
    finished = 1;
}
if (finished) {
    free(ctx);
    bufferevent_free(bev);
} }

struct bufferevent *setup_bufferevent(void) {

struct bufferevent *b1 = NULL;
struct info *info1;

info1 = malloc(sizeof(struct info));
info1->name = "buffer 1";
info1->total_drained = 0;

/* ... Here we should set up the bufferevent and make sure it gets
   connected... */

/* Trigger the read callback only whenever there is at least 128 bytes
   of data in the buffer. */
bufferevent_setwatermark(b1, EV_READ, 128, 0);

bufferevent_setcb(b1, read_callback, NULL, event_callback, info1);

bufferevent_enable(b1, EV_READ); /* Start reading. */
return b1; } 操作bufferevent里的数据 bufferevent提供一些函数让你可以向网络中读写数据:

Interface struct evbuffer *bufferevent_get_input(struct bufferevent
bufev); struct evbuffer bufferevent_get_output(struct bufferevent *bufev); 这两个基础函数非常有用,分别返回输入缓存和输出缓存。evbuffer更多操作函数,见下节。

注意,程序可能只从输入缓存里读取数据(而不添加数据),只向输出缓存里添加数据(而不删除)。

如果输出缓存里太少数据而导致bufferevent的写操作被推迟,可以向输出缓存里添加数据,bufferevent就会自动重新开始把输出缓存里的数据写socket。对于输入缓存也类似。

Interface int bufferevent_write(struct bufferevent *bufev,

const void *data, size_t size); int bufferevent_write_buffer(struct bufferevent *bufev,
struct evbuffer *buf); 这2个函数添加数据到输出缓存里,调用bufferevent_write()把内存里data位置的size字节添加到输出缓存的末尾。调用bufferevent_write_buffer()则把buf的整块内存添加到输出缓存的末端,并把buf的数据删除。

成功返回0,遇到出错返回-1。

Interface size_t bufferevent_read(struct bufferevent *bufev, void
data, size_t size); int bufferevent_read_buffer(struct bufferevent bufev,

struct evbuffer *buf); 这2个函数从输入缓存里读取数据。调用bufferevent_read()从输入缓存里删除size字节,存到内存的data位置,返回被删除的字节数。bufferevent_read_buffer()则删除输入缓存里的所有数据,存放到buf里,成功返回0,失败返回-1。

注意使用bufferevent_read(),内存data位置一定要有足够的空间来容纳size字节的数据。

Example

include <event2/bufferevent.h>

include <event2/buffer.h>

include <ctype.h>

void read_callback_uppercase(struct bufferevent bev, void ctx) {

    /* This callback removes the data from bev's input buffer 128
       bytes at a time, uppercases it, and starts sending it
       back.

       (Watch out!  In practice, you shouldn't use toupper to implement
       a network protocol, unless you know for a fact that the current
       locale is the one you want to be using.)
     */

    char tmp[128];
    size_t n;
    int i;
    while (1) {
            n = bufferevent_read(bev, tmp, sizeof(tmp));
            if (n <= 0)
                    break; /* No more data. */
            for (i=0; i<n; ++i)
                    tmp[i] = toupper(tmp[i]);
            bufferevent_write(bev, tmp, n);
    } }

struct proxy_info {

    struct bufferevent *other_bev; }; void read_callback_proxy(struct bufferevent *bev, void *ctx) {
    /* You might use a function like this if you're implementing
       a simple proxy: it will take data from one connection (on
       bev), and write it to another, copying as little as
       possible. */
    struct proxy_info *inf = ctx;

    bufferevent_read_buffer(bev,
        bufferevent_get_output(inf->other_bev)); }

struct count {

    unsigned long last_fib[2]; };

void write_callback_fibonacci(struct bufferevent bev, void ctx) {

    /* Here's a callback that adds some Fibonacci numbers to the
       output buffer of bev.  It stops once we have added 1k of
       data; once this data is drained, we'll add more. */
    struct count *c = ctx;

    struct evbuffer *tmp = evbuffer_new();
    while (evbuffer_get_length(tmp) < 1024) {
             unsigned long next = c->last_fib[0] + c->last_fib[1];
             c->last_fib[0] = c->last_fib[1];
             c->last_fib[1] = next;

             evbuffer_add_printf(tmp, "%lu", next);
    }

    /* Now we add the whole contents of tmp to bev. */
    bufferevent_write_buffer(bev, tmp);

    /* We don't need tmp any longer. */
    evbuffer_free(tmp); } 读和写的超时 跟其他事件一样,bufferevent如果一定时间之后都没有读写数据,会执行超时回调函数。

Interface void bufferevent_set_timeouts(struct bufferevent *bufev,

const struct timeval *timeout_read, const struct timeval *timeout_write); 传NULL给超时入参来删除对应的超时回调,然后在Libevent 2.1.2-alpha之前并不适用所有的事件类型。 注意,超时时间只有在bufferevent尝试读或写才开始计算,换句话说,如果bufferevent禁用读操作,或者输入缓存满了(达到最高水位),读操作超时时间不会启用。同样,写操作超时时间也并不会被启用如果禁用了些操作,或者没有数据可写。

如果读或写超时发生了,对应的读或写操作会被停止。事件回调函数会被执行,以标识BEV_EVENT_TIMEOUT|BEV_EVENT_READING或BEV_EVENT_TIMEOUT|BEV_EVENT_WRITING。

刷新bufferevent Interface int bufferevent_flush(struct bufferevent
*bufev,

short iotype, enum bufferevent_flush_mode state); 刷新bufferevent来告诉bufferevent忽略其他限制读或写操作的设置,强制从底层传输读或写尽可能多的数据。

参数iotype应为EV_READ, EV_WRITE, 或EV_READ|EV_WRITE,表示应该处理读、写操作,或两者。
参数state应为BEV_NORMAL, BEV_FLUSH,
或BEV_FINISHED。BEV_FINISHED表示应该告诉另一端数据发送完毕,BEV_NORMAL和BEV_FLUSH的差别跟bufferevent的类型有关。

函数bufferevent_flush()失败的话返回-1,如果没有数据可刷新,返回0,如果刷新了数据,返回1。

类型相关的bufferevent函数 这些函数并不支持所有类型的bufferevent。

Interface int bufferevent_priority_set(struct bufferevent *bufev, int
pri); int bufferevent_get_priority(struct bufferevent *bufev);
调整bufev的优先级为pri。参见event_priority_set()获取更多优先级的信息。

成功返回0,失败返回-1,只适用于基于socket的bufferevent。

Interface int bufferevent_setfd(struct bufferevent *bufev,
evutil_socket_t fd); evutil_socket_t bufferevent_getfd(struct
bufferevent *bufev);
设置或获取基于fd事件的文件描述符fd,只支持基于socket的bufferevent。失败返回-1,setfd()成功则返回0。

Interface struct event_base *bufferevent_get_base(struct bufferevent
*bev); 返回bufferevent的event_base。

Interface struct bufferevent *bufferevent_get_underlying(struct
bufferevent *bufev);
当底层传输使用的是另一个bufferevent,通过这个函数来获取这个底层传输的bufferevent。

手工加锁或解锁bufferevent
跟evbuffer一样,有时候你想确保bufferevent上得一些操作是原子操作的。Libevent提供供你手工加锁和解锁的函数。

Interface void bufferevent_lock(struct bufferevent *bufev); void
bufferevent_unlock(struct bufferevent *bufev);
注意,如果bufferevent在创建时如果没有提供BEV_OPT_THREADSAFE线程,或者启用Libevent的线程支持,加锁一个bufferevent是没有效果的。

加锁bufferevent也会锁上它关联的evbuffer。这些函数是可以重入的:对一个已获取的锁再次加锁是安全的,当然,也要由对应的解锁调用。

Referenced from:https://popozhu.github.io/2013/06/26/libevent_r5_bufferevent%25E5%259F%25BA%25E7%25A1%2580%25E5%2592%258C%25E6%25A6%2582%25E5%25BF%25B5/

寄存器是CPU的组成部分,因为在CPU内,所以CPU对其读写速度是最快的,不需要IO传输,但同时也决定了此类寄存器数量非常有限,有限到几乎每个存储都有自己的名字,而且有些还有多个名字。

 IA-32构架提供了16个基本寄存器,这16个基本寄存器可以归纳为如下几类: 通用寄存器 段寄存器 状态和控制寄存器 指令寄存器
        通用寄存器

32位通用寄存器有八个,eax, ebx, ecx, edx, esi, edi, ebp, esp,

他们主要用作逻辑运算、地址计算和内存指针,具体功能如下:

eax    累加和结果寄存器 ebx    数据指针寄存器 ecx    循环计数器 edx    i/o指针 esi    源地址寄存器
edi    目的地址寄存器 esp    堆栈指针 ebp    栈指针寄存器
当然,以上功能并未限制寄存器的使用,特殊情况为了效率也可作其他用途。 这八个寄存器低16位分别有一个引用别名 ax, bx, cx, dx,
bp, si, di, sp, 

其中 ax, bx, cx, dx, 的高8位又引用至 ah, bh, ch, dh,低八位引用至 al, bl, cl, dl

在 64-bit 模式下,有16个通用寄存器,但是这16个寄存器是兼容32位模式的, 32位方式下寄存器名分别为 eax, ebx,
ecx, edx, edi, esi, ebp, esp, r8d – r15d.  在64位模式下,他们被扩展为 rax, rbx,
rcx, rdx, rdi, rsi, rbp, rsp, r8 – r15.  其中 r8 – r15
这八个寄存器是64-bit模式下新加入的寄存器。 段寄存器

段寄存器 cs, ds, ss, es, fs, gs, 保存16位的段选择子,一个段选择子指定了一个段在内存的指针,

以便再内存中访问段,访问方式与内存模式有关,段模式和平坦模式其代表的意义并不相同。

cs    代码段寄存器

ds, es, fs, gs    数据段寄存器

ss    堆栈段寄存器

在 64-bit 模式下,这6个寄存器并无变化,只是使用上略有区别。

状态和控制寄存器 eflags 这个寄存器表示的意义非常丰富,程序中并不直接操作此寄存器,并由此衍生出很多操作指令。
除去一些保留位,其他每位都代表一个具体的含义, 其中 bits 0, 2, 4, 6, 7, 11 是状态位,标识了某此操作后的状态: CF
(bit 0) —— 进位标识,算术操作进行了进位和借位,则此位被设置 PF (bit 2) —— 奇偶标识,结果包含奇数个1,则设置此位
AF (bit 4) —— 辅助进位标识,结果的第3位像第4位借位,则此位被设置 ZF (bit 6) —— 零标识,结果为零,此位设置
SF (bit 7) —— 符号标识,若为负数则设置此位 OF (bit 11) ——
溢出标识,结果像最高位符号位进行借位或者进位,此标志被设置 8, 9, 10 位为控制标识:

TF (bit 8) —— 陷阱标识,设置进程可以被单步调试

IF (bit 9) —— 中断标识,设置能够响应中断请求

DF (bit 10) —— 方向标识,用于标示字符处理过程中指针移动方向。

64-bit模式下,该寄存器被扩展为64位,rflags,但是其高32位保留未被使用,其低32位所表示含义与32位模式相同。 指令寄存器
EIP EIP —— 标志当前进程将要执行指令位置,在64位模式下扩展为 RIP 64位指令寄存器。 控制寄存器 cr0, cr2,
cr3, cr4 系统表指针寄存器

idtr —— 中断描述符表信息 gdtr —— 全局描述符表信息 ldtr —— 局部描述符表信息 任务寄存器tr

保存任务的状态信息 tss

调试寄存器 dr0 – dr7,控制和允许监视进程的调试操作 x87 FPU 寄存器
这组指令专门用过浮点运算,因为浮点运算尤其固有的特性,所以需要使用一组独立寄存器。 数据寄存器包括 r0 – r7 的8个 80
位寄存器,汇编程序中通过名字 st(x) 引用,
另外还有3个16位寄存器,分别是控制寄存器,状态寄存器,标记寄存器。这里的省略具体含义的介绍。 MMX 寄存器 MMX 为一种 SIMD
技术,即可通过一条指令执行多个数据运算,共有8个64位寄存器,分别为mm0 – mm7,
他与其他普通64位寄存器的区别在于通过它的指令进行运算,可以同时计算2个32位数据,或者4个16位数据等等, 可以应用为图像处理过程中图形
颜色的计算。 另外需要特别注意的是,MMX并非一种新的寄存器,而是FPU
80位寄存器的低64位,也就是说,使用MMX指令集,会影响浮点运算! XMM 寄存器 XMM 同 MMX,只是他有 8 个 128
位寄存器,分别为 xmm0 – xmm7,另外还包含计算过程中的状态和控制寄存器

Referenced from:https://blog.csdn.net/qq_38880380/article/details/78907462

x86寄存器说明 ebp和esp是32位的SP,BP esp是堆栈指针 ebp是基址指针 ESP与SP的关系就象AX与AL,AH的关系.

32位CPU所含有的寄存器有:

4个数据寄存器(EAX、EBX、ECX和EDX) 2个变址和指针寄存器(ESI和EDI) 2个指针寄存器(ESP和EBP)
6个段寄存器(ES、CS、SS、DS、FS和GS) 1个指令指针寄存器(EIP) 1个标志寄存器(EFlags)

1、数据寄存器

数据寄存器主要用来保存操作数和运算结果等信息,从而节省读取操作数所需占用总线和访问存储器的时间。

32位CPU有4个32位的通用寄存器EAX、EBX、ECX和EDX。对低16位数据的存取,不会影响高16位的数据。这些
低16位寄存器分别命名为:AX、BX、CX和DX,它和先前的CPU中的寄存器相一致。

4个16位寄存器又可分割成8个独立的8位寄存器(AX:AH-AL、BX:BH-BL、CX:CH-CL、DX:DH-DL),每个寄
存器都有自己的名称,可独立存取。程序员可利用数据寄存器的这种“可分可合”的特性,灵活地处理字/字 节的信息。

寄存器AX和AL通常称为累加器(Accumulator),用累加器进行的操作可能需要更少时间。累加器可用于乘、
除、输入/输出等操作,它们的使用频率很高; 寄存器BX称为基地址寄存器(Base Register)。它可作为存储器指针来使用;
寄存器CX称为计数寄存器(Count Register)。在循环和字符串操作时,要用它来控制循环次数;在位操作
中,当移多位时,要用CL来指明移位的位数; 寄存器DX称为数据寄存器(Data
Register)。在进行乘、除运算时,它可作为默认的操作数参与运算,也 可用于存放I/O的端口地址。

在16位CPU中,AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址,但在32位CPU中,其32位
寄存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑运算结果,而且也可作为指针寄存器,
所以,这些32位寄存器更具有通用性。

2、变址寄存器

32位CPU有2个32位通用寄存器ESI和EDI。其低16位对应先前CPU中的SI和DI,对低16位数据的存取,不影响 高16位的数据。

寄存器ESI、EDI、SI和DI称为变址寄存器(Index Register),它们主要用于存放存储单元在段内的偏移量,
用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。

变址寄存器不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。

它们可作一般的存储器指针使用。在字符串操作指令的执行过程中,对它们有特定的要求,而且还具有特 殊的功能。

3、指针寄存器

32位CPU有2个32位通用寄存器EBP和ESP。其低16位对应先前CPU中的SBP和SP,对低16位数据的存取,不影 响高16位的数据。

寄存器EBP、ESP、BP和SP称为指针寄存器(Pointer Register),主要用于存放堆栈内存储单元的偏移量,
用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。

指针寄存器不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。

它们主要用于访问堆栈内的存储单元,并且规定:

BP为基指针(Base Pointer)寄存器,用它可直接存取堆栈中的数据; SP为堆栈指针(Stack
Pointer)寄存器,用它只可访问栈顶。

4、段寄存器

段寄存器是根据内存分段的管理模式而设置的。内存单元的物理地址由段寄存器的值和一个偏移量组合而成
的,这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址。

CPU内部的段寄存器:

CS——代码段寄存器(Code Segment Register),其值为代码段的段值; DS——数据段寄存器(Data Segment
Register),其值为数据段的段值; ES——附加段寄存器(Extra Segment Register),其值为附加数据段的段值;
SS——堆栈段寄存器(Stack Segment Register),其值为堆栈段的段值; FS——附加段寄存器(Extra Segment
Register),其值为附加数据段的段值; GS——附加段寄存器(Extra Segment Register),其值为附加数据段的段值。

在16位CPU系统中,它只有4个段寄存器,所以,程序在任何时刻至多有4个正在使用的段可直接访问;在32位
微机系统中,它有6个段寄存器,所以,在此环境下开发的程序最多可同时访问6个段。

32位CPU有两个不同的工作方式:实方式和保护方式。在每种方式下,段寄存器的作用是不同的。有关规定简 单描述如下:

实方式: 前4个段寄存器CS、DS、ES和SS与先前CPU中的所对应的段寄存器的含义完全一致,内存单元的逻辑
地址仍为“段值:偏移量”的形式。为访问某内存段内的数据,必须使用该段寄存器和存储单元的偏移量。 保护方式:
在此方式下,情况要复杂得多,装入段寄存器的不再是段值,而是称为“选择子”(Selector)的某个值。。

5、指令指针寄存器

32位CPU把指令指针扩展到32位,并记作EIP,EIP的低16位与先前CPU中的IP作用相同。

指令指针EIP、IP(Instruction Pointer)是存放下次将要执行的指令在代码段的偏移量。在具有预取指令功
能的系统中,下次要执行的指令通常已被预取到指令队列中,除非发生转移情况。所以,在理解它们的功能 时,不考虑存在指令队列的情况。

在实方式下,由于每个段的最大范围为64K,所以,EIP中的高16位肯定都为0,此时,相当于只用其低16位 的IP来反映程序中指令的执行次序。

6、标志寄存器

一、运算结果标志位 1、进位标志CF(Carry Flag)
进位标志CF主要用来反映运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位,那么,其值为1,否则其值为0。

使用该标志位的情况有:多字(字节)数的加减运算,无符号数的大小比较运算,移位操作,字(字节)之间移位,专门改变CF值的指令等。

2、奇偶标志PF(Parity Flag)
奇偶标志PF用于反映运算结果中“1”的个数的奇偶性。如果“1”的个数为偶数,则PF的值为1,否则其值为0。

利用PF可进行奇偶校验检查,或产生奇偶校验位。在数据传送过程中,为了提供传送的可靠性,如果采用奇偶校验的方法,就可使用该标志位。

3、辅助进位标志AF(Auxiliary Carry Flag) 在发生下列情况时,辅助进位标志AF的值被置为1,否则其值为0:

(1)、在字操作时,发生低字节向高字节进位或借位时; (2)、在字节操作时,发生低4位向高4位进位或借位时。

对以上6个运算结果标志位,在一般编程情况下,标志位CF、ZF、SF和OF的使用频率较高,而标志位PF和AF的使用频率较低。

4、零标志ZF(Zero Flag)
零标志ZF用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。

5、符号标志SF(Sign Flag)
符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。在微机系统中,有符号数采用补码表示法,所以,SF也就反映运算结果的正负号。运算结果为正数时,SF的值为0,否则其值为1。

6、溢出标志OF(Overflow Flag)
溢出标志OF用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF的值被置为1,否则,OF的值被清为0。

“溢出”和“进位”是两个不同含义的概念,不要混淆。如果不太清楚的话,请查阅《计算机组成原理》课程中的有关章节。

二、状态控制标志位 状态控制标志位是用来控制CPU操作的,它们要通过专门的指令才能使之发生改变。

1、追踪标志TF(Trap Flag)
当追踪标志TF被置为1时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求。这种方式主要用于程序的调试。

指令系统中没有专门的指令来改变标志位TF的值,但程序员可用其它办法来改变其值。

2、中断允许标志IF(Interrupt-enable Flag)
中断允许标志IF是用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。具体规定如下:

(1)、当IF=1时,CPU可以响应CPU外部的可屏蔽中断发出的中断请求;

(2)、当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。

CPU的指令系统中也有专门的指令来改变标志位IF的值。

3、方向标志DF(Direction Flag)
方向标志DF用来决定在串操作指令执行时有关指针寄存器发生调整的方向。具体规定在第5.2.11节——字符串操作指令——中给出。在微机的指令系统中,还提供了专门的指令来改变标志位DF的值。

三、32位标志寄存器增加的标志位 1、I/O特权标志IOPL(I/O Privilege Level)
I/O特权标志用两位二进制位来表示,也称为I/O特权级字段。该字段指定了要求执行I/O指令的特权级。如果当前的特权级别在数值上小于等于IOPL的值,那么,该I/O指令可执行,否则将发生一个保护异常。

2、嵌套任务标志NT(Nested Task) 嵌套任务标志NT用来控制中断返回指令IRET的执行。具体规定如下:

(1)、当NT=0,用堆栈中保存的值恢复EFLAGS、CS和EIP,执行常规的中断返回操作;

(2)、当NT=1,通过任务转换实现中断返回。

3、重启动标志RF(Restart Flag)
重启动标志RF用来控制是否接受调试故障。规定:RF=0时,表示“接受”调试故障,否则拒绝之。在成功执行完一条指令后,处理机把RF置为0,当接受到一个非调试故障时,处理机就把它置为1。

4、虚拟8086方式标志VM(Virtual 8086 Mode)
如果该标志的值为1,则表示处理机处于虚拟的8086方式下的工作状态,否则,处理机处于一般保护方式下的工作状态。

Referenced from:https://blog.csdn.net/hgd_dingjun/article/details/2809958

两个程序映射同一个文件到自己的地址空间, 进程A先运行, 每隔两秒读取映射区域, 看是否发生变化。进程B后运行, 它修改映射区域, 然后退出, 此时进程A能够观察到存储映射区的变化。

#include <sys/mman.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <stdio.h>    
#include <stdlib.h>    
#include <unistd.h>    
#include <error.h>    
    
#define BUF_SIZE 100    
    
int main(int argc, char **argv)    
{    
    int fd, nread, i;    
    struct stat sb;    
    char *mapped, buf[BUF_SIZE];    
    
    for (i = 0; i < BUF_SIZE; i++) {    
        buf[i] = '#';    
    }    
    
    /* 打开文件 */    
    if ((fd = open(argv[1], O_RDWR)) < 0) {    
        perror("open");    
    }    
    
    /* 获取文件的属性 */    
    if ((fstat(fd, &sb)) == -1) {    
        perror("fstat");    
    }    
    
    /* 将文件映射至进程的地址空间 */    
    if ((mapped = (char *)mmap(NULL, sb.st_size, PROT_READ |     
                    PROT_WRITE, MAP_SHARED, fd, 0)) == (void *)-1) {    
        perror("mmap");    
    }    
    
    /* 文件已在内存, 关闭文件也可以操纵内存 */    
    close(fd);    
        
    /* 每隔两秒查看存储映射区是否被修改 */    
    while (1) {    
        printf("%s\n", mapped);    
        sleep(2);    
    }    
    
    return 0;    
}    

进程B的代码:

#include <sys/mman.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <stdio.h>    
#include <stdlib.h>    
#include <unistd.h>    
#include <error.h>    
    
#define BUF_SIZE 100    
    
int main(int argc, char **argv)    
{    
    int fd, nread, i;    
    struct stat sb;    
    char *mapped, buf[BUF_SIZE];    
    
    for (i = 0; i < BUF_SIZE; i++) {    
        buf[i] = '#';    
    }    
    
    /* 打开文件 */    
    if ((fd = open(argv[1], O_RDWR)) < 0) {    
        perror("open");    
    }    
    
    /* 获取文件的属性 */    
    if ((fstat(fd, &sb)) == -1) {    
        perror("fstat");    
    }    
    
    /* 私有文件映射将无法修改文件 */    
    if ((mapped = (char *)mmap(NULL, sb.st_size, PROT_READ |     
                    PROT_WRITE, MAP_PRIVATE, fd, 0)) == (void *)-1) {    
        perror("mmap");    
    }    
    
    /* 映射完后, 关闭文件也可以操纵内存 */    
    close(fd);    
    
    /* 修改一个字符 */    
    mapped[20] = '9';    
     
    return 0;    
}    

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
  
#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \
  __LINE__, __FILE__, errno, strerror(errno)); exit(1); } while(0)
 
#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)

int main(int argc, char **argv) {
    int fd;
    void *map_base, *virt_addr; 
    unsigned long read_result, writeval;
    off_t target;
    int access_type = 'w';
    
    if(argc < 2) {
        fprintf(stderr, "\nUsage:\t%s { address } [ type [ data ] ]\n"
            "\taddress : memory address to act upon\n"
            "\ttype    : access operation type : [b]yte, [h]alfword, [w]ord\n"
            "\tdata    : data to be written\n\n",
            argv[0]);
        exit(1);
    }
    target = strtoul(argv[1], 0, 0);

    if(argc > 2)
        access_type = tolower(argv[2][0]);


    if((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1) FATAL;
    printf("/dev/mem opened.\n"); 
    fflush(stdout);
    
    /* Map one page */
    map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, target & ~MAP_MASK);
    if(map_base == (void *) -1) FATAL;
    printf("Memory mapped at address %p.\n", map_base); 
    fflush(stdout);
    
    virt_addr = map_base + (target & MAP_MASK);
    switch(access_type) {
        case 'b':
            read_result = *((unsigned char *) virt_addr);
            break;
        case 'h':
            read_result = *((unsigned short *) virt_addr);
            break;
        case 'w':
            read_result = *((unsigned long *) virt_addr);
            break;
        default:
            fprintf(stderr, "Illegal data type '%c'.\n", access_type);
            exit(2);
    }
    printf("Value at address 0x%X (%p): 0x%X\n", target, virt_addr, read_result); 
    fflush(stdout);

    if(argc > 3) {
        writeval = strtoul(argv[3], 0, 0);
        switch(access_type) {
            case 'b':
                *((unsigned char *) virt_addr) = writeval;
                read_result = *((unsigned char *) virt_addr);
                break;
            case 'h':
                *((unsigned short *) virt_addr) = writeval;
                read_result = *((unsigned short *) virt_addr);
                break;
            case 'w':
                *((unsigned long *) virt_addr) = writeval;
                read_result = *((unsigned long *) virt_addr);
                break;
        }
        printf("Written 0x%X; readback 0x%X\n", writeval, read_result); 
        fflush(stdout);
    }
    
    if(munmap(map_base, MAP_SIZE) == -1) FATAL;
    close(fd);
    return 0;
}

digest认证详解

第一步,客户端发起资源请求
第二步,服务端返回WWW-Authentication

Digest realm="whitelist_authority", nonce="CuR5pLqxBQA=95e23db2988c7e3c1a937dba4fdaf6d8e71b8052", algorithm=MD5, domain="/fyx/ http://192.168.21.178/fyx/", qop="auth"
nonce是服务器生成的一段 random 字符串,realm指定了认证的作用域,algorithm指定了hash算法,qop代表保护质量参数,一般是 auth 或 auth-int,这会影响摘要的算法。

第三步,客户端发送认证信息,认证信息放在Authorization字段中,如下

Authorization: Digest username="admin", realm="whitelist_authority", nonce="uTQuyp+2BQA=b8605449f8ef432d6bbe1816ca22b3902a689551", uri="/fyx/", algorithm=MD5, response="c7c61a41f1797a4d8c767b35da71f814", qop=auth, nc=00000002, cnonce="586ee4f738ae507e"
response由以下三个步骤生成:
1.对用户名、认证域(realm)以及密码的合并值计算 MD5 哈希值,结果称为 HA1。
2.对HTTP方法以及URI的摘要的合并值计算 MD5 哈希值,例如,"GET" 和 "/dir/index.html",结果称为 HA2。
3.对 HA1、服务器密码随机数(nonce)、请求计数(nc)、客户端密码随机数(cnonce)、保护质量(qop)以及 HA2 的合并值计算 MD5 哈希值。结果即为客户端提供的 response 值。

HA1 = MD5(admin:whitelist_authority:xfnbs) = fe427accdddeffc487b33c0f174c4d60
HA2 = MD5(GET:/fyx/) = 60042ee7f41275159f74e2be620ad646
response = MD5(HA1:uTQuyp+2BQA=b8605449f8ef432d6bbe1816ca22b3902a689551:00000002:586ee4f738ae507e:auth:HA2) = c7c61a41f1797a4d8c767b35da71f814
之前我计算出来的response总是和报文中的response不一致,后来发现是生成的MD5大小写没有区分。。。

避免重放攻击

服务器只接收nc大于前一次的response,如果简单的将上一次验证成功的响应包重放,那么服务器会丢弃这一次的响应因为nc值不大于上一次响应。

如果修改nc值,那么服务器校验response时不会认证通过。

http basic认证

HTTP 提供一个用于权限控制和认证的通用框架。最常用的HTTP认证方案是HTTP Basic authentication。
RFC 7235 定义了一个 HTTP 身份验证框架,服务器可以用来针对客户端的请求发送 challenge (质询信息),客户端则可以用来提供身份验证凭证。质询与应答的工作流程如下:服务器端向客户端返回 401(Unauthorized,未被授权的) 状态码,并在 WWW-Authenticate 首部提供如何进行验证的信息,其中至少包含有一种质询方式。之后有意向证明自己身份的客户端可以在新的请求中添加 Authorization 首部字段进行验证,字段值为身份验证凭证信息。通常客户端会弹出一个密码框让用户填写,然后发送包含有恰当的 Authorization 首部的请求。

http digest认证

尽管 basic 认证采用了 base64 编码,但是 base64 算法是可逆的,所以基本验证方案并不安全。基本验证方案应与 HTTPS / TLS 协议搭配使用。假如没有这些安全方面的增强,那么基本验证方案不应该被来用保护敏感或者极具价值的信息。
digest 采用 hash 算法将认证信息转换为不可逆的字符串进行传输,可以避免明文传输密码。