什么是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了。使用passwdroot加个密码。用exit退出去然后再用demo登录,然后再用passwd改个密码[7]。用vim或者个人喜欢的编辑器打开/etc/ssh/ssh_configPasswordAuthentication 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 rightport name是在用户空间中看到的,而port right则是由Mach管理,就像文件描述符(file descriptor)一样。每个port right又代表了portrightport是一个单向沟通通道(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则拥有接收权利。读者可能会想问为什么要把clientserver放在同一个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_NONEMACH_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_bitsMACH_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)的更多相关文章

  1. 【Chromium中文文档】跨进程通信 (IPC)

    跨进程通信 (IPC) 转载请注明出处:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//General_Architecture/I ...

  2. C#中使用命名管道进行进程通信的实例

    原文:C#中使用命名管道进行进程通信的实例 1 新建解决方案NamedPipeExample 在解决方案下面新建两个项目:Client和Server,两者的输出类型均为"Windows 应用 ...

  3. PHP 中的多进程使用,进程通信、进程信号等详解

    多进程环境要求 Linux 系统 php-cli 模式 pcntl 扩展 或 swoole 扩展 pcntl 扩展 <?php $str = "hello world!" . ...

  4. [转帖](整理)GNU Hurd项目详解

    (整理)GNU Hurd项目详解 http://www.ha97.com/3188.html 发表于: 开源世界 | 作者: 博客教主 标签: GNU,Hurd,详解,项目 Hurd原本是要成为GNU ...

  5. GNU/Hurd笔记整理

    patch 0 关于文件锁支持的解决方案,大部分是由Neal Walfield在2001年完成的.这些补丁由Marcus Brinkmann发表,随后被Michael Banck于2002年修改了部分 ...

  6. [转帖]2015年时的新闻:Debian GNU/Hurd 2015 发布

    Debian GNU/Hurd 2015 发布 oschina 发布于 2015年04月30日 https://www.oschina.net/news/62004/debian-gnu-hurd-2 ...

  7. C# 项目提交过程中感受

    C# 项目提交过程中感受 新到一家互联网公司,昨天第一次提交代码,遇到了不少问题,而且大多数是代码格式问题,特此将范的错误记录下来,自我警示. 1. 代码对齐,这个虽然一直也都在注意,不过还是有一行代 ...

  8. GNU C 中零长度的数组【转】

    原文链接:http://www.cnblogs.com/dolphin0520/p/3752492.html 在标准C和C++中,长度为0的数组是被禁止使用的.不过在GNU C中,存在一个非常奇怪的用 ...

  9. GNU C中的零长度数组

    http://blog.csdn.net/ssdsafsdsd/article/details/8234736 在标准C和C++中,长度为0的数组是被禁止使用的.不过在GNU C中,存在一个非常奇怪的 ...

  10. GNU Makefile中的条件控制结构

    在常见的编程语言中,使用条件控制结构诸如if ... else if ... else...是很寻常的事情,那么在GNU Makefile中如何使用呢? ifeq ifneq 例如:foo.sh #! ...

随机推荐

  1. JMeter使用指南+实验报告

    JMeter使用指南 目录 JMeter使用指南 界面基本配置方法 1.选项里的放大与缩小--缩放字体 2.选项里的选择语言 3.命令行的调出 注意事项 一些指标介绍 1.TCP取样器 2.汇总/聚合 ...

  2. 探探的IM长连接技术实践:技术选型、架构设计、性能优化

    本文由探探服务端高级技术专家张凯宏分享,原题"探探长链接项目的Go语言实践",因原文内容有较多错误,有修订和改动. 1.引言 即时通信长连接服务处于网络接入层,这个领域非常适合用G ...

  3. 16. C++快速入门--模板和Concept

    待修改 1 定义模板 1.1 模板形参 模板参数 模板可以有两种参数, 一种是类型参数, 一种是非类型参数 这两种参数可以同时存在, 非类型参数 的类型 可以是 模板类型形参 template < ...

  4. 2025-01-04:不包含相邻元素的子序列的最大和。用go语言,给定一个整数数组 nums 和一个由二维数组 queries 组成的查询列表,其中每个查询的格式为 queries[i] = [pos

    2025-01-04:不包含相邻元素的子序列的最大和.用go语言,给定一个整数数组 nums 和一个由二维数组 queries 组成的查询列表,其中每个查询的格式为 queries[i] = [pos ...

  5. Hbase shell学习

    通过Shell工具可以对云数据库HBase进行数据管理,包括建表.插入数据.删除数据和删除表等操作,本文介绍Shell的基本使用命令. 访问配置 如果使用的是云数据库HBase标准版,基本环境的配置操 ...

  6. 学Shiro完结版-2

    第四章 INI配置--<跟我学Shiro> 之前章节我们已经接触过一些INI配置规则了,如果大家使用过如Spring之类的IoC/DI容器的话,Shiro提供的INI配置也是非常类似的,即 ...

  7. 【java提高】---细则(2)

    TreeSet(一) 一.TreeSet定义:      与HashSet是基于HashMap实现一样,TreeSet同样是基于TreeMap实现的.            1)TreeSet类概述 ...

  8. excel表格粘贴到网页的功能

    背景 项目有表格功能,表格过大,一个一个填,过于麻烦. 需要从excel表复制的功能. 过程 监听paste事件,根据事件提供的clipboardData属性,获取数据. 根据换行符 \n 和tab符 ...

  9. python的类机制

    python的类机制 参考:python面向对象 概念 方法重写/覆盖:若从父类继承的方法不能满足子类的需求,可以对其进行改写. 类变量:在实例化对象中是公用的,定义在类中,且在函数体之外,通常不作为 ...

  10. kNN(K- Nearest Neighbor)基本原理