kernel/test.c
static err_t test_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err)
{
printk("test_recv:%lx,buf:%lxn",pcb,p);
if (!pcb||!p) return 0;
dump_mem(p->payload, p->len);
char *resp = "HTTP/1.1 200 OKnContent-Length:5nContent_Type:text/htmlnnhello";
tcp_write(pcb, resp,strlen(resp),TCP_WRITE_FLAG_COPY);
tcp_output(pcb);
return 0;
}
curl "http://192.168.122.98:8080/aaaa"
hello
可以正常显示。在宿主机器上开启nginx代理。
server
{
listen 8080;
location /
{
proxy_pass http://192.168.122.98:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}
在浏览器打开:
lwip源码下面有apps/httpd,复制到module/net/httpd下面。
初始化那里添加httpd_init。
curl "http://192.168.122.98:80/index.html"
把host nginx代理端口改成80
server
{
listen 8080;
location /index.html
{
root /dev/shm;
}
location /
{
proxy_pass http://192.168.122.98:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}
打开是内置的index.html。
发送包那里有个bug,lwip传过来pbuf可能是个链表,即一个包的内容分多块,不连续。
static err_t lwip_tx_func(struct netif *netif, struct pbuf *p)
{
struct pbuf *q;
//err_t ret;
struct iovec vecs[MAX_VECS];
int nr_vec = 0;
for (q = p; q != NULL; q = q->next) {
//printk("q->payload:%016lx,size:%xn",q->payload,q->len);
ulong pageend = ((ulong)q->payload+q->len)&~0xfff;
if (((ulong)q->payload & ~0xfff) == pageend ) {
//if ((ret = virtio_net_send_packet(q->payload, q->len))!=0) return ret;
vecs[nr_vec].iov_base = q->payload;
vecs[nr_vec].iov_len = q->len;
nr_vec++;
ASSERT(nr_vec<MAX_VECS);
} else {
printk("try to send not same pagen");
//ASSERT(0);
vecs[nr_vec].iov_base = q->payload;
vecs[nr_vec].iov_len = pageend - (ulong)q->payload;
nr_vec++;
vecs[nr_vec].iov_base = (void *)pageend;
vecs[nr_vec].iov_len = ((ulong)q->payload+q->len)&0xfff;
nr_vec++;
}
//dump_mem(q->payload, q->len);
}
return (nr_vec && virtio_net_send_packets(vecs, nr_vec) ) || ERR_OK;
}
用ab测试:(去掉调试之后,main循环去掉nsleep)
ab -n 20 "http://192.168.122.98:80/index.html"
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, Welcome to The Apache Software Foundation!
Benchmarking 192.168.122.98 (be patient).....done
Server Software: lwIP/2.0.3d
Server Hostname: 192.168.122.98
Server Port: 80
Document Path: /index.html
Document Length: 1751 bytes
Concurrency Level: 1
Time taken for tests: 0.004 seconds
Complete requests: 20
Failed requests: 0
Total transferred: 37620 bytes
HTML transferred: 35020 bytes
Requests per second: 4671.81 [#/sec] (mean)
Time per request: 0.214 [ms] (mean)
Time per request: 0.214 [ms] (mean, across all concurrent requests)
Transfer rate: 8581.71 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 0 0 0.0 0 0
Waiting: 0 0 0.0 0 0
Total: 0 0 0.1 0 0
Percentage of the requests served within a certain time (ms)
50% 0
66% 0
75% 0
80% 0
90% 0
95% 0
98% 0
99% 0
100% 0 (longest request)
-n不能太大,太大会丢链接,原因未知。
对比下主机端的nginx:
ab -n 20 "http://192.168.10.2:8080/index.html"
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, Welcome to The Apache Software Foundation!
Benchmarking 192.168.10.2 (be patient).....done
Server Software: openresty/1.7.10.2
Server Hostname: 192.168.10.2
Server Port: 8080
Document Path: /index.html
Document Length: 1751 bytes
Concurrency Level: 1
Time taken for tests: 0.003 seconds
Complete requests: 20
Failed requests: 0
Total transferred: 40280 bytes
HTML transferred: 35020 bytes
Requests per second: 7843.14 [#/sec] (mean)
Time per request: 0.128 [ms] (mean)
Time per request: 0.128 [ms] (mean, across all concurrent requests)
Transfer rate: 15425.86 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 0 0 0.0 0 0
Waiting: 0 0 0.0 0 0
Total: 0 0 0.0 0 0
Percentage of the requests served within a certain time (ms)
50% 0
66% 0
75% 0
80% 0
90% 0
95% 0
98% 0
99% 0
100% 0 (longest request)
Requests per second: 7843.14 [#/sec] (mean)对比:
Requests per second: 4671.81 [#/sec] (mean)
性能还可以,毕竟目前只用了一个核心,只有BP在工作,其他核心都在打酱油。
主机端nginx用的是root /dev/shm,用的是内存。
lwip用的也是内存。
location /index.html
{
root /dev/shm;
}
接下来要做的就是嵌入lua进行web处理。
主要参考openresty的ngx_lua模块进行移植。
对于一个http链接,启动一个lua协程进行处理。一个核心一个lua虚拟机。每个核心有多个lua线程(协程)。每个http链接对应lua协程有一个链接相关的数据(ctx)。
每个核心有个核心全局的lua虚拟机vmL
DEFINE_PER_CPU(lua_State *,vmL);
每个链接有个lua的修改数据,叫http_lua_ctx_t。可以通过链接获取ctx。
ngx_lua还有一个协程co_ctx,没有暂不支持用户创建协程。即ngx_lua有多个lua协程对应一个链接。而我们只有一个lua协程对应一个链接。
module/httpd/httpd_request.c:
http_request_t * http_create_request(struct http_state *hs)
{
http_request_t * p = yaos_malloc(sizeof(http_request_t));
if (!p) return p;
p->hs = hs;
for (int i=0; i< MAX_HTTP_MODULE; i++) p->ctx[i] = NULL;
return p;
}
int httpd_lua_handle(struct http_state *hs, http_request_t *r)
{
lua_State * L = getL();
printk("r:%016lx, L:%016lxn", r, L);
int http_lua_content_by_file(const char *name, lua_State *L, http_request_t *r);
return http_lua_content_by_file("httpd", L, r);
}
httpd_request实现httpd跟lua的交互。
httpd.c里面:
if (hs->r == NULL) hs->r = http_create_request(hs);
return httpd_lua_handle(hs, hs->r);
为每个链接创建http_request_t。
http_lua_content_by_file调用lua/httpd.lua进行处理。
module/lua/http/content.c:
int http_lua_content_by_file(const char *name, lua_State *L, http_request_t *r)
{
int co_ref;
lua_State *co;
http_lua_ctx_t *ctx = http_get_module_ctx(r, HTTP_LUA_MODULE);
if (ctx == NULL) {
ctx = http_lua_create_ctx(r);
if (ctx == NULL) {
return ENOMEM;
}
} else {
http_lua_reset_ctx(r, L, ctx);
}
if (load_lua_file(L, (char *)name) != 0) return ESRCH;
/* {{{ new coroutine to handle request */
co = http_lua_new_thread(L, &co_ref);
printk("co:%lxn",co);
if ( co == NULL ) {
return ENOMEM;
}
lua_xmove(L, co, 1);
http_lua_get_globals_table(co);
lua_setfenv(co, -2);
http_lua_set_req(co, r);
ctx->cur_co_ctx = &ctx->entry_co_ctx;
ctx->cur_co_ctx->co = co;
ctx->cur_co_ctx->co_ref = co_ref;
int rc = http_lua_run_thread(L, r, ctx, 0);
return rc;
}
lua_xmove实现load_lua_file加载的lua/httpd.lua复制到协程co上面。
这样httpd.c有数据收到之后,就会调用lua/httpd.lua进行处理。lua/httpd.lua就要做个状态机,来处理请求。
local resps = {ngx.get_req()}
yaos.show_dump(resps);
local ctx = ngx.ctx
local getn = table.getn
local cut = string.sub
local split = yaos.split
local find = string.find
local lower = string.lower
local inserttable = table.insert
--yaos.show_dump(ctx);
if not ctx.status then
ctx.status = 0
ctx.req = {headers = {},bodys = {}, content_len = 0, body_len = 0, method = "GET"}
end
local req = ctx.req
if getn(resps)<1 then
return 1
end
--yaos.show_dump(ctx);
--yaos.show_dump(split(resps[1] or "testrntest2","rn"));
local function parse_line(linestr)
if linestr =="" then
if ctx.status == 1 then
ctx.status = 2
end
elseif ctx.status == 1 then
local pos = find(linestr,": ")
if pos then
local headername = lower(cut(linestr,1,pos-1))
req.headers[headername] = cut(linestr,pos+2)
if headername == "content-length" then
req.content_len = req.headers[headername] - 0
end
end
elseif ctx.status == 2 then
inserttable(req.bodys, linestr);
req.body_len = req.body_len + #linestr;
if req.body_len >= req.content_len then
ctx.status = 3
end
end
end
for _,resp in ipairs(resps) do
if ctx.status == 0 then
local arr = split(resp, "rn");
local firstline = arr[1] or "";
local firstarr = split(firstline,"%s")
req.method = firstarr[1]
req.uri = firstarr[2]
req.ver = firstarr[3]
ctx.status = 1
local idx = 2
while idx <= getn(arr) do
local linestr = arr[idx]
parse_line(linestr)
idx = idx +1
end
elseif ctx.status == 1 then
local arr = split(resp, "rn");
local idx = 1
while idx <= getn(arr) do
local linestr = arr[idx]
parse_line(linestr)
idx = idx +1
end
elseif ctx.status == 2 then
parse_line(resp)
end
end
if ctx.status == 2 and req.content_len == 0 then
ctx.status = 3
end
if ctx.status == 3 then
yaos.show_dump(ctx);
else
yaos.say(ctx.status..req.body_len)
end
ngx.get_req实现从httpd里面的输入数据复制到lua。由于pbuf可能有多个,因此ngx.get_req返回值不固定。local resps = {ngx.get_req()},这样就可以把1个或者多个返回值放到resps数组里面。
static int ngx_lua_get_req(lua_State *L)
{
http_request_t *r = http_lua_get_req(L);
if (r == NULL) {
return luaL_error(L, "request object not found");
}
struct pbuf * p = get_req_buf(r->hs);
if (!p) {
lua_pushnil(L);
return 1;
}
if (p->next == NULL) {
lua_pushlstring(L, (char *) p->payload, p->len);
return 1;
}
int nr = 0;
while (p) {
lua_pushlstring(L, (char *) p->payload, p->len);
nr++;
p = p->next;
}
return nr;
}
运行的时候,根据ngx.ctx.status,进行处理。
status=0表示初始值,处理http请求第一行,即request method,url,http版本。
status=1表示处理了第一行,开始处理头部
status=2表示头部处理结束,记录content_len
status=3表示body处理结束,body_len==content_len
请求数据可能分多次到达,尤其是post一个大文件的时候,把每次的body数据加入到req.bodys数组里面。
发送一个json数据进行测试:
curl -X GET -d '{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }' 192.168.122.98:80/test?dd=2
运行结果
ctx->ctx_ref:fffffffe,fffffffe _ctx_arr
{
"status" = 3,
"req" = {
"ver" = "HTTP/1.1",
"method" = "GET",
"body_len" = 57,
"content_len" = 57,
"uri" = "/test?dd=2",
"bodys" = {
[1] = "{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }",
},
"headers" = {
"host" = "192.168.122.98",
"content-type" = "application/x-www-form-urlencoded",
"content-length" = "57",
"accept" = "*/*",
"user-agent" = "curl/7.47.0",
},
},
}
现在来测试发送一个文件:
curl -d@README 192.168.122.98:80/te
ctx->ctx_ref:fffffffe,fffffffe _ctx_arr
{
"status" = 3,
"req" = {
"ver" = "HTTP/1.1",
"method" = "POST",
"body_len" = 437,
"content_len" = 437,
"uri" = "/te",
"bodys" = {
[1] = "README for LuaJIT 2.1.0-beta3-----------------------------LuaJIT is a Just-In-Time (JIT) compiler for the Lua programming language.Project Homepage: http://luajit.org/LuaJIT is Copyright (C) 2005-2020 Mike Pall.LuaJIT is free software, released under the MIT license.See full Copyright Notice in the COPYRIGHT file or in luajit.h.Documentation for LuaJIT is available in HTML FORMAT - ZAKŁAD PRODUKCJI MEBLI",
[2] = "ease point your favorite browser to: doc/luajit.html",
},
"headers" = {
"host" = "192.168.122.98",
"content-type" = "application/x-www-form-urlencoded",
"content-length" = "437",
"accept" = "*/*",
"user-agent" = "curl/7.47.0",
},
},
}
现在只有处理请求,还没有输出。
现在来添加一个输出
lua/web/index.lua
local html= [=[
<html>
<head><title>FROM LUA:lwIP - A Lightweight TCP/IP Stack</title></head>
<body bgcolor="white" text="black">
<table width="100%">
<tr valign="top"><td width="80">
<a href="RISE"><img src="/img/sics.gif"
border="0" alt="SICS logo" title="SICS logo"></a>
</td><td width="500">
<h1>lwIP - A Lightweight TCP/IP Stack</h1>
<p>
The web page you are watching was served by a simple web
server running on top of the lightweight TCP/IP stack <a
href="RISE">lwIP</a>.
</p>
<p>
lwIP is an open source implementation of the TCP/IP
protocol suite that was originally written by <a
href="RISE">Adam Dunkels
of the Swedish Institute of Computer Science</a> but now is
being actively developed by a team of developers
distributed world-wide. Since it's release, lwIP has
spurred a lot of interest and has been ported to several
platforms and operating systems. lwIP can be used either
with or without an underlying OS.
</p>
<p>
More information about lwIP can be found at the lwIP
homepage at <a
href="lwIP - A Lightweight TCP/IP stack - Summary">lwIP - A Lightweight TCP/IP stack - Summary</a>
or at the lwIP wiki at <a
href="lwIP Wiki">lwIP Wiki</a>.
</p>
</td><td>
</td></tr>
</table>
</body>
</html>
]=]
local headers = ""HTTP/1.1 200 OKrnContent-Type: text/html; charset=UTF-8;rnContent-Length: "..#html.."rnrn"
ngx.say(headers)
ngx.say(html)
module/lua/lua_files.c
添加该文件:
DEFINE_LUA_FILE(dump);
DEFINE_LUA_FILE(split);
DEFINE_LUA_FILE(test2);
DEFINE_LUA_FILE(test);
DEFINE_LUA_FILE(test3);
DEFINE_LUA_FILE(httpd);
DEFINE_LUA_FILE(web_index);
httpd.c添加输出函数:
err_t
http_send_content(struct http_state *hs, char *p, size_t len)
{
hs->file = p;
hs->left = len;
hs->handle = &hs->file_handle;
hs->file_handle.len = len;
hs->file_handle.index = len;
hs->file_handle.data = p;
http_send_data_nonssi(hs->pcb, hs);
return ERR_OK;
}
module/lua/http/content.c添加ngx.say函数
static int ngx_lua_say(lua_State *L)
{
http_request_t *r = http_lua_get_req(L);
if (r == NULL) {
return luaL_error(L, "request object not found");
}
int argc = lua_gettop(L);
if (argc != 1) return luaL_error(L, "expecting one argument");
uchar *p;
size_t len;
p = (uchar *) luaL_checklstring(L, 1, &len);
err_t http_send_content(struct http_state *hs, char *p, size_t len);
err_t err = http_send_content(r->hs, p, len);
printk("http_send_content:%dn",err);
return 0;
}
lua/httpd.lua添加输出
if ctx.status == 3 then
yaos.do_file("web_index");
yaos.show_dump(ctx);
else
yaos.say(ctx.status..req.body_len)
end
ctx.status为3,即request body读取完成之后
curl -d@t.jar 192.168.122.98:80/te
<html>
<head><title>FROM LUA:lwIP - A Lightweight TCP/IP Stack</title></head>
<body bgcolor="white" text="black">
<table width="100%">
<tr valign="top"><td width="80">
<a href="RISE"><img src="/img/sics.gif"
border="0" alt="SICS logo" title="SICS logo"></a>
</td><td width="500">
<h1>lwIP - A Lightweight TCP/IP Stack</h1>
<p>
The web page you are watching was served by a simple web
server running on top of the lightweight TCP/IP stack <a
href="RISE">lwIP</a>.
</p>
<p>
lwIP is an open source implementation of the TCP/IP
protocol suite that was originally written by <a
href="RISE">Adam Dunkels
of the Swedish Institute of Computer Science</a> but now is
being actively developed by a team of developers
distributed world-wide. Since it's release, lwIP has
spurred a lot of interest and has been ported to several
platforms and operating systems. lwIP can be used either
with or without an underlying OS.
</p>
<p>
More information about lwIP can be found at the lwIP
homepage at <a
href="lwIP - A Lightweight TCP/IP stack - Summary">lwIP - A Lightweight TCP/IP stack - Summary</a>
or at the lwIP wiki at <a
href="lwIP Wiki">lwIP Wiki</a>.
</p>
</td><td>
</td></tr>
</table>
下面实践动态内容输出:
修改httpd.lua,添加querystring
if ctx.status == 0 then
local arr = split(resp, "rn");
local firstline = arr[1] or "";
local firstarr = split(firstline,"%s")
req.method = firstarr[1]
if firstarr[2] then
local querypos = find(firstarr[2],'?');
if querypos then
req.uri = cut(firstarr[2], 1, querypos - 1);
req.query_string = cut(firstarr[2], querypos+1)
else
req.uri = firstarr[2]
end
end
req.ver = firstarr[3]
ctx.status = 1
local idx = 2
while idx <= getn(arr) do
local linestr = arr[idx]
parse_line(linestr)
idx = idx +1
end
...
if ctx.status == 3 then
local route = {test="web_test",json="web_json"}
yaos.do_file(route[cut(req.uri,2)] or "web_index");
yaos.show_dump(ctx);
else
yaos.say(ctx.status..req.body_len)
end
添加2个路由,test,json,其他转到web_index
curl -X GET -d '{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }' 192.168.122.98:80/test?dd=2
query body:
{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }
test把body部分回显。
test.lua:
local ctx = ngx.ctx
local concat = table.concat
local req = ctx.req
local html = "query body:n"..concat(req and req.body_arr or {})
local headers = "HTTP/1.1 200 OKrnContent-Type: text/html; charset=UTF-8;rnContent-Length: "..#html.."rnrn"
ngx.say(headers)
ngx.say(html)
curl -X GET -d '{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }' 192.168.122.98:80/json?dd=2
{"query_string":"dd=2","method":"GET","body_len":57,"content_len":57,
"body_arr":["{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }"],
"ver":"HTTP/1.1","uri":"/json","headers":{"host":"192.168.122.98","content-type":
"application/x-www-form-urlencoded",
"content-length":"57","accept":"*/*","user-agent":"curl/7.47.0"}}
json.lua:
local cjson=require("cjson")
local ctx = ngx.ctx
local concat = table.concat
local req = ctx.req
local cut = string.sub
local body = cjson.encode(req)
local html = body
local headers = "HTTP/1.1 200 OKrnContent-Type: application/json; charset=UTF-8;rnContent-Length: "..#html.."rnrn"
ngx.say(headers)
ngx.say(html)
json.lua把请求数据以json格式输出
用了cjson包,移植的openresty下面的lua-cjson-2.1.0.7
同时添加了lua系统的package支持,并且添加了自己的loader。
int luaopen_cjson(lua_State *l);
static const luaL_Reg lua_module[] = {
{ "cjson", luaopen_cjson },
{ NULL, NULL }
};
static int lua_package_loader_yaos(lua_State *L)
{
const char *name = luaL_checkstring(L, 1);
printk("package_loader_yaos:%sn",name);
const luaL_Reg *lib;
for (lib = lua_module; lib->func; lib++) {
if (strcmp(lib->name, name)==0) {
lua_pushcfunction(L, lib->func);
return 1;
}
}
if( 0==load_lua_file(L, (char *)name)) return 1;
lua_pushnil(L);
return 1;
}
先查找lua_module数组,有的话就返回。这个是c语言写的包。
然后再试图load_lua_file,这个是lua文件。
运行本篇:
lwip需要重新编译。
git clone https://github.com/saneee/x86_64_kernel.git
cd lwip-2.1.2
make clean
make
cd ../luajit2
make clean
make
cd ../lua-cjson-2.1.0.7
make
cd ../0026
make qemu
此篇只进行功能性实践,还有很多东西需要完善,也存在bug。
目前用的是0拷贝发送,但是有可能数据还没发送成功,内存就被释放了。需要添加清理函数,virtio-net驱动发送成功了,再释放相应内存。如果遇到没反应重新启动多试几次。
另外lua协程也没清理,只有创建,没有删除。