在GNU Hurd中感受Mach微内核的进程通信(IPC)
什么是GNU Hurd
具体的时间线已经在官方维基页面得到详细描述[0],笔者在此就简单叙述一下。在1983年Richard Stallman开启了GNU项目,目的是创建一个自由的操作系统[1]。在接下来的开发中各种软件都已经到位了,其中包括编译器GNU Compiler Collection,编辑器Emacs,C库GNU C Library,调试器GNU Debugger,完整的列表可以查看这个链接。我相信绝大部分的基于Linux的distribution都或多或少的有安装这些软件,虽然可能注意到的人并不多。而其中最重要的问题就是这些应用应该在什么上面运行,答案就是GNU Hurd。正如官方维基说的那样[0],在1983宣布项目开始后,GNU准备使用的内核在很长时间内都没有确定下来,之后在1991年才有了个比较详细的计划:决定使用CMU开发的Mach微内核并且在此之上编写Hurd系统[0]。
一点题外话:在当时其实被看好会做到如今Linux系统规模的主要有两个系统,一个是FreeBSD而另一个就是GNU Hurd。但FreeBSD有一段时间陷入了法律纠纷而GNU Hurd则是开发方面比较缓慢。上一句的信息来源笔者没记错的话是来自《UNIX 编程艺术》。Linus也表示过“如果在1991春天GNU内核已经能在生产中使用了,他是不会启动Linux项目的:但事实是没有。”[3]
微内核(microkernel)
微内核简单来说就是把大部分功能放在用户空间(userspace)。拿Linux举例子就是这个链接指向RTC(Real-Time Clock)的一个驱动代码,而对于GNU Hurd而言,Mach才是它的内核,而跟Linux的RTC驱动代码类似功能的代码[2]并不属于Mach,即并不在内核空间中而是在用户空间内。可以看出好处之一即是如果RTC驱动有Bug会导致崩溃,在Hurd中只有运行着那段RTC驱动代码的进程会崩溃,而Linux则会直接整个崩溃。这也是普遍认为的微内核的好处之一。微内核的好处因为笔者认为已经听得耳朵要起茧了,所以在这里就不多说太多了,接下来就说说坏处。笔者最常听到的是由于IPC的频繁使用而导致频繁发生上下文切换(context switch)而带来的性能下降问题。这个问题在这篇论文中有提及,虽然论文中使用的是L4这个第二代微内核而不是Mach这个第一代微内核成员来和单内核比较,不过L4在进程通信(IPC)方面做了十分强大的优化,所以笔者认为会比Mach更能体现单内核和微内核之间的性能差距。实验使用了基于L4的Linux即L4Linux与纯Linux进行性能比较,结论是微内核给应用带来的性能下降大概在5%到10%。
一点题外话:因为笔者对微内核的了解不深,但有找到这一篇很不错的文章,讲述了三代微内核的特点。推荐给感兴趣的读者。
多服务器(multi-servers)和进程通信(IPC)
正如Hurd主界面介绍的那样,Hurd是一系列在Mach上运行的servers(服务器),并且由这些servers来实现文件系统,网络协议,等等Unix内核或类似内核所拥有的功能。拿上一段提到的RTC驱动来举例子,这个RTC驱动就是像服务器一样由进程在运行,有message(信息)发送到RTC驱动服务器进程后,RTC服务器就会根据信息要求来读取或写入RTC硬件,如果是读取就会把读到的东西再发回去。而传递信息(即Inter-Process Communication)的重任则是由微内核Mach来完成。
Mach Interface Generator (Mach接口生成器)
Mach有着自己的接口来让别的程序使用IPC功能,因为Mach的接口需要尽量考虑到大部分情况,因此其实直接使用是相对复杂的。为了缓解这个问题,一般开发会使用Mach Interface Generator (Mach接口生成器)[5]。它能隐藏掉构造message(信息)的复杂。不过写这篇文章的起因是因为下面这个链接:
http://walfield.org/pub/people/neal/papers/hurd-misc/mach-ipc-without-mig.txt
这个练习的目标是在不使用MIG的前提下写一个利用Mach来完成一次信息发送再得到返回的程序,是用来给人加深对Mach IPC的印象的。
对于想自己捣鼓的读者,笔者就将对练习有用的链接放在这里,GNU Hurd官网的搜索栏十分有用,请好好利用:
https://darnassus.sceen.net/~hurd-web/
https://darnassus.sceen.net/~hurd-web/microkernel/mach/documentation/
http://www.cs.cmu.edu/afs/cs/project/mach/public/doc/osf/kernel_principles.ps
http://www.cs.cmu.edu/afs/cs/project/mach/public/doc/osf/kernel_interface.ps
http://www.cs.cmu.edu/afs/cs/project/mach/public/doc/osf/server_writer.ps
在虚拟机中运行GNU Hurd
GNU Hurd是可以在真实硬件上运行的,但因为设备驱动的不足所以目前GNU Hurd只能在几款比较老款的Thinkpad笔记本上运行[6]。只为体验或完成这份练习的话虚拟机是完全足够的了。
目前相对比较稳定的Distribution是Debian。64位系统还不是非常完善,所以我们先使用32位的。可以在这个链接中查看所有可用Images。我们能运行以下指令来获得32位的Debian GNU Hurd Image。
wget https://cdimage.debian.org/cdimage/ports/latest/hurd-i386/current/debian-hurd.img.gz
然后把文件解压:
gzip -d debian-hurd.img.gz
笔者使用的是qemu
,所以用这个指令来运行:
qemu-system-i386 -enable-kvm -m 2G -drive \
format=raw,cache=writeback,file=debian-hurd.img \
-nic user,hostfwd=tcp::2222-:22 -display curses -vga std
启动完成后会看到
Debian GNU/Hurd 12 debian tty3
login:
直接输入root
然后Enter
就能进入bash
了。使用passwd
给root
加个密码。用exit
退出去然后再用demo
登录,然后再用passwd
改个密码[7]。用vim
或者个人喜欢的编辑器打开/etc/ssh/ssh_config
把PasswordAuthentication yes
那行的#
去掉。接下来我们就可以直接再开一个新的terminal然后用ssh
来在GNU Hurd上操作了:
ssh -p 2222 demo@localhost
然后准备工作就都完成了,接下来就可以开始研究练习了。
Mach IPC without MIG
练习目标是要在一个进程中创建两个线程,一个是客户一个是伺服器。客户要给伺服器发送一个信息并且要收到一个伺服器的回应。而信息的传递要使用Mach微内核的接口。
两个线程
我们就先写出两个线程吧。
#include <threads.h>
#include <stdio.h>
int
server (void *arg)
{
printf ("I'm server\n");
}
int
client (void *arg)
{
printf ("I'm client\n");
}
int
main (void)
{
thrd_t server_thrd, client_thrd;
thrd_create (&server_thrd, server, NULL);
thrd_create (&client_thrd, client, NULL);
thrd_join (server_thrd, NULL);
thrd_join (client_thrd, NULL);
return 0;
}
编译时要用这条指令:
gcc main.c -lpthread -o main
Ports
我们所编译出来的的main
可执行文件在刚开始会由一个线程(thread)运行,在运行到两个thrd_create
时又会多生出两个线程,那么总共就会有三个线程运行我们的代码,当然运行的路线(routine)是不同的,一个是main()
,一个是server()
而剩下的那个则是client()
。正如Mach 3 Kernel Principles[9]中所描述的那样,这三个线程都是属于同一个task
的。每个task
会有一个port name space
,每个port name
代表一个port right
,port name
是在用户空间中看到的,而port right
则是由Mach管理,就像文件描述符(file descriptor)一样。每个port right
又代表了port
和right
。port
是一个单向沟通通道(unidirectional communication channel),right
则代表了task
能对跟right
一起的port
所进行的操作。right
的类型包括接收权限(receive right),发送权限(send right),单次发送权限(send-once right)等等[10]。简单来说就是如果想要用Mach沟通,那就先要有一个port
。我们可以使用mach_port_allocate
来获得一个port
。
git diff
:
diff --git a/main.c b/main.c
index f73724a..2eb9f38 100644
--- a/main.c
+++ b/main.c
@@ -1,5 +1,8 @@
#include <threads.h>
#include <stdio.h>
+#include <mach.h>
+
+static mach_port_t port;
int
server (void *arg)
@@ -17,6 +20,7 @@ int
main (void)
{
thrd_t server_thrd, client_thrd;
+ mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &port);
thrd_create (&server_thrd, server, NULL);
thrd_create (&client_thrd, client, NULL);
这样子我们就获得了一个在自己的task
中的port
,而我们对这个port
则拥有接收权利。读者可能会想问为什么要把client
和server
放在同一个task
中而不是分开两个放。正如练习中描述的那样[8],放在同一个task
中是为了简化实现,不然就会涉及到另一个关于central name server的课题了。
Message
练习描述中有提到信息的结构[8]:
Inline messages have the following general structure:
[ [mach_msg_header_t] [[mach_msg_type_t][data]] [[mach_msg_type_t][data]]... ]
^ ^ ^^ ^ ^
| | || | \- Second set of arguments
| | || \- First set of arguments
| \- Message header |\- Header for first set of arguments
\- Message \- Pay load
When data is marked out of line, the data section is detached.
我们打算让Client发送:"Alice\0"
给Server并且让Server回复"Hello Alice\0"
,我们可以创建一个这样的结构:
git diff
diff --git a/main.c b/main.c
index 2eb9f38..2307de1 100644
--- a/main.c
+++ b/main.c
@@ -2,6 +2,13 @@
#include <stdio.h>
#include <mach.h>
+struct message
+{
+ mach_msg_header_t msg_header;
+ mach_msg_type_t first_header;
+ char string[100];
+};
+
static mach_port_t port;
int
@@ -13,6 +20,7 @@ server (void *arg)
int
client (void *arg)
{
+ struct message m;
printf ("I'm client\n");
}
Client
在Mach kernel interface中能看到mach_msg
就将是我们用来发送和接收message
的接口函数了。头文件声明则在这里:
extern mach_msg_return_t
mach_msg
(mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify);
可见参数还是蛮多的,参数在Mach 3 Kernel Interface中有介绍,笔者在此斗胆翻译一下:
msg: A message buffer.
即该变量指向存放着信息消息头的区域。在我们目前的代码中其实就是&(m.msg_header)
。(笔者认为这里就很像网络协议中的消息头)
option: Message options are bit values, combined with bitwise-or. One or both of MACH_SEND_MSG and MACH_RCV_MSG should be used.
正如上面所言mach_msg
既能发送也能用来接收,可能会有读者好奇 MACH_SEND_MSG | MACH_RCV_MSG
代表了什么。它意味的发送了信息后还会期待一个信息返回回来,而它正是我们所要在Client里使用的了。
send_size: When sending a message, specifies the size of the message buffer. Otherwise zero should be supplied.
请注意,这里提到的message
并不是"Alice\0"
或Hello Alice\0
,而是在上一节所提到的message
。所以就是sizeof (struct message)
。
rcv_size: When receiving a message, specifies the size of the message buffer. Otherwise zero should be supplied.
因为我们也打算收到一个回应,所以我们要填的值就是sizeof (struct message)
。
rcv_name: When receiving a message, specifies the port or port set. Otherwise MACH_PORT_NULL should be supplied.
我们会从变量port
接收信息,所以就填它。
timeout 和 notify
我们在这次练习中不怎么在乎所以就填MACH_MSG_TIMEOUT_NONE
和MACH_PORT_NULL
。
所以我们在Client
中使用的mach_msg
就会如下所示:
mach_msg (&(m.msg_header), MACH_SEND_MSG | MACH_RCV_MSG, sizeof (struct message), sizeof (struct message), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
当然在这之前还要加上变量m
的一些初始化。
先是记录在Mach 3 Kernel Interface 327页的mach_msg_header
:
typedef struct mach_msg_header {
mach_msg_bits_t msgh_bits;
mach_msg_size_t msgh_size;
mach_port_t msgh_remote_port;
mach_port_t msgh_local_port;
mach_port_seqno_t msgh_seqno;
mach_msg_id_t msgh_id;
} mach_msg_header_t;
可见参数也不少。
msgh_bits
还记得我们在main
中分配的那个right
吗,那是个发送权限,而正如练习中提到的:
Mach ports: how to allocate send and receive rights (the former implicitly in mach_msg and the latter directly via mach_port_allocate).
我们将会在msgh_bits
中分配发送权限。我们会给msgh_bits
填MACH_MSGH_BITS (MACH_MSG_TYPE_MAKE_SEND, 0)
。这个操作意味着放一个之后变量msgh_remote_port
所提及的port
的发送权限到message
中。没错在Mach中权限是可以通过IPC传来传去的,这也是capability-based的体现。因为在前面mach_msg
中填的port
的接收方还是我们这个task
,因此我们可以获得一个发送权限。当然分享权限是有前提的,在源码中能看到MACH_MSG_TYPE_MAKE_SEND
的前提是持有发送权限,而我们则已经在main
中获取了发送权限[12]。
msgh_size
填入sizeof (struct message)
就行。
msgh_remote_port: When sending, specifies the destination port of the message. The field must carry a legitimate send or send-once right for a port. When received, this field is swapped with msgh_local_port.
在这个练习中就是变量port
。因为我们在msgh_bits
中的操作,我们会有一个发送权限。
msgh_local_port: When sending, specifies an auxiliary port right, which is conventionally used as a reply port by the recipient of the message. ......
接收port
我们就填个MACH_PORT_NULL
。
msgh_seqno: The sequence number of this message relative to the port from which it is received. This field is ignored on sent messages.
因为就一个message
,就填入0。
msgh_id: Not set or read by the mach_msg call. ......
我们用不到所以不管。
因此结果会是如下:
m.msg_header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
m.msg_header.msgh_size = sizeof (struct message);
m.msg_header.msgh_remote_port = port;
m.msg_header.msgh_local_port = MACH_PORT_NULL;
m.msg_header.msgh_seqno = 0;
再接下来就是m.first_header
,描述在Mach 3 Kernel Interface 330页。
typedef struct {
unsigned int msgt_name : 8,
msgt_size : 8,
msgt_number : 12,
msgt_inline : 1,
msgt_longform : 1,
msgt_deallocate : 1,
msgt_unused : 1;
} mach_msg_type_t
msgt_name
我们要传输的数据是"Alice\0"
和"Hello Alice\0"
,所以是MACH_MSG_TYPE_STRING_C
。
msgt_size: Specifies the size of each datum, in bits.
一个char
是一字节也就是8 bits,所以是8.
msgt_number: Specifies how many data elements comprise the data item.
我们就选择100。
msgt_inline: When FALSE, specifies that the data actucally resides in an out-of-line region. ......
我们的信息比较短能直接存在message
里所以是true
。
msgt_longform: Specifies, when TRUE, that this type descriptor is a mach_msg_type_long_t instead of a mach_msg_type_t.
我们是mach_msg_type_t
所以是false
。
msgt_unused: Not used, should be zero.
那就是0。
所以结果如下:
m.first_header.msgt_name = MACH_MSG_TYPE_STRING_C;
m.first_header.msgt_size = 8;
m.first_header.msgt_number = 100;
m.first_header.msgt_inline = true;
m.first_header.msgt_longform = false;
m.first_header.msgt_unused = 0;
最后再整合一下:
git diff
diff --git a/main.c b/main.c
index 2307de1..fcea63d 100644
--- a/main.c
+++ b/main.c
@@ -1,6 +1,8 @@
#include <threads.h>
#include <stdio.h>
#include <mach.h>
+#include <string.h>
+#include <stdbool.h>
struct message
{
@@ -21,7 +23,28 @@ int
client (void *arg)
{
struct message m;
- printf ("I'm client\n");
+ mach_msg_return_t err;
+
+ m.msg_header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
+ m.msg_header.msgh_size = sizeof (struct message);
+ m.msg_header.msgh_remote_port = port;
+ m.msg_header.msgh_local_port = MACH_PORT_NULL;
+ m.msg_header.msgh_seqno = 0;
+
+ m.first_header.msgt_name = MACH_MSG_TYPE_STRING_C;
+ m.first_header.msgt_size = 8;
+ m.first_header.msgt_number = 100;
+ m.first_header.msgt_inline = true;
+ m.first_header.msgt_longform = false;
+ m.first_header.msgt_unused = 0;
+
+ strcpy (m.string, "Alice\0");
+
+ err = mach_msg (&(m.msg_header), MACH_SEND_MSG | MACH_RCV_MSG, sizeof (struct message), sizeof (struct message), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+
+ printf ("Client error code: %d\n", err);
+
+ printf ("From Server: %s\n", m.string);
}
int
笔者增加了一个err
变量来检查mach_msg
是否成功,现在编译后运行的话应该会看到如下的输出:
I'm server
Client error code: 0
From Server: Alice
err
的值是0说明成功了,当然这不是我们最终想要的,我们还要把Server
给写了,出现这个输出的原因笔者认为是Client
接收了自己发出去的message
所以mach_msg
成功并且打印了"Alice\0"
。
Server
比较艰难的阶段已经过去了,接下来的操作都跟之前写Client
时有关了。Server
要先接收信息,所以只要MACH_RCV_MSG
而不是MACH_SEND_MSG | MACH_RCV_MSG
。
git diff
:
diff --git a/main.c b/main.c
index fcea63d..8200286 100644
--- a/main.c
+++ b/main.c
@@ -16,7 +16,13 @@ static mach_port_t port;
int
server (void *arg)
{
- printf ("I'm server\n");
+ struct message m;
+ mach_msg_return_t err;
+
+ err = mach_msg (&(m.msg_header), MACH_RCV_MSG, sizeof (struct message), sizeof (struct message), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+
+ printf ("Server receive error code: %d\n", err);
+ printf ("Server: from client: %s\n", m.string);
}
int
现在再编译运行的话我们就会看到:
Server receive error code: 0
Server: from client: Alice
<卡在这>
先庆祝一下!这意味着Server
成功从Client
接收了一个信息,而卡着说明Client
正在等待着来自Server
的回复。当然Client
这辈子都等不到它的回复了,因为我们还没写。那就Ctrl-C
结束进程然后把最后一步给写上去吧。
其实剩下的只要复制Client
的发送方式就行啦:
git diff
diff --git a/main.c b/main.c
index 8200286..03ed4e2 100644
--- a/main.c
+++ b/main.c
@@ -18,11 +18,32 @@ server (void *arg)
{
struct message m;
mach_msg_return_t err;
+ char s[100];
err = mach_msg (&(m.msg_header), MACH_RCV_MSG, sizeof (struct message), sizeof (struct message), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
printf ("Server receive error code: %d\n", err);
printf ("Server: from client: %s\n", m.string);
+
+ m.msg_header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
+ m.msg_header.msgh_size = sizeof (struct message);
+ m.msg_header.msgh_remote_port = port;
+ m.msg_header.msgh_local_port = MACH_PORT_NULL;
+ m.msg_header.msgh_seqno = 0;
+
+ m.first_header.msgt_name = MACH_MSG_TYPE_STRING_C;
+ m.first_header.msgt_size = 8;
+ m.first_header.msgt_number = 100;
+ m.first_header.msgt_inline = true;
+ m.first_header.msgt_longform = false;
+ m.first_header.msgt_unused = 0;
+
+ sprintf (s, "Hello %s\n\0", m.string);
+ strcpy (m.string, s);
+
+ err = mach_msg (&(m.msg_header), MACH_SEND_MSG, sizeof (struct message), sizeof (struct message), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+
+ printf ("Server send error code: %d\n", err);
}
int
最后编译运行后的输出:
Server receive error code: 0
Server: from client: Alice
Server send error code: 0
Server send error code: 0
Client error code: 0
From Server: Hello Alice
Client
成功接收到了Server
的回复!成功了!笔者就是不太清楚为什么会出现两次Server send error code: 0
。不过至少是成功了。
最后再放个完整的代码:
#include <threads.h>
#include <stdio.h>
#include <mach.h>
#include <string.h>
#include <stdbool.h>
struct message
{
mach_msg_header_t msg_header;
mach_msg_type_t first_header;
char string[100];
};
static mach_port_t port;
int
server (void *arg)
{
struct message m;
mach_msg_return_t err;
char s[100];
err = mach_msg (&(m.msg_header), MACH_RCV_MSG, sizeof (struct message), sizeof (struct message), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
printf ("Server receive error code: %d\n", err);
printf ("Server: from client: %s\n", m.string);
m.msg_header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
m.msg_header.msgh_size = sizeof (struct message);
m.msg_header.msgh_remote_port = port;
m.msg_header.msgh_local_port = MACH_PORT_NULL;
m.msg_header.msgh_seqno = 0;
m.first_header.msgt_name = MACH_MSG_TYPE_STRING_C;
m.first_header.msgt_size = 8;
m.first_header.msgt_number = 100;
m.first_header.msgt_inline = true;
m.first_header.msgt_longform = false;
m.first_header.msgt_unused = 0;
sprintf (s, "Hello %s\n\0", m.string);
strcpy (m.string, s);
err = mach_msg (&(m.msg_header), MACH_SEND_MSG, sizeof (struct message), sizeof (struct message), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
printf ("Server send error code: %d\n", err);
}
int
client (void *arg)
{
struct message m;
mach_msg_return_t err;
m.msg_header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
m.msg_header.msgh_size = sizeof (struct message);
m.msg_header.msgh_remote_port = port;
m.msg_header.msgh_local_port = MACH_PORT_NULL;
m.msg_header.msgh_seqno = 0;
m.first_header.msgt_name = MACH_MSG_TYPE_STRING_C;
m.first_header.msgt_size = 8;
m.first_header.msgt_number = 100;
m.first_header.msgt_inline = true;
m.first_header.msgt_longform = false;
m.first_header.msgt_unused = 0;
strcpy (m.string, "Alice\0");
err = mach_msg (&(m.msg_header), MACH_SEND_MSG | MACH_RCV_MSG, sizeof (struct message), sizeof (struct message), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
printf ("Client error code: %d\n", err);
printf ("From Server: %s\n", m.string);
}
int
main (void)
{
thrd_t server_thrd, client_thrd;
mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &port);
thrd_create (&server_thrd, server, NULL);
thrd_create (&client_thrd, client, NULL);
thrd_join (server_thrd, NULL);
thrd_join (client_thrd, NULL);
return 0;
}
总结
可以看到MIG还是帮开发者减少了很多烦恼的。在练习中也能更加清晰的感受到微内核的IPC是怎么一回事。在这里也感谢Hurd的开发者们和练习的作者Neal H Walfield的付出。有任何不正确的地方欢迎指出。
作者:chenw1
链接:https://www.cnblogs.com/chenw1/p/18768256
本文来自博客园,欢迎转载,但请注明原文链接,并保留此段声明,否则保留追究法律责任的权利。
All right reserved.
在GNU Hurd中感受Mach微内核的进程通信(IPC)的更多相关文章
- 【Chromium中文文档】跨进程通信 (IPC)
跨进程通信 (IPC) 转载请注明出处:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//General_Architecture/I ...
- C#中使用命名管道进行进程通信的实例
原文:C#中使用命名管道进行进程通信的实例 1 新建解决方案NamedPipeExample 在解决方案下面新建两个项目:Client和Server,两者的输出类型均为"Windows 应用 ...
- PHP 中的多进程使用,进程通信、进程信号等详解
多进程环境要求 Linux 系统 php-cli 模式 pcntl 扩展 或 swoole 扩展 pcntl 扩展 <?php $str = "hello world!" . ...
- [转帖](整理)GNU Hurd项目详解
(整理)GNU Hurd项目详解 http://www.ha97.com/3188.html 发表于: 开源世界 | 作者: 博客教主 标签: GNU,Hurd,详解,项目 Hurd原本是要成为GNU ...
- GNU/Hurd笔记整理
patch 0 关于文件锁支持的解决方案,大部分是由Neal Walfield在2001年完成的.这些补丁由Marcus Brinkmann发表,随后被Michael Banck于2002年修改了部分 ...
- [转帖]2015年时的新闻:Debian GNU/Hurd 2015 发布
Debian GNU/Hurd 2015 发布 oschina 发布于 2015年04月30日 https://www.oschina.net/news/62004/debian-gnu-hurd-2 ...
- C# 项目提交过程中感受
C# 项目提交过程中感受 新到一家互联网公司,昨天第一次提交代码,遇到了不少问题,而且大多数是代码格式问题,特此将范的错误记录下来,自我警示. 1. 代码对齐,这个虽然一直也都在注意,不过还是有一行代 ...
- GNU C 中零长度的数组【转】
原文链接:http://www.cnblogs.com/dolphin0520/p/3752492.html 在标准C和C++中,长度为0的数组是被禁止使用的.不过在GNU C中,存在一个非常奇怪的用 ...
- GNU C中的零长度数组
http://blog.csdn.net/ssdsafsdsd/article/details/8234736 在标准C和C++中,长度为0的数组是被禁止使用的.不过在GNU C中,存在一个非常奇怪的 ...
- GNU Makefile中的条件控制结构
在常见的编程语言中,使用条件控制结构诸如if ... else if ... else...是很寻常的事情,那么在GNU Makefile中如何使用呢? ifeq ifneq 例如:foo.sh #! ...
随机推荐
- JMeter使用指南+实验报告
JMeter使用指南 目录 JMeter使用指南 界面基本配置方法 1.选项里的放大与缩小--缩放字体 2.选项里的选择语言 3.命令行的调出 注意事项 一些指标介绍 1.TCP取样器 2.汇总/聚合 ...
- 探探的IM长连接技术实践:技术选型、架构设计、性能优化
本文由探探服务端高级技术专家张凯宏分享,原题"探探长链接项目的Go语言实践",因原文内容有较多错误,有修订和改动. 1.引言 即时通信长连接服务处于网络接入层,这个领域非常适合用G ...
- 16. C++快速入门--模板和Concept
待修改 1 定义模板 1.1 模板形参 模板参数 模板可以有两种参数, 一种是类型参数, 一种是非类型参数 这两种参数可以同时存在, 非类型参数 的类型 可以是 模板类型形参 template < ...
- 2025-01-04:不包含相邻元素的子序列的最大和。用go语言,给定一个整数数组 nums 和一个由二维数组 queries 组成的查询列表,其中每个查询的格式为 queries[i] = [pos
2025-01-04:不包含相邻元素的子序列的最大和.用go语言,给定一个整数数组 nums 和一个由二维数组 queries 组成的查询列表,其中每个查询的格式为 queries[i] = [pos ...
- Hbase shell学习
通过Shell工具可以对云数据库HBase进行数据管理,包括建表.插入数据.删除数据和删除表等操作,本文介绍Shell的基本使用命令. 访问配置 如果使用的是云数据库HBase标准版,基本环境的配置操 ...
- 学Shiro完结版-2
第四章 INI配置--<跟我学Shiro> 之前章节我们已经接触过一些INI配置规则了,如果大家使用过如Spring之类的IoC/DI容器的话,Shiro提供的INI配置也是非常类似的,即 ...
- 【java提高】---细则(2)
TreeSet(一) 一.TreeSet定义: 与HashSet是基于HashMap实现一样,TreeSet同样是基于TreeMap实现的. 1)TreeSet类概述 ...
- excel表格粘贴到网页的功能
背景 项目有表格功能,表格过大,一个一个填,过于麻烦. 需要从excel表复制的功能. 过程 监听paste事件,根据事件提供的clipboardData属性,获取数据. 根据换行符 \n 和tab符 ...
- python的类机制
python的类机制 参考:python面向对象 概念 方法重写/覆盖:若从父类继承的方法不能满足子类的需求,可以对其进行改写. 类变量:在实例化对象中是公用的,定义在类中,且在函数体之外,通常不作为 ...
- kNN(K- Nearest Neighbor)基本原理