<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>路上 &#187; 进程</title>
	<atom:link href="http://www.speedvi.net/tag/%e8%bf%9b%e7%a8%8b/feed" rel="self" type="application/rss+xml" />
	<link>http://www.speedvi.net</link>
	<description>为者常成 行者常至</description>
	<lastBuildDate>Sat, 12 Jun 2010 06:30:36 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>进程的启动</title>
		<link>http://www.speedvi.net/2010/01/11/188.html</link>
		<comments>http://www.speedvi.net/2010/01/11/188.html#comments</comments>
		<pubDate>Sun, 10 Jan 2010 17:12:58 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[进程]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/?p=188</guid>
		<description><![CDATA[　　任何线程都能够启动一个进程；唯一的限制就是安全原因的因素，比如文件访问、权限限制等等。实际上，你可以通过系统启动脚本、命令行以及程序调用的方式来启动一个进程。 从命令行启动一个进程 　　在命令行键入： 　　$ program1 　　就会启动一个叫做program1的程序并等待其运行结束。你也可以键入： 　　$ program2 &#38; 　　来启动一个叫做program2的程序而不需要等待其运行结束。我们可以称为这个程序在后台运行。 　　如果你想要在启动一个程序之前先调整这个程序的优先级，你可以使用nice命令，就像在UNIX系统中一样： 　　$ nice program3 　　这个指令将在降低的优先级启动这个程序program3。 在程序中启动一个进程 　　你可能有时会忽略命令行能创建进程，这也是命令行的基本功能。在一些应用程序设计中，可能会用到脚本程序（文件中有批处理指令）来完成某些工作，不过在其他情况下，你需要在程序中自己创建进程。 　　例如，在一个大型的多进程系统中，你需要一个主进程按照某种配置来启动其他的进程。另外的例子就是当检测到某些操作条件或事件时启动某个进程。 　　系统提供的能够启动其他进程的函数包括了： system() exec() spawn() fork() vfork() 　　你要使用哪个函数取决于两个因素：可移植性与功能性。一般来说，要在这两个因素之间寻找最佳的平衡。 　　所有的这些启动新进程的函数调用一般情况如下：在初始进程中的一个线程调用了上面所有函数其中的一个。之后，这个函数另进程管理器为新的进程创建一个地址空间。之后，系统内核在这个新的进程里面启动一个线程。这个线程执行几条指令，之后调用main()函数。（在fork()与vfork()这两个函数中，新线程在从fork()与vfork()返回后才开始在新进程里面运行） 使用system()开始新进程 　　函数system()是最简单的，它接收一个命令行指令（就像我们在命令行输入的一样的指令），之后开始执行组各国指令。 　　实际上，这个函数也确实启动了一个命令行来处理这条你想要执行的指令。 使用exec()与spawn()开始新进程 　　现在看看其他的进程创建函数。下面的就是exec()与spawn()这两个系列的进程创建函数。先看看这两个系列函数之间的差别。 　　exec()系列的函数将当前进程转变为另外的进程。也就是说当一个进程调用exec()函数之后，这个进程就会终止当前程序的运行并开始运行其他的程序。而进程的ID还没有改变，不过进程的程序已经变成另外一个了。那么进程中的线程都怎么样了？这个在下面再做讲解。 　　spawn()系列的函数就不会这样做。它会调用spawn()系列函数中的一个函数创建另外一个有新的ID的进程，在这个进程里面运行函数参数所指定的程序。 　　你可以查看下表中的spawn()与exec()两个函数的不同变体。在下表中你可以看出哪个函数是POSIX标准的，以及哪个函数不是POSIX标准的。为了最大的可移植性，最好是使用POSIX函数。 Spawn POSIX? Exec POSIX? spawn() No &#160; &#160; spawnl() No execl() Yes spawnle() No execle() Yes spawnlp() No execlp() Yes spawnlpe() [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/27/195.html' rel='bookmark' title='Permanent Link: 多线程中壁垒(barrier)的使用'>多线程中壁垒(barrier)的使用</a> <small>　　前面我们讲过main()函数与工作线程结束进行的同步，在那里提到了两种方式：pthread_join()函数以及壁垒(barrier)。 　　现在我们回到房子的比喻，假设这个家庭准备到哪个地方旅行。司机上了小货车并发动了引擎。之后，司机就开始等待。只有全部的家庭成员都上车之后，这个小货车才会开动——因为我们不想把任何人落下！ 　　这和我们在前面说的那个绘图程序的原理是一模一样的。主线程要等待全部工作线程结束后，才执行下一步的程序。 　　不过和这个比喻还有一个很大的差别。那就是通过使用pthread_join()函数，我们是等待所有工作线程的结束。也就是说，之后这些线程已经不存在了，它们退出了。 　　通过使用壁垒(barrier)，我们可以等待某些数量的线程在壁垒处集合。在设定的数目达到之后，我们解锁这些线程，让它们继续运行。 　　你先要使用pthread_barrier+init()函数来创建壁垒： #include &lt;pthread.h&gt; int pthread_barrier_init...</small></li>
<li><a href='http://www.speedvi.net/2010/01/18/191.html' rel='bookmark' title='Permanent Link: 线程的启动'>线程的启动</a> <small>　　任何线程在同一个进程中都可以创建另一个线程，这没有任何的限制。创建线程最常用的就是POSIX函数pthread_create()，该函数的定义如下： #include &lt;pthread.h&gt; int pthread_create (pthread_t *thread, const pthread_attr_t *attr,...</small></li>
<li><a href='http://www.speedvi.net/2010/02/24/211.html' rel='bookmark' title='Permanent Link: 用于线程同步的条件变量(Condition Variables)'>用于线程同步的条件变量(Condition Variables)</a> <small>　　条件变量(condition variables或condvars)与前面讲的睡眠锁(sleepon lock)非常类似。而实际上睡眠锁是在条件变量的基础上构建的，这也是为什么我们在睡眠锁的例子的解释表中有一个CONDVAR状态。它也能通过不停的调用pthread_cond_wait()函数来释放互斥体、等待以及重新获取互斥体，和pthread_sleepon_wait()函数一样。 　　下面我们就略过初始化的步骤，并使用条件变量来重新完成sleepon部分的那个生产者与消费者的多线程的程序。之后再讨论调用的函数。 &nbsp; /* * cp1.c */ #include...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　任何线程都能够启动一个进程；唯一的限制就是安全原因的因素，比如文件访问、权限限制等等。实际上，你可以通过系统启动脚本、命令行以及程序调用的方式来启动一个进程。</p>
<h5>从命令行启动一个进程</h5>
<p>　　在命令行键入：</p>
<p>　　$ program1</p>
<p>　　就会启动一个叫做program1的程序并等待其运行结束。你也可以键入：</p>
<p>　　$ program2 &amp;</p>
<p>　　来启动一个叫做program2的程序而不需要等待其运行结束。我们可以称为这个程序在后台运行。</p>
<p>　　如果你想要在启动一个程序之前先调整这个程序的优先级，你可以使用nice命令，就像在UNIX系统中一样：</p>
<p>　　$ nice program3</p>
<p>　　这个指令将在降低的优先级启动这个程序program3。</p>
<h5>在程序中启动一个进程</h5>
<p>　　你可能有时会忽略命令行能创建进程，这也是命令行的基本功能。在一些应用程序设计中，可能会用到脚本程序（文件中有批处理指令）来完成某些工作，不过在其他情况下，你需要在程序中自己创建进程。</p>
<p>　　例如，在一个大型的多进程系统中，你需要一个主进程按照某种配置来启动其他的进程。另外的例子就是当检测到某些操作条件或事件时启动某个进程。</p>
<p>　　系统提供的能够启动其他进程的函数包括了：</p>
<ul>
<li>system()
<li>exec()
<li>spawn()
<li>fork()
<li>vfork() </li>
</ul>
<p>　　你要使用哪个函数取决于两个因素：可移植性与功能性。一般来说，要在这两个因素之间寻找最佳的平衡。</p>
</p>
<p><span id="more-188"></span>
<p>　　所有的这些启动新进程的函数调用一般情况如下：在初始进程中的一个线程调用了上面所有函数其中的一个。之后，这个函数另进程管理器为新的进程创建一个地址空间。之后，系统内核在这个新的进程里面启动一个线程。这个线程执行几条指令，之后调用main()函数。（在fork()与vfork()这两个函数中，新线程在从fork()与vfork()返回后才开始在新进程里面运行）</p>
<h6>使用system()开始新进程</h6>
<p>　　函数system()是最简单的，它接收一个命令行指令（就像我们在命令行输入的一样的指令），之后开始执行组各国指令。</p>
<p>　　实际上，这个函数也确实启动了一个命令行来处理这条你想要执行的指令。</p>
<h6>使用exec()与spawn()开始新进程</h6>
<p>　　现在看看其他的进程创建函数。下面的就是exec()与spawn()这两个系列的进程创建函数。先看看这两个系列函数之间的差别。</p>
<p>　　exec()系列的函数将当前进程转变为另外的进程。也就是说当一个进程调用exec()函数之后，这个进程就会终止当前程序的运行并开始运行其他的程序。而进程的ID还没有改变，不过进程的程序已经变成另外一个了。那么进程中的线程都怎么样了？这个在下面再做讲解。</p>
<p>　　spawn()系列的函数就不会这样做。它会调用spawn()系列函数中的一个函数创建另外一个有新的ID的进程，在这个进程里面运行函数参数所指定的程序。</p>
<p>　　你可以查看下表中的spawn()与exec()两个函数的不同变体。在下表中你可以看出哪个函数是POSIX标准的，以及哪个函数不是POSIX标准的。为了最大的可移植性，最好是使用POSIX函数。</p>
<table border="1" width="100%">
<tbody>
<tr>
<th>Spawn </th>
<th>POSIX? </th>
<th>Exec </th>
<th>POSIX?</th>
</tr>
<tr>
<td><i class="func">spawn()</i> </td>
<td>No </td>
<td>&nbsp; </td>
<td>&nbsp;</td>
</tr>
<tr>
<td><i class="func">spawnl()</i> </td>
<td>No </td>
<td><i class="func">execl()</i> </td>
<td>Yes</td>
</tr>
<tr>
<td><i class="func">spawnle()</i> </td>
<td>No </td>
<td><i class="func">execle()</i> </td>
<td>Yes</td>
</tr>
<tr>
<td><i class="func">spawnlp()</i> </td>
<td>No </td>
<td><i class="func">execlp()</i> </td>
<td>Yes</td>
</tr>
<tr>
<td><i class="func">spawnlpe()</i> </td>
<td>No </td>
<td><i class="func">execlpe()</i> </td>
<td>No</td>
</tr>
<tr>
<td><i class="func">spawnp()</i> </td>
<td>No </td>
<td>&nbsp; </td>
<td>&nbsp;</td>
</tr>
<tr>
<td><i class="func">spawnv()</i> </td>
<td>No </td>
<td><i class="func">execv()</i> </td>
<td>Yes</td>
</tr>
<tr>
<td><i class="func">spawnve()</i> </td>
<td>No </td>
<td><i class="func">execve()</i> </td>
<td>Yes</td>
</tr>
<tr>
<td><i class="func">spawnvp()</i> </td>
<td>No </td>
<td><i class="func">execvp()</i> </td>
<td>Yes</td>
</tr>
<tr>
<td><i class="func">spawnvpe()</i> </td>
<td>No </td>
<td><i class="func">execvpe()</i> </td>
<td>No</td>
</tr>
</tbody>
</table>
<p>　　这么多变体可能会比较混乱，实际上它们的后缀是有固定模式的，如下表所示：</p>
<table border="1" width="100%">
<tbody>
<tr>
<th>后缀 </th>
<th>含义 </th>
</tr>
<tr>
<td><tt class="lit">l</tt> (<span class="quote">“L”的小写</span>) </td>
<td>函数的参数表通过调用设定的参数表而定，并使用<span class="const">NULL作为终止。</span></td>
</tr>
<tr>
<td><tt class="lit">e</tt> </td>
<td>环境变量被设定。</td>
</tr>
<tr>
<td><tt class="lit">p</tt> </td>
<td><strong>PATH</strong>环境变量在程序的完整路径未确定时将被使用。</td>
</tr>
<tr>
<td><tt class="lit">v</tt> </td>
<td>函数参数通过指向参数向量的指针所确定。</td>
</tr>
</tbody>
</table>
<p>　　参数表就是传递给程序的一个命令行参数的列表。</p>
<p>　　另外，在C库中，spwanlp()、spawnvp()、spawnlpe()全都调用spawnvpe()函数，而这个函数还会接着调用spawnp()函数。spawnle()、spawnv()、spawnl()函数最终都会调用spawnve()函数，而这个函数还会接着调用spawn()函数。最后，spawnp()也会调用spawn()函数。也就是说这个spawning功能都是通过spawn()实现的。</p>
<p>　　下面看几个例子：</p>
<p>“l”后缀</p>
<p>　　如果我们想要启动ls命令并使用-t、-r以及-l参数（分别表示用时间排列、反序排列以及使用长文件名显示），我们就可以这样使用：</p>
<pre class="codesamp">/* To run ls and keep going: */
spawnl (P_WAIT, "/bin/ls", "/bin/ls", "-t", "-r", "-l", NULL);

/* To transform into ls: */
execl ("/bin/ls", "/bin/ls", "-t", "-r", "-l", NULL);</pre>
<p>　　或者是使用v后缀变量：</p>
<pre class="codesamp">char *argv [] =
{
    "/bin/ls",
    "-t",
    "-r",
    "-l",
    NULL
};

/* To run ls and keep going: */
spawnv (P_WAIT, "/bin/ls", argv);

/* To transform into ls: */
execv ("/bin/ls", argv);</pre>
<p>　　之所以这样使用就是为了方便。你可能在你的程序中内置了解释器，这样传递字符串数组就很方便了。这时就推荐使用“v”后缀的变体。如果，你对你的编程中所使用的程序的参数非常熟悉，这时就没有必要使用字符串数组了，你可以使用那个后缀为 l 的变体函数了。</p>
<p>　　你可以看到程序中我们传送的是程序的实际路径(/bin/ls)以及在第一个参数中再次写出程序的名称。以让程序能够按照调用时所设定的条件执行。</p>
<p>　　例如，GNU的压缩以及解压缩功能(gzip和gunzip)实际上连接到了同一个执行文件。当这个执行文件启动时，它会先看参数argv[0]（传递给main()函数）并确定是压缩还是解压缩。</p>
<p>“e”后缀</p>
<p>　　e后缀会将环境变量传递给程序。所谓环境就是指程序操作时所给的外部环境。举例来说，比如你有一个拼写检查程序，它有一个单词字典。在每次使用命令行调用这个程序的时候就没有必要每次都设定字典的路径，你可以将这个路径设置到环境变量中，如下面的代码所示：</p>
<pre class="cmdsamp">$ export DICTIONARY=/home/rk/.dict

$ spellcheck document.1</pre>
<p>　　这里的export命令告诉命令行创建一个新的环境变量（在这里就是DICTIONARY），并将这个环境变量的值指定为/home/rk/.dict。</p>
<p>　　如果你要使用不同的字典，就需要在运行这个程序之前修改这个环境变量。在命令行中很简单：</p>
<pre class="cmdsamp">$ export DICTIONARY=/home/rk/.altdict

$ spellcheck document.1</pre>
<p>　　不过如果在你的程序中该如何完成呢？就可以使用“e”版本的spawn()与exec()函数了，你可以设定一个代表环境变量的字符串数组：</p>
<pre class="codesamp">char *env [] =
{
    "DICTIONARY=/home/rk/.altdict",
    NULL
};

// To start the spell-checker:
spawnle (P_WAIT, "/usr/bin/spellcheck", "/usr/bin/spellcheck",
         "document.1", NULL, env);

// To transform into the spell-checker:
execle ("/usr/bin/spellcheck", "/usr/bin/spellcheck",
        "document.1", NULL, env);</pre>
<p>“p”后缀</p>
<p>　　p后缀版本的函数将在你的PATH环境变量里面所指定的目录中寻找可执行文件。你可能注意到了前面的例子中所有的可执行文件都有一个给定的地址——/bin/ls以及/usr/bin/spellcheck。那么其他的可执行文件呢？除非你先找到了那个特定程序的准确地址，那么最好是让用户来告诉你的程序到哪里去寻找可执行文件，标准的PATH环境变量就是做这个用的。下面就是一个极小系统中的一个例子：</p>
<pre class="cmdsamp">PATH=/proc/boot:/bin</pre>
<p>　　这就告诉了命令行，当我键入一个指令的时候，它应该现在/proc/boot目录中查找，如果在那里找不到的话，再到/bin目录下查找。PATH是使用冒号来分开的目录列表。你可以为PATH添加任意多的元素，不过你需要留意的就是所有的路径都会被按照先后顺序搜索可执行文件。</p>
<p>　　如果你不知道可执行文件的路径，就可以使用p的函数变体。举例如下：</p>
<pre class="codesamp">// Using an explicit path:
execl ("/bin/ls", "/bin/ls", "-l", "-t", "-r", NULL);

// Search your PATH for the executable:
execlp ("ls", "ls", "-l", "-t", "-r", NULL);</pre>
<p>　　如果execl()在/bin目录中找不到ls，它就会返回一个错误。exexlp()函数将会在PATH中所给的全部路径中寻找ls，只有在所有的路径中都找不到ls才会返回错误。这对于多平台的支持是有利的，你的程序没有必要在编程是就知道不同的CPU名称，它只需要找到可执行文件就行。</p>
<p>　　那么下面的这条语句会做什么呢？</p>
<pre class="codesamp">execlp ("/bin/ls", "ls", "-l", "-t", "-r", NULL);</pre>
<p>　　它会搜索环境变量里的路径么？不会的。这里是让execlp()使用一个明确的路径，并让其忽略了普通的PATH搜索规则。如果它在/bin目录中没有找到ls，那么它就不会做进一步的努力了。</p>
<p>　　将明确的路径与命令名混在是不是危险的？（就像这样，路径参数为/bin/ls，命令名参数为ls，而不是/bin/ls）实际上这一般是很安全的，因为：</p>
<ul>
<li>很多程序忽略第一个命令行参数argv[0]；</li>
<li>那些不忽略的一般都调用basename()函数，这个函数就会将argv[0]中的目录部分去掉，只返回名字。</li>
</ul>
<p>　　为第一个命令行参数指定明确路径的唯一的原因就是让程序能够输出诊断信息，并在诊断信息里面有第一个参数，这个参数可以清晰的告诉你程序是从哪里启动的。在程序可以在PATH中的多个路径都可以找到的时候是很有帮助的。</p>
<p>　　spawn()都有一个额外的参数，在上面所有的例子中，我们都一直指定P_WAIT。一共有四种标志可以用来传递给spawn()来改变其行为。</p>
<p>P_WAIT</p>
<p>　　调用进程（也就是你的程序）将闭塞，知道这个新建的程序运行结束并退出。</p>
<p>P_NOWAIT</p>
<p>　　调用程序在新建程序运行时将不闭塞。你可以这样在后台启动一个程序并在这个程序做自己的事的时候继续运行。</p>
<p>P_NOWAITO</p>
<p>　　与P_NOWAIT功能相同，不同的地方是SPWAN_NOZOMBIE标志被设定，也就是说你不用再使用waitpid()函数来清除那个进程的退出码了。</p>
<p>P_OVERLAY</p>
<p>　　这个标志将spawn()函数转换为exec()函数。你的程序转换为那个指定的程序并且进程ID不变。如果你确实想这样做的话，最好还是使用exec()函数，这样就更清晰些。</p>
<p>“单纯”spawn()</p>
<p>　　就像我们上面说提的，所有的spawn()函数最终都会调用spawn()函数。下面就是spawn()函数的原型：</p>
<pre class="codesamp">#include &lt;spawn.h&gt;

pid_t
spawn (const char *<i class="var">path</i>,
       int <i class="var">fd_count</i>,
       const int <i class="var">fd_map</i> [],
       const struct inheritance *<i class="var">inherit</i>,
       char * const <i class="var">argv</i> [],
       char * const <i class="var">envp</i> []);</pre>
<p>　　现在我们可以直接省略path、argv以及envp参数了，这几个参数在前面的例子中已经详细讲解过。</p>
<p>　　fd_count和fd_map两个参数是互相关联的。如果你的指定fd_count为0，那么fd_map就被忽略，这就意味着所有的文件描述符（除了被fcntl()所修改的）将被继承到新的进程。如果fd_count不是0，那它就是指定了在fd_map所包含的文件描述符的数量；这些指定的文件将被继承。</p>
<p>　　inherit参数是一个指向结构的指针，在这个结构中包含了一组标志位、信号掩码等等。</p>
<h6>使用fork()开始新进程</h6>
<p>　　如果你想要创建一个新的进程，这个进程与当前执行的进程一模一样并让其与当前进程同时运行。你可以使用spawn()函数缤纷使用p_NOWAIT参数，提供给这个新的进程足够的信息，包括你的进程的确切状态。以让其能够设置自己。不过，这个方法可能是非常复杂的，因为描述当前进程的状态牵涉到大量的数据。</p>
<p>　　其实有个更简单的方式，就是fork()函数，这个函数可以复制当前进程。所有的代码将是一样的，而且数据与创建进程或父进程也是一样的。</p>
<p>　　当然了，创建一个与父进程一模一样的进程是不可能的。这两个进程之间最明显的不同就是它们的进程ID，两个进程的ID是不能相同的。如果在手册中仔细查看fork()的文档就会发现这两个进程之间还有很多的不同。在你使用这个函数之前最好要搞清楚这些差别。</p>
<p>　　如果fork()函数两方面的进程非常像，该如何分辨它们呢？当你调用fork()函数的时候，就在与父进程相同的地方创建了一个运行相同代码的新进程。例子代码如下：</p>
<pre class="codesamp">int main (int argc, char **argv)
{
    int retval;

    printf ("This is most definitely the parent process\n");
    fflush (stdout);
    retval = fork ();
    printf ("Which process printed this?\n");

    return (EXIT_SUCCESS);
}</pre>
<p>　　在fork()调用之后，两个进程都会执行第二条的printf()函数。如果你运行这个程序，它打印出来的结果就像下面这样：</p>
<pre class="codesamp">This is most definitely the parent process
Which process printed this?
Which process printed this?</pre>
<p>　　两个进程打印了第二行。</p>
<p>　　区分两个进程的唯一方法就是fork()函数的返回值retval。在新建的子进程中，retval为0；在父进程中，这个变量的值是子进程的ID。</p>
<p>　　下面是另一段用来搞清概念的代码：</p>
<pre class="codesamp">printf ("The parent is pid %d\n", getpid ());
fflush (stdout);

if (child_pid = fork ()) {
    printf ("This is the parent, child pid is %d\n",
            child_pid);
} else {
    printf ("This is the child, pid is %d\n",
            getpid ());
}</pre>
<p>　　这个程序的可能输出如下：</p>
<pre class="codesamp">The parent is pid 4496
This is the parent, child pid is 8197
This is the child, pid is 8197</pre>
<p>　　通过fork()函数的返回值你可以区别两个进程了。</p>
<h6>使用vfork()开始新进程</h6>
<p>　　vfork()函数要比普通的fork()函数消耗更少的资源，因为它共享了父进程的地址空间。</p>
<p>　　vfork()函数创建一个子进程，之后就暂停父线程直到子进程调用exec()函数或退出。另外，vfork()函数将在物理内存模型系统上工作，而fork()函数是不能做的，fork（）函数要创建一样的地址空间，而这不可能是物理内存模型的。</p>
<h6>启动进程与线程</h6>
<p>　　假设你有一个进程，并且在这个进程中尚未创建任何线程。当你调用fork()函数，另外的进程就被创建并且也只有一个线程。这是简单的例子。</p>
<p>　　现在假设在你的进程中，使用pthread_create()函数创建了一个新的线程。再调用fork()函数，就会返回ENOSYS（表示不支持该函数）参数。这是为什么？</p>
<p>　　不过，这确实是POSIX兼容的，因为POSIX说fork()函数可以返回ENOSYS。实际的情况是这样的：系统的C函数库中没有处理有多个线程的进程的分支功能。当调用pthread_create()函数，在各个函数会设置一个标志，这个标志表明“别处理fork()函数，我没准备处理它”。另外在函数库的fork()函数中，将检查这个标志，如果这个标志已经被设定了，就返回ENOSYS。</p>
<p>　　这是故意设定这样做的，而起因就应该是线程与互斥体。如果没有这个限制，新建的进程就与原始进程有同样多的线程。这可能是你所希望的。不过，由于原始线程可能拥有互斥体，这就变得复杂了。由于新建进程有与原始进程一样的数据空间内容，库函数就必须跟踪在原始进程中哪个互斥体被哪个线程所拥有，并将这个拥有关系复制到新进程里面。实际上这是不可能的，有个pthread_atfork()函数可以来处理这个，不过并不是所有的系统互斥体支持这个函数的。</p>
<h6>我们该如何做？</h6>
<p>　　当你移植已有代码的时候，你肯定是保留越多的原代码越好。对于新代码来说，要尽量避免使用fork()函数。原因如下：</p>
<ul>
<li>fork()不支持多线程；</li>
<li>由于fork()不支持多线程，你就需要注册一个pthread_arfork()句柄并在你使用fork之前锁定每一个互斥体，这会增加你的设计的复杂度；</li>
<li>fork()函数创建的子进程会复制所有打开的文件的描述符。这将导致很多无用的操作。</li>
</ul>
<p>　　vfork()函数与spawn()函数之间的选择归结到一点就是可移植性，以及你想要父进程与子进程做些什么。函数vfork()会暂停父进程直到子进程调用exec()或退出，而spawn()系列的函数可以让两者同时运行。而vfork()在不同的操作系统之间也有轻微的不同。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/27/195.html' rel='bookmark' title='Permanent Link: 多线程中壁垒(barrier)的使用'>多线程中壁垒(barrier)的使用</a> <small>　　前面我们讲过main()函数与工作线程结束进行的同步，在那里提到了两种方式：pthread_join()函数以及壁垒(barrier)。 　　现在我们回到房子的比喻，假设这个家庭准备到哪个地方旅行。司机上了小货车并发动了引擎。之后，司机就开始等待。只有全部的家庭成员都上车之后，这个小货车才会开动——因为我们不想把任何人落下！ 　　这和我们在前面说的那个绘图程序的原理是一模一样的。主线程要等待全部工作线程结束后，才执行下一步的程序。 　　不过和这个比喻还有一个很大的差别。那就是通过使用pthread_join()函数，我们是等待所有工作线程的结束。也就是说，之后这些线程已经不存在了，它们退出了。 　　通过使用壁垒(barrier)，我们可以等待某些数量的线程在壁垒处集合。在设定的数目达到之后，我们解锁这些线程，让它们继续运行。 　　你先要使用pthread_barrier+init()函数来创建壁垒： #include &lt;pthread.h&gt; int pthread_barrier_init...</small></li>
<li><a href='http://www.speedvi.net/2010/01/18/191.html' rel='bookmark' title='Permanent Link: 线程的启动'>线程的启动</a> <small>　　任何线程在同一个进程中都可以创建另一个线程，这没有任何的限制。创建线程最常用的就是POSIX函数pthread_create()，该函数的定义如下： #include &lt;pthread.h&gt; int pthread_create (pthread_t *thread, const pthread_attr_t *attr,...</small></li>
<li><a href='http://www.speedvi.net/2010/02/24/211.html' rel='bookmark' title='Permanent Link: 用于线程同步的条件变量(Condition Variables)'>用于线程同步的条件变量(Condition Variables)</a> <small>　　条件变量(condition variables或condvars)与前面讲的睡眠锁(sleepon lock)非常类似。而实际上睡眠锁是在条件变量的基础上构建的，这也是为什么我们在睡眠锁的例子的解释表中有一个CONDVAR状态。它也能通过不停的调用pthread_cond_wait()函数来释放互斥体、等待以及重新获取互斥体，和pthread_sleepon_wait()函数一样。 　　下面我们就略过初始化的步骤，并使用条件变量来重新完成sleepon部分的那个生产者与消费者的多线程的程序。之后再讨论调用的函数。 &nbsp; /* * cp1.c */ #include...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/01/11/188.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>实际系统中的进程与线程</title>
		<link>http://www.speedvi.net/2010/01/09/185.html</link>
		<comments>http://www.speedvi.net/2010/01/09/185.html#comments</comments>
		<pubDate>Fri, 08 Jan 2010 16:47:25 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[进程]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/?p=185</guid>
		<description><![CDATA[　　我们知道一个进程可以有一个或多个线程。如果一个进程有0个线程那就无法作任何事了，就像是没有人在家就不能实际完成任何事情了。而操作系统可以有一个或多个进程在运行中。这里可以同样引用上面的例子，没有任何进程的系统也做不了什么事情的。 　　那么这些线程与进程都干了些什么？最终它们形成了操作系统，就是为了完成某个目标的线程与进程的集合。 　　在最高层，系统包含了一定数目的进程。每个进程负责提供某种类型的服务，包括了文件系统、显示驱动、数据采集模块、控制模块等等。 　　在每个进程中，都有一定数量的线程。这些线程的数目都是不同的。一个设计者只使用一个线程的设计可能能够完成另外一个设计者使用五个线程实现的功能。有些问题由于自身的原因必须使用多线程来解决，而实际上也是相对来说易于解决的，而另外的问题可能只能使用单线程处理，很难使用多线程来解决。 为何使用多个进程？ 　　那么我们为什么不在一个进程使用一亿个线程呢？也许有的系统强迫你按照这种方式编码，不过将这些事物分散到多个进程中的益处也是很多的，这些益处包括了： 防干扰与模块化； 可维护性； 可靠性。 　　这种将问题分解为几个独立的问题的能力是一个非常有用的概念。这也是操作系统的核心。操作系统包含了很多独立的模块，每个模块负责一定的事务。这些独立的模块都是独立的进程。这些模块之间唯一的依赖就是少数完善定义的接口。 　　由于缺少相互之间的依赖，这种结构就能带来增强的可维护性。每个模块有其自己的特别的定义，这样就很容易来对一个模块进行修补，特别是这个模块不和任何其他模块有联系的时候。 　　可靠性可能是最重要的一点了。一个进程就像是一个房子一样有着明确的边界定义。房中的一个人可以清晰地分辨出他自己什么时候在房子里面，什么时候在房子外面。一个线程也有同样的情况，只有在其访问进程中的内存时它才能够生存。如果越过了进程地址空间的边界，它就会被杀掉。这就是说，两个运行在不同进程中的线程被有效的相互隔离了。 　　进程地址空间由操作系统的进程管理模块维护。当一个进程开始的时候，进程管理器就为其分配一定数量的内存并开始一个线程的运行。这块被分配的内存就被标志为归那个进程所有。 　　这就意味着如果在那个进程中有多个线程，内核就需要在这些线程之间完成环境变量切换，这是一个非常高效的操作，因为我们不需要更换地址空间，所有的线程都在这个地址空间运行。如果我们需要切换到其他进程中的一个线程，进程管理器就会介入并执行地址空间的切换。不过不用担心，虽然增加了一步操作会消耗资源，不过在操作系统中这个操作也是非常快的。 Related posts:进程与线程 进程与线程基础 　　在谈论线程、进程、时间片以及更神奇的调度概念之前，先打个比方。 　　我们首先要说明的就是线程与进程是如何工作的。最形象的理解就是将线程与进程想象为现实世界的东西。我们的比方如下： 进程想象为一个房子 　　先使用我们在日常生活中常见的东西——房子来开始线程与进程的比方。 　　房子其实就是一个容器，并且有其自己的特定属性（比如有几层楼、多少个房间等等）。 　　在这个比方中，我们可以看到房子靠其自身是不能做任何事的——它是一个被动的东西。而进程也是这样的。 线程想象为房子的住户 　　在房子中居住的人才是主动的对象，他们才是房子的使用者，在这里看电视、做饭、洗澡等等。 &#160;... 进程与线程 信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &#160; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。... 系统内核的角色 　　我们前面的使用房子做的比喻对于解释同步的概念是非常合适的，但对系统中另一个重要方面就不合适了。在我们的比喻中，多个线程是同时运行的。可是，在实际的系统中，一般只有一个处理器，那么在一个时间点上只能运行一件“事”。 单处理器情况 　　下面看看在实际系统中的真实运行情况，在较“经济”的情况下，一个系统中只有一个中央处理器。这时，在任何时间点就只能有一个线程可以运行。系统内核使用数种规则来决定让哪个线程运行并运行该线程。 多处理器情况 　　如果你购买的计算机有多个、相同的CPU，并且这些CPU共享内存与设备，那么就可以说你有了一个SMP系统了（SMP是对称多处理器的简称，指系统中的这些处理器是一样的）。这时，可以同时运行的线程的数量就受限于CPU的个数了。由于一个CPU一次只能运行一个线程，在有多个CPU的情况下，多个线程就可以同步运行。 　　我们可以忽略CPU的个数，而抽象的设计系统能够让多个线程同步运行，虽然实际情况并不是这样。 内核作为仲裁者 　　那么谁来决定在指定时间哪个线程运行呢？这就是内核的工作职责。 　　内核决定了在特定时刻可以使用CPU的线程，并切换环境变量到那个线程。下面详细说明一下内核对CPU做了什么。 　　CPU有一定数量的寄存器（寄存器的具体数量依赖于处理器的类型）。当线程运行的时候，线程的一些信息就保存在这些寄存器里面，例如当前程序位置等等。...


Related posts:<ol><li><a href='http://www.speedvi.net/2009/12/22/181.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>进程与线程基础 　　在谈论线程、进程、时间片以及更神奇的调度概念之前，先打个比方。 　　我们首先要说明的就是线程与进程是如何工作的。最形象的理解就是将线程与进程想象为现实世界的东西。我们的比方如下： 进程想象为一个房子 　　先使用我们在日常生活中常见的东西——房子来开始线程与进程的比方。 　　房子其实就是一个容器，并且有其自己的特定属性（比如有几层楼、多少个房间等等）。 　　在这个比方中，我们可以看到房子靠其自身是不能做任何事的——它是一个被动的东西。而进程也是这样的。 线程想象为房子的住户 　　在房子中居住的人才是主动的对象，他们才是房子的使用者，在这里看电视、做饭、洗澡等等。 &nbsp;...</small></li>
<li><a href='http://www.speedvi.net/2009/12/23/182.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &nbsp; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。...</small></li>
<li><a href='http://www.speedvi.net/2009/12/26/183.html' rel='bookmark' title='Permanent Link: 系统内核的角色'>系统内核的角色</a> <small>　　我们前面的使用房子做的比喻对于解释同步的概念是非常合适的，但对系统中另一个重要方面就不合适了。在我们的比喻中，多个线程是同时运行的。可是，在实际的系统中，一般只有一个处理器，那么在一个时间点上只能运行一件“事”。 单处理器情况 　　下面看看在实际系统中的真实运行情况，在较“经济”的情况下，一个系统中只有一个中央处理器。这时，在任何时间点就只能有一个线程可以运行。系统内核使用数种规则来决定让哪个线程运行并运行该线程。 多处理器情况 　　如果你购买的计算机有多个、相同的CPU，并且这些CPU共享内存与设备，那么就可以说你有了一个SMP系统了（SMP是对称多处理器的简称，指系统中的这些处理器是一样的）。这时，可以同时运行的线程的数量就受限于CPU的个数了。由于一个CPU一次只能运行一个线程，在有多个CPU的情况下，多个线程就可以同步运行。 　　我们可以忽略CPU的个数，而抽象的设计系统能够让多个线程同步运行，虽然实际情况并不是这样。 内核作为仲裁者 　　那么谁来决定在指定时间哪个线程运行呢？这就是内核的工作职责。 　　内核决定了在特定时刻可以使用CPU的线程，并切换环境变量到那个线程。下面详细说明一下内核对CPU做了什么。 　　CPU有一定数量的寄存器（寄存器的具体数量依赖于处理器的类型）。当线程运行的时候，线程的一些信息就保存在这些寄存器里面，例如当前程序位置等等。...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　我们知道一个进程可以有一个或多个线程。如果一个进程有0个线程那就无法作任何事了，就像是没有人在家就不能实际完成任何事情了。而操作系统可以有一个或多个进程在运行中。这里可以同样引用上面的例子，没有任何进程的系统也做不了什么事情的。</p>
<p>　　那么这些线程与进程都干了些什么？最终它们形成了操作系统，就是为了完成某个目标的线程与进程的集合。</p>
<p>　　在最高层，系统包含了一定数目的进程。每个进程负责提供某种类型的服务，包括了文件系统、显示驱动、数据采集模块、控制模块等等。</p>
<p>　　在每个进程中，都有一定数量的线程。这些线程的数目都是不同的。一个设计者只使用一个线程的设计可能能够完成另外一个设计者使用五个线程实现的功能。有些问题由于自身的原因必须使用多线程来解决，而实际上也是相对来说易于解决的，而另外的问题可能只能使用单线程处理，很难使用多线程来解决。</p>
<h5>为何使用多个进程？<br /></h5>
<p>　　那么我们为什么不在一个进程使用一亿个线程呢？也许有的系统强迫你按照这种方式编码，不过将这些事物分散到多个进程中的益处也是很多的，这些益处包括了：</p>
<ul style="margin-left: 43pt">
<li>防干扰与模块化；
<li>可维护性；
<li>可靠性。</li>
</ul>
<p>　　这种将问题分解为几个独立的问题的能力是一个非常有用的概念。这也是操作系统的核心。操作系统包含了很多独立的模块，每个模块负责一定的事务。这些独立的模块都是独立的进程。这些模块之间唯一的依赖就是少数完善定义的接口。</p>
<p>　　由于缺少相互之间的依赖，这种结构就能带来增强的可维护性。每个模块有其自己的特别的定义，这样就很容易来对一个模块进行修补，特别是这个模块不和任何其他模块有联系的时候。</p>
<p>　　可靠性可能是最重要的一点了。一个进程就像是一个房子一样有着明确的边界定义。房中的一个人可以清晰地分辨出他自己什么时候在房子里面，什么时候在房子外面。一个线程也有同样的情况，只有在其访问进程中的内存时它才能够生存。如果越过了进程地址空间的边界，它就会被杀掉。这就是说，两个运行在不同进程中的线程被有效的相互隔离了。</p>
<p>　　进程地址空间由操作系统的进程管理模块维护。当一个进程开始的时候，进程管理器就为其分配一定数量的内存并开始一个线程的运行。这块被分配的内存就被标志为归那个进程所有。</p>
<p>　　这就意味着如果在那个进程中有多个线程，内核就需要在这些线程之间完成环境变量切换，这是一个非常高效的操作，因为我们不需要更换地址空间，所有的线程都在这个地址空间运行。如果我们需要切换到其他进程中的一个线程，进程管理器就会介入并执行地址空间的切换。不过不用担心，虽然增加了一步操作会消耗资源，不过在操作系统中这个操作也是非常快的。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2009/12/22/181.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>进程与线程基础 　　在谈论线程、进程、时间片以及更神奇的调度概念之前，先打个比方。 　　我们首先要说明的就是线程与进程是如何工作的。最形象的理解就是将线程与进程想象为现实世界的东西。我们的比方如下： 进程想象为一个房子 　　先使用我们在日常生活中常见的东西——房子来开始线程与进程的比方。 　　房子其实就是一个容器，并且有其自己的特定属性（比如有几层楼、多少个房间等等）。 　　在这个比方中，我们可以看到房子靠其自身是不能做任何事的——它是一个被动的东西。而进程也是这样的。 线程想象为房子的住户 　　在房子中居住的人才是主动的对象，他们才是房子的使用者，在这里看电视、做饭、洗澡等等。 &nbsp;...</small></li>
<li><a href='http://www.speedvi.net/2009/12/23/182.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &nbsp; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。...</small></li>
<li><a href='http://www.speedvi.net/2009/12/26/183.html' rel='bookmark' title='Permanent Link: 系统内核的角色'>系统内核的角色</a> <small>　　我们前面的使用房子做的比喻对于解释同步的概念是非常合适的，但对系统中另一个重要方面就不合适了。在我们的比喻中，多个线程是同时运行的。可是，在实际的系统中，一般只有一个处理器，那么在一个时间点上只能运行一件“事”。 单处理器情况 　　下面看看在实际系统中的真实运行情况，在较“经济”的情况下，一个系统中只有一个中央处理器。这时，在任何时间点就只能有一个线程可以运行。系统内核使用数种规则来决定让哪个线程运行并运行该线程。 多处理器情况 　　如果你购买的计算机有多个、相同的CPU，并且这些CPU共享内存与设备，那么就可以说你有了一个SMP系统了（SMP是对称多处理器的简称，指系统中的这些处理器是一样的）。这时，可以同时运行的线程的数量就受限于CPU的个数了。由于一个CPU一次只能运行一个线程，在有多个CPU的情况下，多个线程就可以同步运行。 　　我们可以忽略CPU的个数，而抽象的设计系统能够让多个线程同步运行，虽然实际情况并不是这样。 内核作为仲裁者 　　那么谁来决定在指定时间哪个线程运行呢？这就是内核的工作职责。 　　内核决定了在特定时刻可以使用CPU的线程，并切换环境变量到那个线程。下面详细说明一下内核对CPU做了什么。 　　CPU有一定数量的寄存器（寄存器的具体数量依赖于处理器的类型）。当线程运行的时候，线程的一些信息就保存在这些寄存器里面，例如当前程序位置等等。...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/01/09/185.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>内核状态</title>
		<link>http://www.speedvi.net/2009/12/27/184.html</link>
		<comments>http://www.speedvi.net/2009/12/27/184.html#comments</comments>
		<pubDate>Sat, 26 Dec 2009 16:46:35 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[系统内核]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[进程]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2009/12/27/184.html</guid>
		<description><![CDATA[RUNNING 　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。 READY 　　就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。 &#160; 阻塞状态(blocked states) 　　我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。 　　为什么会有这么多种呢？因为内核一直保留着线程为何被阻塞的原因。 　　在前面我们已经讲到了两种阻塞状态——当线程等待互斥体时被阻塞，这时线程就是MUTEX状态。当一个线程由于等待信号变量而被阻塞的话，这时线程就是SEM状态。这些状态简要的说明了线程是阻塞于哪个队列或资源。 　　如果有多个线程阻塞于某个互斥体（属于MUTEX阻塞状态），系统内核就不会特别留意它们，直到拥有该互斥体的线程释放了该互斥体。那时，这些阻塞线程之一就进入READY状态，系统内核在必要情况下完成调度决定。 　　为什么说必要情况下呢？如果释放互斥体的线程还有其他事要做并且相对于等待线程有更高的优先级，这时，我们就要遵守第二条规则了。“最高优先级的就绪线程运行”。也就是说，调度顺序没有改变，较高优先级的线程继续运行。 Related posts:进程间通信 　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递... 进程间通信的消息复制 　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU... 进程间通信的通道与连接 　　在UNIX系统中，消息传递是沿着通道与连接传送的，而不是直接的从线程到线程。一个线程要接收消息就先要创建一个通道，另外一个线程如果想要向这个线程传递消息就需要创建一个连接附加到该通道上。 　　通道是消息内核调用所必须的，服务器线程在调用MsgReceive()函数就是使用这个通道来接收消息的。连接由客户线程创建并用于“连接”服务器线程创建的可用通道。一旦连接确定，客户线程就可以使用MsgSend()函数发送消息了。如果一个进程中有多个线程连接到同一个通道，为了效率所有的连接就被映射到同一个内核对象。在一个进程中的通道与连接是使用一个小整数进行命名的。客户连接直接映射为文件描述符。 　　从架构上来说，这是一个关键点。通过将客户连接直接映射为文件描述符，就消除了使用另一层变换的必要。我们不必“指出”根据文件描述符向哪里发送消息。相反，我们可以很简单的将消息直接发送给“文件描述符”（也就是连接ID）。 　　这里可以使用的函数如下： 　　ChannelCreate() 创建一个通道来接收消息；　　ChannelDestroy() 清除一个通道；　　ConnectAttach() 创建一个连接来发送消息；　　ConnectDetach() 释放一个连接。 　　作为服务器端的进程可以使用事件循环来接收与处理消息，代码如下所示：...


Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/163.html' rel='bookmark' title='Permanent Link: 进程间通信的消息复制'>进程间通信的消息复制</a> <small>　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/170.html' rel='bookmark' title='Permanent Link: 进程间通信的通道与连接'>进程间通信的通道与连接</a> <small>　　在UNIX系统中，消息传递是沿着通道与连接传送的，而不是直接的从线程到线程。一个线程要接收消息就先要创建一个通道，另外一个线程如果想要向这个线程传递消息就需要创建一个连接附加到该通道上。 　　通道是消息内核调用所必须的，服务器线程在调用MsgReceive()函数就是使用这个通道来接收消息的。连接由客户线程创建并用于“连接”服务器线程创建的可用通道。一旦连接确定，客户线程就可以使用MsgSend()函数发送消息了。如果一个进程中有多个线程连接到同一个通道，为了效率所有的连接就被映射到同一个内核对象。在一个进程中的通道与连接是使用一个小整数进行命名的。客户连接直接映射为文件描述符。 　　从架构上来说，这是一个关键点。通过将客户连接直接映射为文件描述符，就消除了使用另一层变换的必要。我们不必“指出”根据文件描述符向哪里发送消息。相反，我们可以很简单的将消息直接发送给“文件描述符”（也就是连接ID）。 　　这里可以使用的函数如下： 　　ChannelCreate() 创建一个通道来接收消息；　　ChannelDestroy() 清除一个通道；　　ConnectAttach() 创建一个连接来发送消息；　　ConnectDetach() 释放一个连接。 　　作为服务器端的进程可以使用事件循环来接收与处理消息，代码如下所示：...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p><strong>RUNNING</strong></p>
<p>　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。</p>
<p><strong>READY</strong></p>
<p><strong>　　</strong>就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。</p>
<p><strong></strong>&nbsp;</p>
<p><span id="more-184"></span>
<p><strong>阻塞状态(blocked states)</strong></p>
<p><strong>　　</strong>我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。</p>
<p>　　为什么会有这么多种呢？因为内核一直保留着线程为何被阻塞的原因。</p>
<p>　　在前面我们已经讲到了两种阻塞状态——当线程等待互斥体时被阻塞，这时线程就是MUTEX状态。当一个线程由于等待信号变量而被阻塞的话，这时线程就是SEM状态。这些状态简要的说明了线程是阻塞于哪个队列或资源。</p>
<p>　　如果有多个线程阻塞于某个互斥体（属于MUTEX阻塞状态），系统内核就不会特别留意它们，直到拥有该互斥体的线程释放了该互斥体。那时，这些阻塞线程之一就进入READY状态，系统内核在必要情况下完成调度决定。</p>
<p>　　为什么说必要情况下呢？如果释放互斥体的线程还有其他事要做并且相对于等待线程有更高的优先级，这时，我们就要遵守第二条规则了。“最高优先级的就绪线程运行”。也就是说，调度顺序没有改变，较高优先级的线程继续运行。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/163.html' rel='bookmark' title='Permanent Link: 进程间通信的消息复制'>进程间通信的消息复制</a> <small>　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/170.html' rel='bookmark' title='Permanent Link: 进程间通信的通道与连接'>进程间通信的通道与连接</a> <small>　　在UNIX系统中，消息传递是沿着通道与连接传送的，而不是直接的从线程到线程。一个线程要接收消息就先要创建一个通道，另外一个线程如果想要向这个线程传递消息就需要创建一个连接附加到该通道上。 　　通道是消息内核调用所必须的，服务器线程在调用MsgReceive()函数就是使用这个通道来接收消息的。连接由客户线程创建并用于“连接”服务器线程创建的可用通道。一旦连接确定，客户线程就可以使用MsgSend()函数发送消息了。如果一个进程中有多个线程连接到同一个通道，为了效率所有的连接就被映射到同一个内核对象。在一个进程中的通道与连接是使用一个小整数进行命名的。客户连接直接映射为文件描述符。 　　从架构上来说，这是一个关键点。通过将客户连接直接映射为文件描述符，就消除了使用另一层变换的必要。我们不必“指出”根据文件描述符向哪里发送消息。相反，我们可以很简单的将消息直接发送给“文件描述符”（也就是连接ID）。 　　这里可以使用的函数如下： 　　ChannelCreate() 创建一个通道来接收消息；　　ChannelDestroy() 清除一个通道；　　ConnectAttach() 创建一个连接来发送消息；　　ConnectDetach() 释放一个连接。 　　作为服务器端的进程可以使用事件循环来接收与处理消息，代码如下所示：...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2009/12/27/184.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>系统内核的角色</title>
		<link>http://www.speedvi.net/2009/12/26/183.html</link>
		<comments>http://www.speedvi.net/2009/12/26/183.html#comments</comments>
		<pubDate>Sat, 26 Dec 2009 08:09:44 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[进程]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2009/12/26/183.html</guid>
		<description><![CDATA[　　我们前面的使用房子做的比喻对于解释同步的概念是非常合适的，但对系统中另一个重要方面就不合适了。在我们的比喻中，多个线程是同时运行的。可是，在实际的系统中，一般只有一个处理器，那么在一个时间点上只能运行一件“事”。 单处理器情况 　　下面看看在实际系统中的真实运行情况，在较“经济”的情况下，一个系统中只有一个中央处理器。这时，在任何时间点就只能有一个线程可以运行。系统内核使用数种规则来决定让哪个线程运行并运行该线程。 多处理器情况 　　如果你购买的计算机有多个、相同的CPU，并且这些CPU共享内存与设备，那么就可以说你有了一个SMP系统了（SMP是对称多处理器的简称，指系统中的这些处理器是一样的）。这时，可以同时运行的线程的数量就受限于CPU的个数了。由于一个CPU一次只能运行一个线程，在有多个CPU的情况下，多个线程就可以同步运行。 　　我们可以忽略CPU的个数，而抽象的设计系统能够让多个线程同步运行，虽然实际情况并不是这样。 内核作为仲裁者 　　那么谁来决定在指定时间哪个线程运行呢？这就是内核的工作职责。 　　内核决定了在特定时刻可以使用CPU的线程，并切换环境变量到那个线程。下面详细说明一下内核对CPU做了什么。 　　CPU有一定数量的寄存器（寄存器的具体数量依赖于处理器的类型）。当线程运行的时候，线程的一些信息就保存在这些寄存器里面，例如当前程序位置等等。 　　当内核确定要让另外一个线程运行时，它就会： 保存当前运行线程的寄存器以及环境变量信息； 将新线程的寄存器以及环境变量信息载入CPU。 　　不过内核是如何确定另外一个要运行的线程的呢？它先检查某个特定线程当前是否有能力使用CPU。以前我们讨论互斥体时，曾经提到过一个阻塞状态（当一个线程拥有互斥体，而另外一个线程想要获取它时，第二个线程就会进入阻塞状态）。 　　从内核的角度来看，一个线程正在使用CPU，而另外一个线程因为正在等待互斥体而进入阻塞状态而不能够使用CPU。这种情况下，内核就让可以运行的线程使用CPU，而将其他线程放入其内部列表中（从而内核就可以跟踪对于互斥体的请求）。 　　这显然不是一个有趣的情况。假设很多线程可以使用CPU。以前我们提到过线程对于互斥体的访问是基于优先级以及等待时间。内核也是使用一个类似的策略来决定哪个线程应该接下来跟着运行的。也就是说有两个因素：优先级和调度算法来确定这个线程的执行顺序。 优先级 　　假设两个线程有能力使用CPU。如果这两个线程的优先级不同，那么解决办法就非常简单——系统内核就将CPU交给优先级最高的那个线程。N系统的优先级从1（最低的可用优先级）向上计数。需要注意的是优先级0是保留给空闲线程的，用户是不能使用这个优先级的。 　　如果另外一个有更高优先级的线程突然能够使用CPU了，系统内核就会立即执行环境变量切换，将其切换为这个有较高优先级的线程。这就是我们经常说的抢占——有较高优先级的线程抢占较低优先级的线程。当较高优先级线程完成之后，系统内核就将环境变量切换回刚才运行的较低优先级线程，称这个为接续(resumption)，内核接续了前一个线程的运行。 　　如果现在的两个可以使用CPU的线程拥有同样的优先级，该如何处理呢？ 调度算法 　　假设这两个线程中的一个正在使用CPU。系统内核在这种情况下是按照什么规则执行环境变量切换的？系统内核所使用的两个主要的调度算法就是环形(Round Robin)和先入先出(FIFO)。 FIFO 　　在这种算法中，线程可以按自己的意愿，使用CPU任意长的时间。这就意味着，如果这个线程执行一个非常长的数学计算，并且没有较高优先级的线程抢占CPU，那么这个线程可能会永远的运行下去了。那么和它优先级相同的线程会如何呢？它们就被据于CPU的使用之外了。至于更低优先级的同样也没有运行的机会了。 　　如果运行线程退出或自动释放CPU，那么系统内核就查找能够使用CPU的同优先级的其他线程。如果找不到这样的线程，内核就会查找可以使用CPU的较低优先级的线程。自动释放CPU可能有两种情况，一个是线程进入睡眠状态，另一个就是线程因为信号量而阻塞等等，之后较低优先级的线程就可能运行了。除此之外，还有一个特别的调用函数sched_yield()，这个函数会让线程放弃CPU，让同优先级的其他线程使用——如果有较高优先级的线程已经就绪的话，较低优先级的线程就永远没有机会运行。如果一个线程调用了这个函数，可是没有同优先级的其他线程可以运行，那么原来这个线程就会继续运行。简单来说，sched_yield()函数就是让同优先级的线程有一个得到CPU并运行的机会。 环形（Round Robin） 　　RR调度算法与FIFO十分类似，不过有一点不同就是，如果有同优先级的线程正在等待运行，当前运行的线程就不会永远运行。它只会运行系统所设定的时间片（timeslice）那么长的时间。时间片的具体数值可以通过调用系统函数sched_rr_get_intercal()进行查询。通常时间片的具体值为4毫秒，实际上就是最小时间间隔的四倍，而最小时间间隔可以通过函数ClockPeriod()查询或设定。 　　具体实现是这样的，系统内核开始一个RR线程并留意其时间。如果这个RR线程运行了一会，并且分配给它的时间就要用完了（时间片就要失效了）。系统内核就会查找是否有同优先级的其他线程已经准备运行了。如果有的话，就运行那个线程。如果没有，系统内核就会继续运行这个RR线程（也就是为这个线程再分配了一个时间片）。 运行规则 　　对于单CPU的系统，线程的调度规则按重要性总结如下： 一次只能运行一个线程 较高优先级并已就绪的线程先开始运行 一个线程除了阻塞或退出之前就一直运行 RR线程会运行分配给它的时间片那么长的时间，之后内核会重新调度它 Related posts:进程间通信 　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递... 进程与线程 进程与线程基础 　　在谈论线程、进程、时间片以及更神奇的调度概念之前，先打个比方。 　　我们首先要说明的就是线程与进程是如何工作的。最形象的理解就是将线程与进程想象为现实世界的东西。我们的比方如下： 进程想象为一个房子 　　先使用我们在日常生活中常见的东西——房子来开始线程与进程的比方。 　　房子其实就是一个容器，并且有其自己的特定属性（比如有几层楼、多少个房间等等）。 [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/12/22/181.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>进程与线程基础 　　在谈论线程、进程、时间片以及更神奇的调度概念之前，先打个比方。 　　我们首先要说明的就是线程与进程是如何工作的。最形象的理解就是将线程与进程想象为现实世界的东西。我们的比方如下： 进程想象为一个房子 　　先使用我们在日常生活中常见的东西——房子来开始线程与进程的比方。 　　房子其实就是一个容器，并且有其自己的特定属性（比如有几层楼、多少个房间等等）。 　　在这个比方中，我们可以看到房子靠其自身是不能做任何事的——它是一个被动的东西。而进程也是这样的。 线程想象为房子的住户 　　在房子中居住的人才是主动的对象，他们才是房子的使用者，在这里看电视、做饭、洗澡等等。 &nbsp;...</small></li>
<li><a href='http://www.speedvi.net/2009/12/23/182.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &nbsp; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　我们前面的使用房子做的比喻对于解释同步的概念是非常合适的，但对系统中另一个重要方面就不合适了。在我们的比喻中，多个线程是同时运行的。可是，在实际的系统中，一般只有一个处理器，那么在一个时间点上只能运行一件“事”。</p>
<h5>单处理器情况</h5>
<p>　　下面看看在实际系统中的真实运行情况，在较“经济”的情况下，一个系统中只有一个中央处理器。这时，在任何时间点就只能有一个线程可以运行。系统内核使用数种规则来决定让哪个线程运行并运行该线程。</p>
<h5>多处理器情况</h5>
<p>　　如果你购买的计算机有多个、相同的CPU，并且这些CPU共享内存与设备，那么就可以说你有了一个SMP系统了（SMP是对称多处理器的简称，指系统中的这些处理器是一样的）。这时，可以同时运行的线程的数量就受限于CPU的个数了。由于一个CPU一次只能运行一个线程，在有多个CPU的情况下，多个线程就可以同步运行。</p>
</p>
<p><span id="more-183"></span>
<p>　　我们可以忽略CPU的个数，而抽象的设计系统能够让多个线程同步运行，虽然实际情况并不是这样。</p>
<h6>内核作为仲裁者</h6>
<p>　　那么谁来决定在指定时间哪个线程运行呢？这就是内核的工作职责。</p>
<p>　　内核决定了在特定时刻可以使用CPU的线程，并切换环境变量到那个线程。下面详细说明一下内核对CPU做了什么。</p>
<p>　　CPU有一定数量的寄存器（寄存器的具体数量依赖于处理器的类型）。当线程运行的时候，线程的一些信息就保存在这些寄存器里面，例如当前程序位置等等。</p>
<p>　　当内核确定要让另外一个线程运行时，它就会：</p>
<ol>
<li>保存当前运行线程的寄存器以及环境变量信息；
<li>将新线程的寄存器以及环境变量信息载入CPU。 </li>
</ol>
<p>　　不过内核是如何确定另外一个要运行的线程的呢？它先检查某个特定线程当前是否有能力使用CPU。以前我们讨论互斥体时，曾经提到过一个阻塞状态（当一个线程拥有互斥体，而另外一个线程想要获取它时，第二个线程就会进入阻塞状态）。</p>
<p>　　从内核的角度来看，一个线程正在使用CPU，而另外一个线程因为正在等待互斥体而进入阻塞状态而不能够使用CPU。这种情况下，内核就让可以运行的线程使用CPU，而将其他线程放入其内部列表中（从而内核就可以跟踪对于互斥体的请求）。</p>
<p>　　这显然不是一个有趣的情况。假设很多线程可以使用CPU。以前我们提到过线程对于互斥体的访问是基于优先级以及等待时间。内核也是使用一个类似的策略来决定哪个线程应该接下来跟着运行的。也就是说有两个因素：优先级和调度算法来确定这个线程的执行顺序。</p>
<h6>优先级</h6>
<p>　　假设两个线程有能力使用CPU。如果这两个线程的优先级不同，那么解决办法就非常简单——系统内核就将CPU交给优先级最高的那个线程。N系统的优先级从1（最低的可用优先级）向上计数。需要注意的是优先级0是保留给空闲线程的，用户是不能使用这个优先级的。</p>
<p>　　如果另外一个有更高优先级的线程突然能够使用CPU了，系统内核就会立即执行环境变量切换，将其切换为这个有较高优先级的线程。这就是我们经常说的抢占——有较高优先级的线程抢占较低优先级的线程。当较高优先级线程完成之后，系统内核就将环境变量切换回刚才运行的较低优先级线程，称这个为接续(resumption)，内核接续了前一个线程的运行。</p>
<p>　　如果现在的两个可以使用CPU的线程拥有同样的优先级，该如何处理呢？</p>
<h6>调度算法</h6>
<p>　　假设这两个线程中的一个正在使用CPU。系统内核在这种情况下是按照什么规则执行环境变量切换的？系统内核所使用的两个主要的调度算法就是环形(Round Robin)和先入先出(FIFO)。</p>
<p><strong>FIFO</strong></p>
<p><strong>　　</strong>在这种算法中，线程可以按自己的意愿，使用CPU任意长的时间。这就意味着，如果这个线程执行一个非常长的数学计算，并且没有较高优先级的线程抢占CPU，那么这个线程可能会永远的运行下去了。那么和它优先级相同的线程会如何呢？它们就被据于CPU的使用之外了。至于更低优先级的同样也没有运行的机会了。</p>
<p>　　如果运行线程退出或自动释放CPU，那么系统内核就查找能够使用CPU的同优先级的其他线程。如果找不到这样的线程，内核就会查找可以使用CPU的较低优先级的线程。自动释放CPU可能有两种情况，一个是线程进入睡眠状态，另一个就是线程因为信号量而阻塞等等，之后较低优先级的线程就可能运行了。除此之外，还有一个特别的调用函数sched_yield()，这个函数会让线程放弃CPU，让同优先级的其他线程使用——如果有较高优先级的线程已经就绪的话，较低优先级的线程就永远没有机会运行。如果一个线程调用了这个函数，可是没有同优先级的其他线程可以运行，那么原来这个线程就会继续运行。简单来说，sched_yield()函数就是让同优先级的线程有一个得到CPU并运行的机会。</p>
<p><strong>环形（Round Robin）</strong></p>
<p><strong>　　</strong>RR调度算法与FIFO十分类似，不过有一点不同就是，如果有同优先级的线程正在等待运行，当前运行的线程就不会永远运行。它只会运行系统所设定的时间片（timeslice）那么长的时间。时间片的具体数值可以通过调用系统函数sched_rr_get_intercal()进行查询。通常时间片的具体值为4毫秒，实际上就是最小时间间隔的四倍，而最小时间间隔可以通过函数ClockPeriod()查询或设定。</p>
<p>　　具体实现是这样的，系统内核开始一个RR线程并留意其时间。如果这个RR线程运行了一会，并且分配给它的时间就要用完了（时间片就要失效了）。系统内核就会查找是否有同优先级的其他线程已经准备运行了。如果有的话，就运行那个线程。如果没有，系统内核就会继续运行这个RR线程（也就是为这个线程再分配了一个时间片）。</p>
<p><strong>运行规则</strong></p>
<p>　　对于单CPU的系统，线程的调度规则按重要性总结如下：</p>
<ul>
<li>一次只能运行一个线程</li>
<li>较高优先级并已就绪的线程先开始运行</li>
<li>一个线程除了阻塞或退出之前就一直运行</li>
<li>RR线程会运行分配给它的时间片那么长的时间，之后内核会重新调度它</li>
</ul>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/12/22/181.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>进程与线程基础 　　在谈论线程、进程、时间片以及更神奇的调度概念之前，先打个比方。 　　我们首先要说明的就是线程与进程是如何工作的。最形象的理解就是将线程与进程想象为现实世界的东西。我们的比方如下： 进程想象为一个房子 　　先使用我们在日常生活中常见的东西——房子来开始线程与进程的比方。 　　房子其实就是一个容器，并且有其自己的特定属性（比如有几层楼、多少个房间等等）。 　　在这个比方中，我们可以看到房子靠其自身是不能做任何事的——它是一个被动的东西。而进程也是这样的。 线程想象为房子的住户 　　在房子中居住的人才是主动的对象，他们才是房子的使用者，在这里看电视、做饭、洗澡等等。 &nbsp;...</small></li>
<li><a href='http://www.speedvi.net/2009/12/23/182.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &nbsp; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2009/12/26/183.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>进程与线程</title>
		<link>http://www.speedvi.net/2009/12/23/182.html</link>
		<comments>http://www.speedvi.net/2009/12/23/182.html#comments</comments>
		<pubDate>Wed, 23 Dec 2009 07:41:23 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[信号变量]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[进程]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2009/12/23/182.html</guid>
		<description><![CDATA[信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &#160; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。 　　我们举厨房的例子是要说明另一类不同的锁。 计数大于1的信号变量 　　假设我们在厨房安装了传统的需要钥匙开启的锁。这种锁的工作方式如下，如果你有一把对应的钥匙，你就可以打开这把锁并进入厨房里面。使用这个锁的所有人都同意当他们进入厨房之后就立刻从内部锁上这个门，这样外面的人必须在有钥匙的情况下才能进入厨房。 　　于是，控制厨房中的人数就变得很简单了——在门外挂两把钥匙，并且厨房永远是锁住的。如果有人想进入厨房的话，就先看看门外是否有一把挂着的钥匙。如果有的话，他们就可以拿到那把钥匙，打开厨房的门，进入厨房并使用这把钥匙锁门。 　　由于进入厨房的人必须带着他们的钥匙，我们就能够通过控制厨房门外的钥匙的数量，来控制在厨房中允许进入的人数。 　　对于线程来说，完成这个功能的就是信号变量（semaphore）。简单的信号变量的工作方式和互斥体是几乎一样的——你要么拥有互斥体从而可以访问资源，要么就不拥有互斥体从而不能访问资源。我们刚刚描述的信号变量是计数信号变量（counting semaphore）——它一直保持计数（就是线程可用的钥匙的数量）。 信号变量作为互斥体使用 　　我们刚刚问了个问题，就是能否使用互斥体来实现有计数功能的锁。对这个问题的答案是不能。是否还有其他的方式来完成该目的？另外能否将信号变量作为互斥体使用？ 　　是的。在一些操作系统中，它们实际上就是这样做的——它们没有互斥体，只有信号变量！也就没有让互斥体造成困扰的必要了。 　　要回答这个问题，请想一下你的房子的洗手间。盖房子的人如何来实现“互斥体”？我想你应该没有一把钥匙挂在墙上吧。 　　互斥体是一种特定用途的信号变量。如果你想让一个线程运行特定段代码，互斥体就是目前为止最有效的实现方式。 　　后面要提到的其他同步策略，包括了叫做条件变量、屏障以及睡眠量的东西。 Related posts:进程与线程 进程与线程基础 　　在谈论线程、进程、时间片以及更神奇的调度概念之前，先打个比方。 　　我们首先要说明的就是线程与进程是如何工作的。最形象的理解就是将线程与进程想象为现实世界的东西。我们的比方如下： 进程想象为一个房子 　　先使用我们在日常生活中常见的东西——房子来开始线程与进程的比方。 　　房子其实就是一个容器，并且有其自己的特定属性（比如有几层楼、多少个房间等等）。 　　在这个比方中，我们可以看到房子靠其自身是不能做任何事的——它是一个被动的东西。而进程也是这样的。 线程想象为房子的住户 　　在房子中居住的人才是主动的对象，他们才是房子的使用者，在这里看电视、做饭、洗澡等等。 &#160;... 系统内核的角色 　　我们前面的使用房子做的比喻对于解释同步的概念是非常合适的，但对系统中另一个重要方面就不合适了。在我们的比喻中，多个线程是同时运行的。可是，在实际的系统中，一般只有一个处理器，那么在一个时间点上只能运行一件“事”。 单处理器情况 　　下面看看在实际系统中的真实运行情况，在较“经济”的情况下，一个系统中只有一个中央处理器。这时，在任何时间点就只能有一个线程可以运行。系统内核使用数种规则来决定让哪个线程运行并运行该线程。 多处理器情况 　　如果你购买的计算机有多个、相同的CPU，并且这些CPU共享内存与设备，那么就可以说你有了一个SMP系统了（SMP是对称多处理器的简称，指系统中的这些处理器是一样的）。这时，可以同时运行的线程的数量就受限于CPU的个数了。由于一个CPU一次只能运行一个线程，在有多个CPU的情况下，多个线程就可以同步运行。 　　我们可以忽略CPU的个数，而抽象的设计系统能够让多个线程同步运行，虽然实际情况并不是这样。 内核作为仲裁者 　　那么谁来决定在指定时间哪个线程运行呢？这就是内核的工作职责。 　　内核决定了在特定时刻可以使用CPU的线程，并切换环境变量到那个线程。下面详细说明一下内核对CPU做了什么。 　　CPU有一定数量的寄存器（寄存器的具体数量依赖于处理器的类型）。当线程运行的时候，线程的一些信息就保存在这些寄存器里面，例如当前程序位置等等。... 内核状态 RUNNING 　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。 READY 　　就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。 &#160; 阻塞状态(blocked states) 　　我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。 [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2009/12/22/181.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>进程与线程基础 　　在谈论线程、进程、时间片以及更神奇的调度概念之前，先打个比方。 　　我们首先要说明的就是线程与进程是如何工作的。最形象的理解就是将线程与进程想象为现实世界的东西。我们的比方如下： 进程想象为一个房子 　　先使用我们在日常生活中常见的东西——房子来开始线程与进程的比方。 　　房子其实就是一个容器，并且有其自己的特定属性（比如有几层楼、多少个房间等等）。 　　在这个比方中，我们可以看到房子靠其自身是不能做任何事的——它是一个被动的东西。而进程也是这样的。 线程想象为房子的住户 　　在房子中居住的人才是主动的对象，他们才是房子的使用者，在这里看电视、做饭、洗澡等等。 &nbsp;...</small></li>
<li><a href='http://www.speedvi.net/2009/12/26/183.html' rel='bookmark' title='Permanent Link: 系统内核的角色'>系统内核的角色</a> <small>　　我们前面的使用房子做的比喻对于解释同步的概念是非常合适的，但对系统中另一个重要方面就不合适了。在我们的比喻中，多个线程是同时运行的。可是，在实际的系统中，一般只有一个处理器，那么在一个时间点上只能运行一件“事”。 单处理器情况 　　下面看看在实际系统中的真实运行情况，在较“经济”的情况下，一个系统中只有一个中央处理器。这时，在任何时间点就只能有一个线程可以运行。系统内核使用数种规则来决定让哪个线程运行并运行该线程。 多处理器情况 　　如果你购买的计算机有多个、相同的CPU，并且这些CPU共享内存与设备，那么就可以说你有了一个SMP系统了（SMP是对称多处理器的简称，指系统中的这些处理器是一样的）。这时，可以同时运行的线程的数量就受限于CPU的个数了。由于一个CPU一次只能运行一个线程，在有多个CPU的情况下，多个线程就可以同步运行。 　　我们可以忽略CPU的个数，而抽象的设计系统能够让多个线程同步运行，虽然实际情况并不是这样。 内核作为仲裁者 　　那么谁来决定在指定时间哪个线程运行呢？这就是内核的工作职责。 　　内核决定了在特定时刻可以使用CPU的线程，并切换环境变量到那个线程。下面详细说明一下内核对CPU做了什么。 　　CPU有一定数量的寄存器（寄存器的具体数量依赖于处理器的类型）。当线程运行的时候，线程的一些信息就保存在这些寄存器里面，例如当前程序位置等等。...</small></li>
<li><a href='http://www.speedvi.net/2009/12/27/184.html' rel='bookmark' title='Permanent Link: 内核状态'>内核状态</a> <small>RUNNING 　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。 READY 　　就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。 &nbsp; 阻塞状态(blocked states) 　　我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。 　　为什么会有这么多种呢？因为内核一直保留着线程为何被阻塞的原因。 　　在前面我们已经讲到了两种阻塞状态——当线程等待互斥体时被阻塞，这时线程就是MUTEX状态。当一个线程由于等待信号变量而被阻塞的话，这时线程就是SEM状态。这些状态简要的说明了线程是阻塞于哪个队列或资源。...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<h5>信号变量(Semaphores)</h5>
<p>　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。</p>
<p>　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。</p>
<h6>计数为1的信号变量</h6>
<p>　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的：</p>
<ul>
<li>门是开的并且无人在房间内</li>
<li>门被锁住并且有一个人在房间内</li>
</ul>
<p>&nbsp;</p>
<p><span id="more-182"></span>
<p>　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。</p>
<p>　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。</p>
<p>　　我们举厨房的例子是要说明另一类不同的锁。</p>
<h6>计数大于1的信号变量</h6>
<p>　　假设我们在厨房安装了传统的需要钥匙开启的锁。这种锁的工作方式如下，如果你有一把对应的钥匙，你就可以打开这把锁并进入厨房里面。使用这个锁的所有人都同意当他们进入厨房之后就立刻从内部锁上这个门，这样外面的人必须在有钥匙的情况下才能进入厨房。</p>
<p>　　于是，控制厨房中的人数就变得很简单了——在门外挂两把钥匙，并且厨房永远是锁住的。如果有人想进入厨房的话，就先看看门外是否有一把挂着的钥匙。如果有的话，他们就可以拿到那把钥匙，打开厨房的门，进入厨房并使用这把钥匙锁门。</p>
<p>　　由于进入厨房的人必须带着他们的钥匙，我们就能够通过控制厨房门外的钥匙的数量，来控制在厨房中允许进入的人数。</p>
<p>　　对于线程来说，完成这个功能的就是信号变量（semaphore）。简单的信号变量的工作方式和互斥体是几乎一样的——你要么拥有互斥体从而可以访问资源，要么就不拥有互斥体从而不能访问资源。我们刚刚描述的信号变量是计数信号变量（counting semaphore）——它一直保持计数（就是线程可用的钥匙的数量）。</p>
<h5>信号变量作为互斥体使用</h5>
<p>　　我们刚刚问了个问题，就是能否使用互斥体来实现有计数功能的锁。对这个问题的答案是不能。是否还有其他的方式来完成该目的？另外能否将信号变量作为互斥体使用？</p>
<p>　　是的。在一些操作系统中，它们实际上就是这样做的——它们没有互斥体，只有信号变量！也就没有让互斥体造成困扰的必要了。</p>
<p>　　要回答这个问题，请想一下你的房子的洗手间。盖房子的人如何来实现“互斥体”？我想你应该没有一把钥匙挂在墙上吧。</p>
<p>　　互斥体是一种特定用途的信号变量。如果你想让一个线程运行特定段代码，互斥体就是目前为止最有效的实现方式。</p>
<p>　　后面要提到的其他同步策略，包括了叫做条件变量、屏障以及睡眠量的东西。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2009/12/22/181.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>进程与线程基础 　　在谈论线程、进程、时间片以及更神奇的调度概念之前，先打个比方。 　　我们首先要说明的就是线程与进程是如何工作的。最形象的理解就是将线程与进程想象为现实世界的东西。我们的比方如下： 进程想象为一个房子 　　先使用我们在日常生活中常见的东西——房子来开始线程与进程的比方。 　　房子其实就是一个容器，并且有其自己的特定属性（比如有几层楼、多少个房间等等）。 　　在这个比方中，我们可以看到房子靠其自身是不能做任何事的——它是一个被动的东西。而进程也是这样的。 线程想象为房子的住户 　　在房子中居住的人才是主动的对象，他们才是房子的使用者，在这里看电视、做饭、洗澡等等。 &nbsp;...</small></li>
<li><a href='http://www.speedvi.net/2009/12/26/183.html' rel='bookmark' title='Permanent Link: 系统内核的角色'>系统内核的角色</a> <small>　　我们前面的使用房子做的比喻对于解释同步的概念是非常合适的，但对系统中另一个重要方面就不合适了。在我们的比喻中，多个线程是同时运行的。可是，在实际的系统中，一般只有一个处理器，那么在一个时间点上只能运行一件“事”。 单处理器情况 　　下面看看在实际系统中的真实运行情况，在较“经济”的情况下，一个系统中只有一个中央处理器。这时，在任何时间点就只能有一个线程可以运行。系统内核使用数种规则来决定让哪个线程运行并运行该线程。 多处理器情况 　　如果你购买的计算机有多个、相同的CPU，并且这些CPU共享内存与设备，那么就可以说你有了一个SMP系统了（SMP是对称多处理器的简称，指系统中的这些处理器是一样的）。这时，可以同时运行的线程的数量就受限于CPU的个数了。由于一个CPU一次只能运行一个线程，在有多个CPU的情况下，多个线程就可以同步运行。 　　我们可以忽略CPU的个数，而抽象的设计系统能够让多个线程同步运行，虽然实际情况并不是这样。 内核作为仲裁者 　　那么谁来决定在指定时间哪个线程运行呢？这就是内核的工作职责。 　　内核决定了在特定时刻可以使用CPU的线程，并切换环境变量到那个线程。下面详细说明一下内核对CPU做了什么。 　　CPU有一定数量的寄存器（寄存器的具体数量依赖于处理器的类型）。当线程运行的时候，线程的一些信息就保存在这些寄存器里面，例如当前程序位置等等。...</small></li>
<li><a href='http://www.speedvi.net/2009/12/27/184.html' rel='bookmark' title='Permanent Link: 内核状态'>内核状态</a> <small>RUNNING 　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。 READY 　　就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。 &nbsp; 阻塞状态(blocked states) 　　我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。 　　为什么会有这么多种呢？因为内核一直保留着线程为何被阻塞的原因。 　　在前面我们已经讲到了两种阻塞状态——当线程等待互斥体时被阻塞，这时线程就是MUTEX状态。当一个线程由于等待信号变量而被阻塞的话，这时线程就是SEM状态。这些状态简要的说明了线程是阻塞于哪个队列或资源。...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2009/12/23/182.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>进程与线程</title>
		<link>http://www.speedvi.net/2009/12/22/181.html</link>
		<comments>http://www.speedvi.net/2009/12/22/181.html#comments</comments>
		<pubDate>Tue, 22 Dec 2009 08:19:46 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[进程]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2009/12/22/181.html</guid>
		<description><![CDATA[进程与线程基础 　　在谈论线程、进程、时间片以及更神奇的调度概念之前，先打个比方。 　　我们首先要说明的就是线程与进程是如何工作的。最形象的理解就是将线程与进程想象为现实世界的东西。我们的比方如下： 进程想象为一个房子 　　先使用我们在日常生活中常见的东西——房子来开始线程与进程的比方。 　　房子其实就是一个容器，并且有其自己的特定属性（比如有几层楼、多少个房间等等）。 　　在这个比方中，我们可以看到房子靠其自身是不能做任何事的——它是一个被动的东西。而进程也是这样的。 线程想象为房子的住户 　　在房子中居住的人才是主动的对象，他们才是房子的使用者，在这里看电视、做饭、洗澡等等。 &#160; 单线程情况 　　如果你是自己一个人住在房子里，你就知道可能的情况是这样的——你可以在任何时间在你的房子里面做任何事，因为这个房子里面除了你没有别人。如果你想听广播、洗澡或是吃午餐，不论是什么，只要去做就行了！ 多线程情况 　　当有其他人开始在房中居住后，所有的这一切将有戏剧性的变化。比如你结婚了，你的配偶也会住在你的房子里面。这时你就不能在任何时间点都能直接冲进卫生间了，你先要检查你的配偶没有使用卫生间之后才能进去使用。 　　如果两个负责的成年人住在一个房子里面，你对安全就可以稍微马虎些。因为你知道另一个成年人会尊重你的空间，并且也不会让厨房起火等等。 　　这时放几个孩子进来，所有的境况就会开始变得有趣起来。 继续谈论进程与线程 　　就像房子占用了不动产的一个区域一样，一个进程占用了内存。同样，房子中的住户可以进入房子中的任何房间，进程中的线程也可以访问进程所占用的全部内存。如果一个线程分配了什么（妈妈出去买了一个游戏），那么其他的线程立刻就可以访问这个分配的东西（因为这个东西位于通用地址空间——因为这个游戏在家里）。同样的，如果进程分配了内存，全部线程就可以访问这个新内存。这里需要考虑的是这个新内存是否应该让进程中的全部线程可以访问。如果是的话，你就得让全部线程同步它们对内存的访问权。如果不是，架设只让特定的线程可以访问它。这时只有特定的线程可以访问它，就没有同步的必要了。 　　就像我们的日常生活一样，什么事都不是那么简单的。现在我们了解了一个基本特性（所有东西是共享的），现在看看更有趣的情况及其缘由。 互斥现象（Mutual Exclusion） 　　如果在房子中你想洗个澡，不过这是已经有人在使用卫生间了，你得先等等。对于线程来说是如何处理这种情况的呢？ 　　线程是使用叫做互斥（mutual exclusion）的东西来处理这种情况的。它和你想象的十分相似，当一些线程同时访问特定资源时就会互斥。 　　如果你正在洗澡，你就想要独占卫生间。为了完成这个，你一般是进入卫生间之后在内部反锁卫生间的门。想要使用卫生间的其他人就会因为锁而阻挡在卫生间之外。当你完事后，你就会打开这个锁，其他人就可以使用卫生间了。 　　其实线程也是这样做的。它使用叫做互斥体(mutex)的对象。这个对象就像门上的锁——一旦一个线程将其锁上，其他线程就无法访问这个对象，直到现在的拥有线程释放它（或称解锁）。就像门锁一样，想要获得互斥体的线程将被阻挡。 　　在互斥体与门锁之间相同的特点还有一个，就是它们都是“建议性”的锁。如果一个线程不遵守互斥体使用的准则，这个保护机制就一点用也没有。在我们房子的比喻中，就像有人忽略了门锁的规则直接破墙而入一样。 优先级（Priorities） 　　假设现在卫生间正被反锁并且有很多人正在等着使用，这时该怎么办？显然这时这些人都在卫生间外面坐着等卫生间里面的人出来。这时的问题就是，如果门开了，谁先进去呢？ 　　你可能想让等待时间最长的人先用是“公平”的、或让最年长的先用是“公平”的、或最高的、或最重要的等等是“公平”的。实际上是有好多种方法来判定什么是“公平”的。 　　在系统中，我们是使用两个参数来解决线程的这个问题的，这两个参数是：优先级以及等待时间的长短。 　　假如两个人同时出现在锁着门的卫生间前面。其中一个人有一个急迫的最后期限（比如已经晚了他要参加的会议），而另外一个人没有这个期限。是不是就应该让这个有最后期限的人先使用卫生间呢？是的，应该是这样的。这里唯一的问题是你如何确定谁更“重要”呢？这是可以通过分配优先级来实现的。那个有最后期限的人被分配了一个较高的优先级，而没有期限的被分配了较低的优先级。 　　对于线程我们可以做同样的事。一个线程会从其母线程继承其调度算法，但是我们可通过调用pthread_setschedparam()函数来修改其调度策略以及优先级（在有相应权限的时候才能这样做）。 　　如果很多线程都在等待，并且互斥体已经解锁，我们就会将互斥体交给等待线程中优先级最高的线程。那么假如两人的优先级相同，我们该怎么做呢？在这种情况下，让等待时间最长的人先进是“公平”的。其实在系统内核中，也是这样针对线程来实现的。在很多线程等待的时候，首先是根据优先级，其次再根据等待时间来选择线程的。 　　互斥体只是我们要遇到的同步对象的一种，后面还会介绍其他的同步对象。 Related posts:进程与线程 信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &#160; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。... 系统内核的角色 　　我们前面的使用房子做的比喻对于解释同步的概念是非常合适的，但对系统中另一个重要方面就不合适了。在我们的比喻中，多个线程是同时运行的。可是，在实际的系统中，一般只有一个处理器，那么在一个时间点上只能运行一件“事”。 单处理器情况 　　下面看看在实际系统中的真实运行情况，在较“经济”的情况下，一个系统中只有一个中央处理器。这时，在任何时间点就只能有一个线程可以运行。系统内核使用数种规则来决定让哪个线程运行并运行该线程。 多处理器情况 　　如果你购买的计算机有多个、相同的CPU，并且这些CPU共享内存与设备，那么就可以说你有了一个SMP系统了（SMP是对称多处理器的简称，指系统中的这些处理器是一样的）。这时，可以同时运行的线程的数量就受限于CPU的个数了。由于一个CPU一次只能运行一个线程，在有多个CPU的情况下，多个线程就可以同步运行。 　　我们可以忽略CPU的个数，而抽象的设计系统能够让多个线程同步运行，虽然实际情况并不是这样。 内核作为仲裁者 [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2009/12/23/182.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &nbsp; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。...</small></li>
<li><a href='http://www.speedvi.net/2009/12/26/183.html' rel='bookmark' title='Permanent Link: 系统内核的角色'>系统内核的角色</a> <small>　　我们前面的使用房子做的比喻对于解释同步的概念是非常合适的，但对系统中另一个重要方面就不合适了。在我们的比喻中，多个线程是同时运行的。可是，在实际的系统中，一般只有一个处理器，那么在一个时间点上只能运行一件“事”。 单处理器情况 　　下面看看在实际系统中的真实运行情况，在较“经济”的情况下，一个系统中只有一个中央处理器。这时，在任何时间点就只能有一个线程可以运行。系统内核使用数种规则来决定让哪个线程运行并运行该线程。 多处理器情况 　　如果你购买的计算机有多个、相同的CPU，并且这些CPU共享内存与设备，那么就可以说你有了一个SMP系统了（SMP是对称多处理器的简称，指系统中的这些处理器是一样的）。这时，可以同时运行的线程的数量就受限于CPU的个数了。由于一个CPU一次只能运行一个线程，在有多个CPU的情况下，多个线程就可以同步运行。 　　我们可以忽略CPU的个数，而抽象的设计系统能够让多个线程同步运行，虽然实际情况并不是这样。 内核作为仲裁者 　　那么谁来决定在指定时间哪个线程运行呢？这就是内核的工作职责。 　　内核决定了在特定时刻可以使用CPU的线程，并切换环境变量到那个线程。下面详细说明一下内核对CPU做了什么。 　　CPU有一定数量的寄存器（寄存器的具体数量依赖于处理器的类型）。当线程运行的时候，线程的一些信息就保存在这些寄存器里面，例如当前程序位置等等。...</small></li>
<li><a href='http://www.speedvi.net/2009/12/27/184.html' rel='bookmark' title='Permanent Link: 内核状态'>内核状态</a> <small>RUNNING 　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。 READY 　　就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。 &nbsp; 阻塞状态(blocked states) 　　我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。 　　为什么会有这么多种呢？因为内核一直保留着线程为何被阻塞的原因。 　　在前面我们已经讲到了两种阻塞状态——当线程等待互斥体时被阻塞，这时线程就是MUTEX状态。当一个线程由于等待信号变量而被阻塞的话，这时线程就是SEM状态。这些状态简要的说明了线程是阻塞于哪个队列或资源。...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<h5>进程与线程基础</h5>
<p>　　在谈论线程、进程、时间片以及更神奇的调度概念之前，先打个比方。</p>
<p>　　我们首先要说明的就是线程与进程是如何工作的。最形象的理解就是将线程与进程想象为现实世界的东西。我们的比方如下：</p>
<h6>进程想象为一个房子</h6>
<p>　　先使用我们在日常生活中常见的东西——房子来开始线程与进程的比方。</p>
<p>　　房子其实就是一个容器，并且有其自己的特定属性（比如有几层楼、多少个房间等等）。</p>
<p>　　在这个比方中，我们可以看到房子靠其自身是不能做任何事的——它是一个被动的东西。而进程也是这样的。</p>
<h6>线程想象为房子的住户</h6>
<p>　　在房子中居住的人才是主动的对象，他们才是房子的使用者，在这里看电视、做饭、洗澡等等。</p>
<p><strong></strong>&nbsp;</p>
<p><span id="more-181"></span>
<p><strong>单线程情况</strong></p>
<p><strong>　　</strong>如果你是自己一个人住在房子里，你就知道可能的情况是这样的——你可以在任何时间在你的房子里面做任何事，因为这个房子里面除了你没有别人。如果你想听广播、洗澡或是吃午餐，不论是什么，只要去做就行了！</p>
<p><strong>多线程情况</strong></p>
<p>　　当有其他人开始在房中居住后，所有的这一切将有戏剧性的变化。比如你结婚了，你的配偶也会住在你的房子里面。这时你就不能在任何时间点都能直接冲进卫生间了，你先要检查你的配偶没有使用卫生间之后才能进去使用。</p>
<p>　　如果两个负责的成年人住在一个房子里面，你对安全就可以稍微马虎些。因为你知道另一个成年人会尊重你的空间，并且也不会让厨房起火等等。</p>
<p>　　这时放几个孩子进来，所有的境况就会开始变得有趣起来。</p>
<h6>继续谈论进程与线程</h6>
<p>　　就像房子占用了不动产的一个区域一样，一个进程占用了内存。同样，房子中的住户可以进入房子中的任何房间，进程中的线程也可以访问进程所占用的全部内存。如果一个线程分配了什么（妈妈出去买了一个游戏），那么其他的线程立刻就可以访问这个分配的东西（因为这个东西位于通用地址空间——因为这个游戏在家里）。同样的，如果进程分配了内存，全部线程就可以访问这个新内存。这里需要考虑的是这个新内存是否应该让进程中的全部线程可以访问。如果是的话，你就得让全部线程同步它们对内存的访问权。如果不是，架设只让特定的线程可以访问它。这时只有特定的线程可以访问它，就没有同步的必要了。</p>
<p>　　就像我们的日常生活一样，什么事都不是那么简单的。现在我们了解了一个基本特性（所有东西是共享的），现在看看更有趣的情况及其缘由。</p>
<h6>互斥现象（Mutual Exclusion）</h6>
<p>　　如果在房子中你想洗个澡，不过这是已经有人在使用卫生间了，你得先等等。对于线程来说是如何处理这种情况的呢？</p>
<p>　　线程是使用叫做<em>互斥（mutual exclusion）</em>的东西来处理这种情况的。它和你想象的十分相似，当一些线程同时访问特定资源时就会互斥。</p>
<p>　　如果你正在洗澡，你就想要独占卫生间。为了完成这个，你一般是进入卫生间之后在内部反锁卫生间的门。想要使用卫生间的其他人就会因为锁而阻挡在卫生间之外。当你完事后，你就会打开这个锁，其他人就可以使用卫生间了。</p>
<p>　　其实线程也是这样做的。它使用叫做互斥体(mutex)的对象。这个对象就像门上的锁——一旦一个线程将其锁上，其他线程就无法访问这个对象，直到现在的拥有线程释放它（或称解锁）。就像门锁一样，想要获得互斥体的线程将被阻挡。</p>
<p>　　在互斥体与门锁之间相同的特点还有一个，就是它们都是“建议性”的锁。如果一个线程不遵守互斥体使用的准则，这个保护机制就一点用也没有。在我们房子的比喻中，就像有人忽略了门锁的规则直接破墙而入一样。</p>
<h6>优先级（Priorities）</h6>
<p>　　假设现在卫生间正被反锁并且有很多人正在等着使用，这时该怎么办？显然这时这些人都在卫生间外面坐着等卫生间里面的人出来。这时的问题就是，如果门开了，谁先进去呢？</p>
<p>　　你可能想让等待时间最长的人先用是“公平”的、或让最年长的先用是“公平”的、或最高的、或最重要的等等是“公平”的。实际上是有好多种方法来判定什么是“公平”的。</p>
<p>　　在系统中，我们是使用两个参数来解决线程的这个问题的，这两个参数是：优先级以及等待时间的长短。</p>
<p>　　假如两个人同时出现在锁着门的卫生间前面。其中一个人有一个急迫的最后期限（比如已经晚了他要参加的会议），而另外一个人没有这个期限。是不是就应该让这个有最后期限的人先使用卫生间呢？是的，应该是这样的。这里唯一的问题是你如何确定谁更“重要”呢？这是可以通过分配优先级来实现的。那个有最后期限的人被分配了一个较高的优先级，而没有期限的被分配了较低的优先级。</p>
<p>　　对于线程我们可以做同样的事。一个线程会从其母线程继承其调度算法，但是我们可通过调用pthread_setschedparam()函数来修改其调度策略以及优先级（在有相应权限的时候才能这样做）。</p>
<p>　　如果很多线程都在等待，并且互斥体已经解锁，我们就会将互斥体交给等待线程中优先级最高的线程。那么假如两人的优先级相同，我们该怎么做呢？在这种情况下，让等待时间最长的人先进是“公平”的。其实在系统内核中，也是这样针对线程来实现的。在很多线程等待的时候，首先是根据优先级，其次再根据等待时间来选择线程的。</p>
<p>　　互斥体只是我们要遇到的同步对象的一种，后面还会介绍其他的同步对象。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2009/12/23/182.html' rel='bookmark' title='Permanent Link: 进程与线程'>进程与线程</a> <small>信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &nbsp; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。...</small></li>
<li><a href='http://www.speedvi.net/2009/12/26/183.html' rel='bookmark' title='Permanent Link: 系统内核的角色'>系统内核的角色</a> <small>　　我们前面的使用房子做的比喻对于解释同步的概念是非常合适的，但对系统中另一个重要方面就不合适了。在我们的比喻中，多个线程是同时运行的。可是，在实际的系统中，一般只有一个处理器，那么在一个时间点上只能运行一件“事”。 单处理器情况 　　下面看看在实际系统中的真实运行情况，在较“经济”的情况下，一个系统中只有一个中央处理器。这时，在任何时间点就只能有一个线程可以运行。系统内核使用数种规则来决定让哪个线程运行并运行该线程。 多处理器情况 　　如果你购买的计算机有多个、相同的CPU，并且这些CPU共享内存与设备，那么就可以说你有了一个SMP系统了（SMP是对称多处理器的简称，指系统中的这些处理器是一样的）。这时，可以同时运行的线程的数量就受限于CPU的个数了。由于一个CPU一次只能运行一个线程，在有多个CPU的情况下，多个线程就可以同步运行。 　　我们可以忽略CPU的个数，而抽象的设计系统能够让多个线程同步运行，虽然实际情况并不是这样。 内核作为仲裁者 　　那么谁来决定在指定时间哪个线程运行呢？这就是内核的工作职责。 　　内核决定了在特定时刻可以使用CPU的线程，并切换环境变量到那个线程。下面详细说明一下内核对CPU做了什么。 　　CPU有一定数量的寄存器（寄存器的具体数量依赖于处理器的类型）。当线程运行的时候，线程的一些信息就保存在这些寄存器里面，例如当前程序位置等等。...</small></li>
<li><a href='http://www.speedvi.net/2009/12/27/184.html' rel='bookmark' title='Permanent Link: 内核状态'>内核状态</a> <small>RUNNING 　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。 READY 　　就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。 &nbsp; 阻塞状态(blocked states) 　　我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。 　　为什么会有这么多种呢？因为内核一直保留着线程为何被阻塞的原因。 　　在前面我们已经讲到了两种阻塞状态——当线程等待互斥体时被阻塞，这时线程就是MUTEX状态。当一个线程由于等待信号变量而被阻塞的话，这时线程就是SEM状态。这些状态简要的说明了线程是阻塞于哪个队列或资源。...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2009/12/22/181.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>进程间通信的共享内存</title>
		<link>http://www.speedvi.net/2009/09/28/177.html</link>
		<comments>http://www.speedvi.net/2009/09/28/177.html#comments</comments>
		<pubDate>Mon, 28 Sep 2009 09:03:24 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[共享内存]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[进程]]></category>
		<category><![CDATA[通信]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/?p=177</guid>
		<description><![CDATA[　　共享内存提供了进程间通信所能实现的最高带宽。一个共享内存对象创建之后，可以访问这个对象的进程就能够使用指针直接对其读写。这就意味着，共享内存访问本身就是非同步的。如果一个进程更新共享内存的一个区域，就必须特别小心不要让其他进程读取或更新同一块区域。即使是最简单的读取操作时，其他进程仍然有可能读到变化与不稳定的数据。 　　为了解决这个问题，共享内存就常与其他同步原(synchronization primitives)结合在一起使用以使进程之间的内存更新原子化。如果更新的间距很小，同步原就会限制自己固有的使用共享内存的高带宽。共享内存用于以块的模式更新大量的数据是最有效的。 　　信号量(semaphores)与互斥体(mutexes)都是适用与共享内存结合使用的同步原。信号量是在创建进程间同步的POSIX实时标准时引入的。互斥体则是在创建线程同步的POSIX标准是引入的。互斥体也可以在不同进程中的线程之间使用。POSIX将其作为一个可选的能力，我们在这里则是支持的。一般来说，互斥体要比信号量效率更高。 用于消息传递的共享内存 　　共享内存与消息传递可以结合起来以提供支持以下功能的IPC： 非常高的性能(共享内存) 同步(消息传递) 网络透明(消息传递) &#160; 　　客户端使用消息传递向一个服务器端发送请求并阻塞。服务器端按优先级接收客户端发送的消息，处理消息并满足一个请求之后再回复。这时，客户端就解除阻塞并继续运行。发送消息这个动作就自然的在客户端与服务器端之间做了同步。这时可以不必将全部数据复制到消息中进行传输，只要让消息中包含一个共享内存区范围的引用，服务器端就可以直接读写数据了。这通过一个简单的例子就能解释清楚了。 　　假设一个图形服务器从客户端接收绘图的请求并在一个图像卡上将其着色为一个帧缓冲。如果只使用消息传递，客户端就需要将图像数据包含在一个消息中传到服务器。结果就是从客户端的地址空间中的图像数据要复制到服务器的地址空间。之后服务器端为该图像着色并发送一个简短的回复。 　　如果客户端没有在消息中直接插入图像数据，而是发送了包含图像数据的共享内存区域的引用，那么服务器端就能够直接访问客户端的数据了。 　　由于客户端向服务器端发送消息而被阻塞，服务器端就知道共享内存中的数据是固定的并在服务器端回复之前是不会被改变的。这种消息传递与共享内存的结合就能自然同步并能提供很高的性能。 　　这个动作模式也可以倒过来——让服务器端产生数据并将其发送给客户端。例如，一个客户端先服务器端发送一条消息请求其从CD-ROM中读取视频数据并将其直接写入客户端所提供的共享内存缓存中。服务器端在写入共享内存时，客户端是阻塞的。当服务器端回复，客户端继续运行时，共享内存就已经是固定的了并可以让客户端访问了。这样的设计可以通过使用多个共享内存区域而流水线化。 　　对于通过网络连接的不同计算机上的进程之间是不能够使用简单共享内存的。而消息传递才是网络透明的。一个服务器端可以使用共享内存用于本地客户端，而使用全消息传递用于远程客户端。这样就可让服务器端既能高效也能网络透明。 　　实际使用中，消息传递原的速度已经足够主要进程间通信的需求了。只有在需要非常高带宽的特殊情况下才需要考虑这种复杂的各方式结合的手段。 创建共享内存对象 　　同一个进程中的多个线程共享该进程的内存。要在进程之间共享内存，就先要创建一个共享内存区域并将其映射到你的进程的地址空间。共享内存区域的创建与操作可以使用如下函数： Function Description Classification shm_open() Open (or create) a shared-memory region. POSIX close() Close a shared-memory region. POSIX mmap() Map a shared-memory region into a process&#8217;s address space. POSIX munmap() Unmap a shared-memory region from a process&#8217;s [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/27/195.html' rel='bookmark' title='Permanent Link: 多线程中壁垒(barrier)的使用'>多线程中壁垒(barrier)的使用</a> <small>　　前面我们讲过main()函数与工作线程结束进行的同步，在那里提到了两种方式：pthread_join()函数以及壁垒(barrier)。 　　现在我们回到房子的比喻，假设这个家庭准备到哪个地方旅行。司机上了小货车并发动了引擎。之后，司机就开始等待。只有全部的家庭成员都上车之后，这个小货车才会开动——因为我们不想把任何人落下！ 　　这和我们在前面说的那个绘图程序的原理是一模一样的。主线程要等待全部工作线程结束后，才执行下一步的程序。 　　不过和这个比喻还有一个很大的差别。那就是通过使用pthread_join()函数，我们是等待所有工作线程的结束。也就是说，之后这些线程已经不存在了，它们退出了。 　　通过使用壁垒(barrier)，我们可以等待某些数量的线程在壁垒处集合。在设定的数目达到之后，我们解锁这些线程，让它们继续运行。 　　你先要使用pthread_barrier+init()函数来创建壁垒： #include &lt;pthread.h&gt; int pthread_barrier_init...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2008/04/09/39.html' rel='bookmark' title='Permanent Link: 谈谈网站空间'>谈谈网站空间</a> <small>本文从西西河网整理而来。希望能够对所有要做网站、写blog的朋友有些帮助。 做一个网站，最重要的一个步骤，是找到合适的网站空间服务商 (web host provider)，来把你的网站放上去。应该说，webhosting 是一个巨大的市场。需求和相应的供给也是五花八门，各有各的特色，各有各的优点和缺点。找到一个合适的，而不是最好的服务商是一个网站起步的关键之一。 怎么样才是合适的，这里面涉及到各种因素。几个应该重点考虑的因素包括：价格，性能，可靠性，服务。 价格是每个人都关心的。作为消费者来说，当然是希望价格越便宜越好。但是，要记住，天上不会白白的掉馅饼。越便宜的你得越小心，特别是在同一类档次的品种中。Too good to...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　共享内存提供了进程间通信所能实现的最高带宽。一个共享内存对象创建之后，可以访问这个对象的进程就能够使用指针直接对其读写。这就意味着，共享内存访问本身就是非同步的。如果一个进程更新共享内存的一个区域，就必须特别小心不要让其他进程读取或更新同一块区域。即使是最简单的读取操作时，其他进程仍然有可能读到变化与不稳定的数据。</p>
<p>　　为了解决这个问题，共享内存就常与其他同步原(synchronization primitives)结合在一起使用以使进程之间的内存更新原子化。如果更新的间距很小，同步原就会限制自己固有的使用共享内存的高带宽。共享内存用于以块的模式更新大量的数据是最有效的。</p>
<p>　　信号量(semaphores)与互斥体(mutexes)都是适用与共享内存结合使用的同步原。信号量是在创建进程间同步的POSIX实时标准时引入的。互斥体则是在创建线程同步的POSIX标准是引入的。互斥体也可以在不同进程中的线程之间使用。POSIX将其作为一个可选的能力，我们在这里则是支持的。一般来说，互斥体要比信号量效率更高。</p>
<h6>用于消息传递的共享内存</h6>
<p>　　共享内存与消息传递可以结合起来以提供支持以下功能的IPC：</p>
<ul>
<li>非常高的性能(共享内存)</li>
<li>同步(消息传递)</li>
<li>网络透明(消息传递)</li>
</ul>
<p>&nbsp;</p>
<p><span id="more-177"></span>
<p>　　客户端使用消息传递向一个服务器端发送请求并阻塞。服务器端按优先级接收客户端发送的消息，处理消息并满足一个请求之后再回复。这时，客户端就解除阻塞并继续运行。发送消息这个动作就自然的在客户端与服务器端之间做了同步。这时可以不必将全部数据复制到消息中进行传输，只要让消息中包含一个共享内存区范围的引用，服务器端就可以直接读写数据了。这通过一个简单的例子就能解释清楚了。</p>
<p>　　假设一个图形服务器从客户端接收绘图的请求并在一个图像卡上将其着色为一个帧缓冲。如果只使用消息传递，客户端就需要将图像数据包含在一个消息中传到服务器。结果就是从客户端的地址空间中的图像数据要复制到服务器的地址空间。之后服务器端为该图像着色并发送一个简短的回复。</p>
<p>　　如果客户端没有在消息中直接插入图像数据，而是发送了包含图像数据的共享内存区域的引用，那么服务器端就能够直接访问客户端的数据了。</p>
<p>　　由于客户端向服务器端发送消息而被阻塞，服务器端就知道共享内存中的数据是固定的并在服务器端回复之前是不会被改变的。这种消息传递与共享内存的结合就能自然同步并能提供很高的性能。</p>
<p>　　这个动作模式也可以倒过来——让服务器端产生数据并将其发送给客户端。例如，一个客户端先服务器端发送一条消息请求其从CD-ROM中读取视频数据并将其直接写入客户端所提供的共享内存缓存中。服务器端在写入共享内存时，客户端是阻塞的。当服务器端回复，客户端继续运行时，共享内存就已经是固定的了并可以让客户端访问了。这样的设计可以通过使用多个共享内存区域而流水线化。</p>
<p>　　对于通过网络连接的不同计算机上的进程之间是不能够使用简单共享内存的。而消息传递才是网络透明的。一个服务器端可以使用共享内存用于本地客户端，而使用全消息传递用于远程客户端。这样就可让服务器端既能高效也能网络透明。</p>
<p>　　实际使用中，消息传递原的速度已经足够主要进程间通信的需求了。只有在需要非常高带宽的特殊情况下才需要考虑这种复杂的各方式结合的手段。</p>
<h6>创建共享内存对象</h6>
<p>　　同一个进程中的多个线程共享该进程的内存。要在进程之间共享内存，就先要创建一个共享内存区域并将其映射到你的进程的地址空间。共享内存区域的创建与操作可以使用如下函数：</p>
<table border="1" width="100%">
<tbody>
<tr>
<th>Function</th>
<th>Description</th>
<th>Classification</th>
</tr>
<tr>
<td><i class="func">shm_open()</i></td>
<td>Open (or create) a shared-memory region.</td>
<td>POSIX</td>
</tr>
<tr>
<td><i class="func">close()</i></td>
<td>Close a shared-memory region.</td>
<td>POSIX</td>
</tr>
<tr>
<td><i class="func">mmap()</i></td>
<td>Map a shared-memory region into a process&#8217;s address space.</td>
<td>POSIX</td>
</tr>
<tr>
<td><i class="func">munmap()</i></td>
<td>Unmap a shared-memory region from a process&#8217;s address space.</td>
<td>POSIX</td>
</tr>
<tr>
<td><i class="func">munmap_flags()</i></td>
<td>Unmap previously mapped addresses, exercising more control than possible with <i class="func">munmap()</i> </td>
<td>QNX Neutrino</td>
</tr>
<tr>
<td><i class="func">mprotect()</i></td>
<td>Change protections on a shared-memory region.</td>
<td>POSIX</td>
</tr>
<tr>
<td><i class="func">msync()</i></td>
<td>Synchronize memory with physical storage.</td>
<td>POSIX</td>
</tr>
<tr>
<td><i class="func">shm_ctl()</i>,<br /><i class="func">shm_ctl_special()</i></td>
<td>Give special attributes to a shared-memory object.</td>
<td>QNX Neutrino</td>
</tr>
<tr>
<td><i class="func">shm_unlink()</i></td>
<td>Remove a shared-memory region.</td>
<td>POSIX</td>
</tr>
</tbody>
</table>
<p>　　POSIX共享内存函数在Neutrino中是通过进程管理器(procnto)来实现的。以上的函数都是作为发送到procnto的消息来实现的。</p>
<p>　　Shm_open()函数所使用的参数与open()函数一致并向对象返回一个文件描述符。与对常规文件的操作一样，这个函数可以用于创建一个新的共享内存对象或是打开一个已有的共享内存对象。</p>
<p>　　当一个新的共享内存对象创建之后，其大小为0。要设置其大小需要使用ftruncate()函数（设置文件大小也是使用同一个函数），或是使用shm_ctl()函数。</p>
<h6>mmap()</h6>
<p>　　一旦你有了指向共享内存对象的文件描述符，就可以使用mmap()函数将这个共享内存的全部或部分映射到你的进程的地址空间。mmap()函数是Neutrino系统内存管理的基石并有必要在下面对其功能详细讨论。</p>
<p>　　该函数的详细定义如下：</p>
<pre class="codesamp">void * mmap( void *<i class="var">where_i_want_it</i>,
             size_t <i class="var">length</i>,
             int <i class="var">memory_protections</i>,
             int <i class="var">mapping_flags</i>,
             int <i class="var">fd</i>,
             off_t <i class="var">offset_within_shared_memory</i> );</pre>
<p>　　简单的说就是：将fd所关联的共享内存对象的offset_within_shared-memory处开始的length字节的共享内存映射到进程中。</p>
<p>　　mmap()函数将尝试将该内存放到你的地址空间的where_i_want_it地址处。该内存将按照指定的memory_protections进行保护并按照mapping_flag的模式映射。</p>
<p>　　三个参数fd、offset_within_shared_memory和length定义了要映射的共享内存的区域。通常都是映射整个共享对象，在这时便宜量就是0而长度就是共享对象的以字节计算的大小。在使用Intel处理器的时候，该长度将是页面大小的整倍数，使用Intel处理器时一个页面的大小为4096字节。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/27/195.html' rel='bookmark' title='Permanent Link: 多线程中壁垒(barrier)的使用'>多线程中壁垒(barrier)的使用</a> <small>　　前面我们讲过main()函数与工作线程结束进行的同步，在那里提到了两种方式：pthread_join()函数以及壁垒(barrier)。 　　现在我们回到房子的比喻，假设这个家庭准备到哪个地方旅行。司机上了小货车并发动了引擎。之后，司机就开始等待。只有全部的家庭成员都上车之后，这个小货车才会开动——因为我们不想把任何人落下！ 　　这和我们在前面说的那个绘图程序的原理是一模一样的。主线程要等待全部工作线程结束后，才执行下一步的程序。 　　不过和这个比喻还有一个很大的差别。那就是通过使用pthread_join()函数，我们是等待所有工作线程的结束。也就是说，之后这些线程已经不存在了，它们退出了。 　　通过使用壁垒(barrier)，我们可以等待某些数量的线程在壁垒处集合。在设定的数目达到之后，我们解锁这些线程，让它们继续运行。 　　你先要使用pthread_barrier+init()函数来创建壁垒： #include &lt;pthread.h&gt; int pthread_barrier_init...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2008/04/09/39.html' rel='bookmark' title='Permanent Link: 谈谈网站空间'>谈谈网站空间</a> <small>本文从西西河网整理而来。希望能够对所有要做网站、写blog的朋友有些帮助。 做一个网站，最重要的一个步骤，是找到合适的网站空间服务商 (web host provider)，来把你的网站放上去。应该说，webhosting 是一个巨大的市场。需求和相应的供给也是五花八门，各有各的特色，各有各的优点和缺点。找到一个合适的，而不是最好的服务商是一个网站起步的关键之一。 怎么样才是合适的，这里面涉及到各种因素。几个应该重点考虑的因素包括：价格，性能，可靠性，服务。 价格是每个人都关心的。作为消费者来说，当然是希望价格越便宜越好。但是，要记住，天上不会白白的掉馅饼。越便宜的你得越小心，特别是在同一类档次的品种中。Too good to...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2009/09/28/177.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>进程间通信的发/收/回复的健壮实现</title>
		<link>http://www.speedvi.net/2009/09/27/173.html</link>
		<comments>http://www.speedvi.net/2009/09/27/173.html#comments</comments>
		<pubDate>Sun, 27 Sep 2009 07:20:04 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[系统内核]]></category>
		<category><![CDATA[进程]]></category>
		<category><![CDATA[通信]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2009/09/27/173.html</guid>
		<description><![CDATA[　　通过使用发/收/回复将共同合作的线程与进程作为一个团队来构建一个UNIX应用程序的架构就会得到一个同步通知的系统。进程间通信就为这个系统的特殊转型而产生的，而不是在其后。 　　异步系统的一个重要问题就是事件通知需要依靠信号处理器才能运行。异步的进程间通信将难于进行彻底的系统运行测试，并不能够确保不论信号处理器怎样，处理工作能够如预想的那样运行。应用程序一般都是依赖一个确定的起始点的“窗口”，在这个窗口中信号的延迟是可以忍受的，来避免出现这种情况。 　　如果使用基于发/收/回复的同步、无队列的系统架构，健壮的应用程序架构就能够很容易的实现并交付。 　　使用队列IPC、共享内存以及其他同步原的各种组合来构建应用程序的另外一个关键问题就是如何避免死锁状态。例如，线程A在线程B释放复用体2之前不会释放复用体1，而且不幸的是线程B在线程A释放复用体1之前也不会释放复用体2，这时死锁就出现了。经常激活模拟工具来确保在系统运行时没有死锁的情况发生。 　　发/收/回复这些IPC原在遵守以下简单原则的话就能够构建无死锁的系统了： 永远不让两个线程互发； 一直使用继承的方式排列线程，只向继承树的上级发送。 　　第一条就避免了死锁情况发生，第二条则需要详细解释。合作的线程与进程的排列如下图所示： 　　在这里可以看到，任何级别的线程都不互相之间发送，只向其上层发送。 　　一个例子就是向数据库服务器进程发信的客户应用程序，它按序向文件系统经常发送信息。由于发送线程阻塞并等待回复，而目标线程没有发送阻塞就不会产生死锁。 　　但是，高层的线程如何提示底层线程它已经有了以前的操作请求的结果了？（假如底层线程在上一次发送之后不想继续等待回复结果。） 　　UNIX系统提供了一个非常灵活的架构，通过使用MsgDeliverEvent()这个内核调用来发布非阻塞的事件。所有异步服务都可以使用这种方式完成。 Related posts:进程间通信 　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递... 进程间通信的消息复制 　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU... 进程间通信的通道与连接 　　在UNIX系统中，消息传递是沿着通道与连接传送的，而不是直接的从线程到线程。一个线程要接收消息就先要创建一个通道，另外一个线程如果想要向这个线程传递消息就需要创建一个连接附加到该通道上。 　　通道是消息内核调用所必须的，服务器线程在调用MsgReceive()函数就是使用这个通道来接收消息的。连接由客户线程创建并用于“连接”服务器线程创建的可用通道。一旦连接确定，客户线程就可以使用MsgSend()函数发送消息了。如果一个进程中有多个线程连接到同一个通道，为了效率所有的连接就被映射到同一个内核对象。在一个进程中的通道与连接是使用一个小整数进行命名的。客户连接直接映射为文件描述符。 　　从架构上来说，这是一个关键点。通过将客户连接直接映射为文件描述符，就消除了使用另一层变换的必要。我们不必“指出”根据文件描述符向哪里发送消息。相反，我们可以很简单的将消息直接发送给“文件描述符”（也就是连接ID）。 　　这里可以使用的函数如下： 　　ChannelCreate() 创建一个通道来接收消息；　　ChannelDestroy() 清除一个通道；　　ConnectAttach() 创建一个连接来发送消息；　　ConnectDetach() 释放一个连接。 　　作为服务器端的进程可以使用事件循环来接收与处理消息，代码如下所示：...


Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/163.html' rel='bookmark' title='Permanent Link: 进程间通信的消息复制'>进程间通信的消息复制</a> <small>　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/170.html' rel='bookmark' title='Permanent Link: 进程间通信的通道与连接'>进程间通信的通道与连接</a> <small>　　在UNIX系统中，消息传递是沿着通道与连接传送的，而不是直接的从线程到线程。一个线程要接收消息就先要创建一个通道，另外一个线程如果想要向这个线程传递消息就需要创建一个连接附加到该通道上。 　　通道是消息内核调用所必须的，服务器线程在调用MsgReceive()函数就是使用这个通道来接收消息的。连接由客户线程创建并用于“连接”服务器线程创建的可用通道。一旦连接确定，客户线程就可以使用MsgSend()函数发送消息了。如果一个进程中有多个线程连接到同一个通道，为了效率所有的连接就被映射到同一个内核对象。在一个进程中的通道与连接是使用一个小整数进行命名的。客户连接直接映射为文件描述符。 　　从架构上来说，这是一个关键点。通过将客户连接直接映射为文件描述符，就消除了使用另一层变换的必要。我们不必“指出”根据文件描述符向哪里发送消息。相反，我们可以很简单的将消息直接发送给“文件描述符”（也就是连接ID）。 　　这里可以使用的函数如下： 　　ChannelCreate() 创建一个通道来接收消息；　　ChannelDestroy() 清除一个通道；　　ConnectAttach() 创建一个连接来发送消息；　　ConnectDetach() 释放一个连接。 　　作为服务器端的进程可以使用事件循环来接收与处理消息，代码如下所示：...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　通过使用发/收/回复将共同合作的线程与进程作为一个团队来构建一个UNIX应用程序的架构就会得到一个同步通知的系统。进程间通信就为这个系统的特殊转型而产生的，而不是在其后。</p>
<p>　　异步系统的一个重要问题就是事件通知需要依靠信号处理器才能运行。异步的进程间通信将难于进行彻底的系统运行测试，并不能够确保不论信号处理器怎样，处理工作能够如预想的那样运行。应用程序一般都是依赖一个确定的起始点的“窗口”，在这个窗口中信号的延迟是可以忍受的，来避免出现这种情况。</p>
<p><span id="more-173"></span>
<p>　　如果使用基于发/收/回复的同步、无队列的系统架构，健壮的应用程序架构就能够很容易的实现并交付。</p>
<p>　　使用队列IPC、共享内存以及其他同步原的各种组合来构建应用程序的另外一个关键问题就是如何避免死锁状态。例如，线程A在线程B释放复用体2之前不会释放复用体1，而且不幸的是线程B在线程A释放复用体1之前也不会释放复用体2，这时死锁就出现了。经常激活模拟工具来确保在系统运行时没有死锁的情况发生。</p>
<p>　　发/收/回复这些IPC原在遵守以下简单原则的话就能够构建无死锁的系统了：</p>
<ol>
<li>永远不让两个线程互发；</li>
<li>一直使用继承的方式排列线程，只向继承树的上级发送。</li>
<p>　　第一条就避免了死锁情况发生，第二条则需要详细解释。合作的线程与进程的排列如下图所示：</ol>
<ol><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="thtree" border="0" alt="thtree" src="http://www.speedvi.net/wp-content/uploads/2009/09/thtree.jpg" width="259" height="163"> </ol>
<p>　　在这里可以看到，任何级别的线程都不互相之间发送，只向其上层发送。</p>
<p>　　一个例子就是向数据库服务器进程发信的客户应用程序，它按序向文件系统经常发送信息。由于发送线程阻塞并等待回复，而目标线程没有发送阻塞就不会产生死锁。</p>
<p>　　但是，高层的线程如何提示底层线程它已经有了以前的操作请求的结果了？（假如底层线程在上一次发送之后不想继续等待回复结果。）</p>
<p>　　UNIX系统提供了一个非常灵活的架构，通过使用MsgDeliverEvent()这个内核调用来发布非阻塞的事件。所有异步服务都可以使用这种方式完成。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/163.html' rel='bookmark' title='Permanent Link: 进程间通信的消息复制'>进程间通信的消息复制</a> <small>　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/170.html' rel='bookmark' title='Permanent Link: 进程间通信的通道与连接'>进程间通信的通道与连接</a> <small>　　在UNIX系统中，消息传递是沿着通道与连接传送的，而不是直接的从线程到线程。一个线程要接收消息就先要创建一个通道，另外一个线程如果想要向这个线程传递消息就需要创建一个连接附加到该通道上。 　　通道是消息内核调用所必须的，服务器线程在调用MsgReceive()函数就是使用这个通道来接收消息的。连接由客户线程创建并用于“连接”服务器线程创建的可用通道。一旦连接确定，客户线程就可以使用MsgSend()函数发送消息了。如果一个进程中有多个线程连接到同一个通道，为了效率所有的连接就被映射到同一个内核对象。在一个进程中的通道与连接是使用一个小整数进行命名的。客户连接直接映射为文件描述符。 　　从架构上来说，这是一个关键点。通过将客户连接直接映射为文件描述符，就消除了使用另一层变换的必要。我们不必“指出”根据文件描述符向哪里发送消息。相反，我们可以很简单的将消息直接发送给“文件描述符”（也就是连接ID）。 　　这里可以使用的函数如下： 　　ChannelCreate() 创建一个通道来接收消息；　　ChannelDestroy() 清除一个通道；　　ConnectAttach() 创建一个连接来发送消息；　　ConnectDetach() 释放一个连接。 　　作为服务器端的进程可以使用事件循环来接收与处理消息，代码如下所示：...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2009/09/27/173.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>进程间通信的通道与连接</title>
		<link>http://www.speedvi.net/2009/09/25/170.html</link>
		<comments>http://www.speedvi.net/2009/09/25/170.html#comments</comments>
		<pubDate>Fri, 25 Sep 2009 08:02:18 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[传输]]></category>
		<category><![CDATA[消息]]></category>
		<category><![CDATA[系统内核]]></category>
		<category><![CDATA[进程]]></category>
		<category><![CDATA[连接]]></category>
		<category><![CDATA[通信]]></category>
		<category><![CDATA[通道]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2009/09/25/170.html</guid>
		<description><![CDATA[　　在UNIX系统中，消息传递是沿着通道与连接传送的，而不是直接的从线程到线程。一个线程要接收消息就先要创建一个通道，另外一个线程如果想要向这个线程传递消息就需要创建一个连接附加到该通道上。 　　通道是消息内核调用所必须的，服务器线程在调用MsgReceive()函数就是使用这个通道来接收消息的。连接由客户线程创建并用于“连接”服务器线程创建的可用通道。一旦连接确定，客户线程就可以使用MsgSend()函数发送消息了。如果一个进程中有多个线程连接到同一个通道，为了效率所有的连接就被映射到同一个内核对象。在一个进程中的通道与连接是使用一个小整数进行命名的。客户连接直接映射为文件描述符。 　　从架构上来说，这是一个关键点。通过将客户连接直接映射为文件描述符，就消除了使用另一层变换的必要。我们不必“指出”根据文件描述符向哪里发送消息。相反，我们可以很简单的将消息直接发送给“文件描述符”（也就是连接ID）。 　　这里可以使用的函数如下： 　　ChannelCreate() 创建一个通道来接收消息；　　ChannelDestroy() 清除一个通道；　　ConnectAttach() 创建一个连接来发送消息；　　ConnectDetach() 释放一个连接。 　　作为服务器端的进程可以使用事件循环来接收与处理消息，代码如下所示： 1 2 3 4 5 6 7 8 9 10 11 chid = ChannelCreate&#40;flags&#41;; SETIOV&#40;&#38;amp;iov, &#38;amp;msg, sizeof&#40;msg&#41;&#41;; for&#40;;;&#41; &#123; rcv_id = MsgReceivev&#40; chid, &#38;amp;iov, parts, &#38;amp;info &#41;; &#160; switch&#40; msg.type &#41; &#123; /* Perform message processing here */ &#125; &#160; MsgReplyv&#40; rcv_id, &#38;amp;iov, rparts &#41;; [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/163.html' rel='bookmark' title='Permanent Link: 进程间通信的消息复制'>进程间通信的消息复制</a> <small>　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/09/27/173.html' rel='bookmark' title='Permanent Link: 进程间通信的发/收/回复的健壮实现'>进程间通信的发/收/回复的健壮实现</a> <small>　　通过使用发/收/回复将共同合作的线程与进程作为一个团队来构建一个UNIX应用程序的架构就会得到一个同步通知的系统。进程间通信就为这个系统的特殊转型而产生的，而不是在其后。 　　异步系统的一个重要问题就是事件通知需要依靠信号处理器才能运行。异步的进程间通信将难于进行彻底的系统运行测试，并不能够确保不论信号处理器怎样，处理工作能够如预想的那样运行。应用程序一般都是依赖一个确定的起始点的“窗口”，在这个窗口中信号的延迟是可以忍受的，来避免出现这种情况。 　　如果使用基于发/收/回复的同步、无队列的系统架构，健壮的应用程序架构就能够很容易的实现并交付。 　　使用队列IPC、共享内存以及其他同步原的各种组合来构建应用程序的另外一个关键问题就是如何避免死锁状态。例如，线程A在线程B释放复用体2之前不会释放复用体1，而且不幸的是线程B在线程A释放复用体1之前也不会释放复用体2，这时死锁就出现了。经常激活模拟工具来确保在系统运行时没有死锁的情况发生。 　　发/收/回复这些IPC原在遵守以下简单原则的话就能够构建无死锁的系统了： 永远不让两个线程互发； 一直使用继承的方式排列线程，只向继承树的上级发送。 　　第一条就避免了死锁情况发生，第二条则需要详细解释。合作的线程与进程的排列如下图所示： 　　在这里可以看到，任何级别的线程都不互相之间发送，只向其上层发送。 　　一个例子就是向数据库服务器进程发信的客户应用程序，它按序向文件系统经常发送信息。由于发送线程阻塞并等待回复，而目标线程没有发送阻塞就不会产生死锁。...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　在UNIX系统中，消息传递是沿着通道与连接传送的，而不是直接的从线程到线程。一个线程要接收消息就先要创建一个通道，另外一个线程如果想要向这个线程传递消息就需要创建一个连接附加到该通道上。</p>
<p>　　通道是消息内核调用所必须的，服务器线程在调用MsgReceive()函数就是使用这个通道来接收消息的。连接由客户线程创建并用于“连接”服务器线程创建的可用通道。一旦连接确定，客户线程就可以使用MsgSend()函数发送消息了。如果一个进程中有多个线程连接到同一个通道，为了效率所有的连接就被映射到同一个内核对象。在一个进程中的通道与连接是使用一个小整数进行命名的。客户连接直接映射为文件描述符。</p>
<p>　　从架构上来说，这是一个关键点。通过将客户连接直接映射为文件描述符，就消除了使用另一层变换的必要。我们不必“指出”根据文件描述符向哪里发送消息。相反，我们可以很简单的将消息直接发送给“文件描述符”（也就是连接ID）。</p>
<p>　　这里可以使用的函数如下：</p>
<p><span id="more-170"></span>
</p>
<p>　　ChannelCreate() 创建一个通道来接收消息；<br />　　<i>ChannelDestroy() </i>清除一个通道；<br />　　<i>ConnectAttach() </i>创建一个连接来发送消息；<br />　　<em>ConnectDetach() </em>释放一个连接。</p>
<p><img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="connect" border="0" alt="connect" src="http://www.speedvi.net/wp-content/uploads/2009/09/connect.jpg" width="368" height="215"> </p>
<p>　　作为服务器端的进程可以使用事件循环来接收与处理消息，代码如下所示：</p>

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="code"><pre class="c" style="font-family:monospace;">chid <span style="color: #339933;">=</span> ChannelCreate<span style="color: #009900;">&#40;</span>flags<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
SETIOV<span style="color: #009900;">&#40;</span><span style="color: #339933;">&amp;</span>amp<span style="color: #339933;">;</span>iov<span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>amp<span style="color: #339933;">;</span>msg<span style="color: #339933;">,</span> <span style="color: #993333;">sizeof</span><span style="color: #009900;">&#40;</span>msg<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #b1b100;">for</span><span style="color: #009900;">&#40;</span><span style="color: #339933;">;;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    rcv_id <span style="color: #339933;">=</span> MsgReceivev<span style="color: #009900;">&#40;</span> chid<span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>amp<span style="color: #339933;">;</span>iov<span style="color: #339933;">,</span> parts<span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>amp<span style="color: #339933;">;</span>info <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #b1b100;">switch</span><span style="color: #009900;">&#40;</span> msg.<span style="color: #202020;">type</span> <span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #808080; font-style: italic;">/* Perform message processing here */</span>
        <span style="color: #009900;">&#125;</span>
&nbsp;
    MsgReplyv<span style="color: #009900;">&#40;</span> rcv_id<span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>amp<span style="color: #339933;">;</span>iov<span style="color: #339933;">,</span> rparts <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>&nbsp;</p>
<p>　　在循环中，线程可以接受连接到通道的所有线程的消息。</p>
<p>　　通道有几个与其关联的消息列表：</p>
<ul>
<li>接收：一个LIFO的等待消息的线程队列；</li>
<li>接收：一个有优先权的线程FIFO队列，这些线程发送了消息当还未被接收；</li>
<li>回复：一个无序的线程列表，这些线程发送的消息已被接受但还未被回复。</li>
</ul>
<p>　　在上面的任何一个列表中的等待队列都是阻塞状态(RECEIVE-、SEND-或REPLY-阻塞)。多个线程或客户端可能在等待同一个通道。</p>
<h6>脉冲</h6>
<p>　　除了同步的发送/接收/回复服务外，操作系统也提供了规定大小、非阻塞的消息。这种消息一般被称为脉冲并有小的体积（四个字节的数据和一个单字节的代码）。</p>
<p>　　脉冲的载荷相对来说很小——八位的代码以及32位的数据。脉冲常被用于中断处理器中的通知机制。也可以用于服务器端通知客户端而不阻塞它们。</p>
<p>　　一个脉冲的示意图如下：</p>
<p><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="pulse" border="0" alt="pulse" src="http://www.speedvi.net/wp-content/uploads/2009/09/pulse.jpg" width="269" height="78"> </p>
<h6>优先级继承与消息</h6>
<p>　　服务器进程按照优先级来接收消息与脉冲。当服务器进程中的线程接收到请求之后，它们就继承了发送消息的线程的优先级，而不是调度算法的优先级。这样，请求服务器端操作的线程的相对优先级就被保存了，服务器端的操作就会按照合适的优先级运行。这种消息驱动的优先级继承避免了优先级反转的问题。</p>
<p>　　例如，假如系统包含了如下线程：</p>
<ul>
<li>一个服务器线程，优先级为22；</li>
<li>一个客户线程，T1，优先级为13；</li>
<li>一个客户线程，T2，优先级为10.</li>
</ul>
<p>　　如果没有优先级继承，如果T2向服务器线程发送了一个消息，它所请求的动作就会在优先级22上执行，这样的话T2的优先级就被反转了。</p>
<p>　　实际上发生的是，当服务器端接收到一个消息，它的有效优先级就变为最高优先级发送者的优先级。在这里，T2的优先级要低于服务器端的优先级，这样当服务器端接收到消息之后其有效优先级就会发生变化。</p>
<p>　　接着，假设T1在服务器端的优先级还在10的时候发送了一个消息。由于T1的优先级要比服务器端的当前优先级高，当T1发送消息的时候服务器端的优先级就发生改变。</p>
<p>　　在服务器端接收到消息之前就改变优先级是为了防止又一次优先级反转的情况。如果服务器端的优先级保持低于10，而另一个线程T3开始以优先级11运行，服务器就要等待T3直到其能够有CPU时间来使其最终能够接收到T1的消息。这样的话，T1就会被低优先级的T3线程所延迟。</p>
<p>　　在调用ChannelCreate()函数的时候，可以通过设定——NTO_CHF_FIXED_PRIORITY标志来关闭优先级继承。如果使用自适应分块，这个标志将导致接收线程不在发送线程的分块中运行。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/163.html' rel='bookmark' title='Permanent Link: 进程间通信的消息复制'>进程间通信的消息复制</a> <small>　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/09/27/173.html' rel='bookmark' title='Permanent Link: 进程间通信的发/收/回复的健壮实现'>进程间通信的发/收/回复的健壮实现</a> <small>　　通过使用发/收/回复将共同合作的线程与进程作为一个团队来构建一个UNIX应用程序的架构就会得到一个同步通知的系统。进程间通信就为这个系统的特殊转型而产生的，而不是在其后。 　　异步系统的一个重要问题就是事件通知需要依靠信号处理器才能运行。异步的进程间通信将难于进行彻底的系统运行测试，并不能够确保不论信号处理器怎样，处理工作能够如预想的那样运行。应用程序一般都是依赖一个确定的起始点的“窗口”，在这个窗口中信号的延迟是可以忍受的，来避免出现这种情况。 　　如果使用基于发/收/回复的同步、无队列的系统架构，健壮的应用程序架构就能够很容易的实现并交付。 　　使用队列IPC、共享内存以及其他同步原的各种组合来构建应用程序的另外一个关键问题就是如何避免死锁状态。例如，线程A在线程B释放复用体2之前不会释放复用体1，而且不幸的是线程B在线程A释放复用体1之前也不会释放复用体2，这时死锁就出现了。经常激活模拟工具来确保在系统运行时没有死锁的情况发生。 　　发/收/回复这些IPC原在遵守以下简单原则的话就能够构建无死锁的系统了： 永远不让两个线程互发； 一直使用继承的方式排列线程，只向继承树的上级发送。 　　第一条就避免了死锁情况发生，第二条则需要详细解释。合作的线程与进程的排列如下图所示： 　　在这里可以看到，任何级别的线程都不互相之间发送，只向其上层发送。 　　一个例子就是向数据库服务器进程发信的客户应用程序，它按序向文件系统经常发送信息。由于发送线程阻塞并等待回复，而目标线程没有发送阻塞就不会产生死锁。...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2009/09/25/170.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>进程间通信的消息复制</title>
		<link>http://www.speedvi.net/2009/09/25/163.html</link>
		<comments>http://www.speedvi.net/2009/09/25/163.html#comments</comments>
		<pubDate>Fri, 25 Sep 2009 06:17:30 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[传递]]></category>
		<category><![CDATA[消息]]></category>
		<category><![CDATA[系统内核]]></category>
		<category><![CDATA[进程]]></category>
		<category><![CDATA[通信]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/?p=163</guid>
		<description><![CDATA[　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU page flipping)的页面对齐内存池中进行分配。这样，完成客户与服务器进程之间的API的库例程就可以很简单的进行表述，而不用调用IPC特定的内存分配函数了。 　　例如，一个客户线程用来请求文件系统管理器作为其代理来执行lseek的代码如下： #include &#60;unistd.h&#62; #include &#60;errno.h&#62; #include &#60;sys/iomsg.h&#62; &#160; off64_t lseek64&#40;int fd, off64_t offset, int whence&#41; &#123; io_lseek_t msg; off64_t off; &#160; msg.i.type = _IO_LSEEK; msg.i.combine_len = sizeof msg.i; msg.i.offset = offset; msg.i.whence = whence; msg.i.zero = 0; if&#40;MsgSend&#40;fd, &#38;amp;msg.i, sizeof msg.i, &#38;amp;off, sizeof [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/170.html' rel='bookmark' title='Permanent Link: 进程间通信的通道与连接'>进程间通信的通道与连接</a> <small>　　在UNIX系统中，消息传递是沿着通道与连接传送的，而不是直接的从线程到线程。一个线程要接收消息就先要创建一个通道，另外一个线程如果想要向这个线程传递消息就需要创建一个连接附加到该通道上。 　　通道是消息内核调用所必须的，服务器线程在调用MsgReceive()函数就是使用这个通道来接收消息的。连接由客户线程创建并用于“连接”服务器线程创建的可用通道。一旦连接确定，客户线程就可以使用MsgSend()函数发送消息了。如果一个进程中有多个线程连接到同一个通道，为了效率所有的连接就被映射到同一个内核对象。在一个进程中的通道与连接是使用一个小整数进行命名的。客户连接直接映射为文件描述符。 　　从架构上来说，这是一个关键点。通过将客户连接直接映射为文件描述符，就消除了使用另一层变换的必要。我们不必“指出”根据文件描述符向哪里发送消息。相反，我们可以很简单的将消息直接发送给“文件描述符”（也就是连接ID）。 　　这里可以使用的函数如下： 　　ChannelCreate() 创建一个通道来接收消息；　　ChannelDestroy() 清除一个通道；　　ConnectAttach() 创建一个连接来发送消息；　　ConnectDetach() 释放一个连接。 　　作为服务器端的进程可以使用事件循环来接收与处理消息，代码如下所示：...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/09/28/177.html' rel='bookmark' title='Permanent Link: 进程间通信的共享内存'>进程间通信的共享内存</a> <small>　　共享内存提供了进程间通信所能实现的最高带宽。一个共享内存对象创建之后，可以访问这个对象的进程就能够使用指针直接对其读写。这就意味着，共享内存访问本身就是非同步的。如果一个进程更新共享内存的一个区域，就必须特别小心不要让其他进程读取或更新同一块区域。即使是最简单的读取操作时，其他进程仍然有可能读到变化与不稳定的数据。 　　为了解决这个问题，共享内存就常与其他同步原(synchronization primitives)结合在一起使用以使进程之间的内存更新原子化。如果更新的间距很小，同步原就会限制自己固有的使用共享内存的高带宽。共享内存用于以块的模式更新大量的数据是最有效的。 　　信号量(semaphores)与互斥体(mutexes)都是适用与共享内存结合使用的同步原。信号量是在创建进程间同步的POSIX实时标准时引入的。互斥体则是在创建线程同步的POSIX标准是引入的。互斥体也可以在不同进程中的线程之间使用。POSIX将其作为一个可选的能力，我们在这里则是支持的。一般来说，互斥体要比信号量效率更高。 用于消息传递的共享内存 　　共享内存与消息传递可以结合起来以提供支持以下功能的IPC： 非常高的性能(共享内存) 同步(消息传递) 网络透明(消息传递) &nbsp;...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。</p>
<p>　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。</p>
<p>　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。</p>
<p>　　多段传送的示意图如下：<span id="more-163"></span></p>
<p><img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="messcopy" src="http://www.speedvi.net/wp-content/uploads/2009/09/messcopy.gif" border="0" alt="messcopy" width="310" height="126" />  多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。</p>
<p>　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下：</p>
<p><img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="scatter" src="http://www.speedvi.net/wp-content/uploads/2009/09/scatter.jpg" border="0" alt="scatter" width="260" height="302" /></p>
<p>　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU page flipping)的页面对齐内存池中进行分配。这样，完成客户与服务器进程之间的API的库例程就可以很简单的进行表述，而不用调用IPC特定的内存分配函数了。</p>
<p>　　例如，一个客户线程用来请求文件系统管理器作为其代理来执行lseek的代码如下：</p>

<div class="wp_syntax"><div class="code"><pre class="c" style="font-family:monospace;"><span style="color: #339933;">#include &lt;unistd.h&gt;</span>
<span style="color: #339933;">#include &lt;errno.h&gt;</span>
<span style="color: #339933;">#include &lt;sys/iomsg.h&gt;</span>
&nbsp;
off64_t lseek64<span style="color: #009900;">&#40;</span><span style="color: #993333;">int</span> fd<span style="color: #339933;">,</span> off64_t offset<span style="color: #339933;">,</span> <span style="color: #993333;">int</span> whence<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    io_lseek_t                            msg<span style="color: #339933;">;</span>
    off64_t                               off<span style="color: #339933;">;</span>
&nbsp;
    msg.<span style="color: #202020;">i</span>.<span style="color: #202020;">type</span> <span style="color: #339933;">=</span> _IO_LSEEK<span style="color: #339933;">;</span>
    msg.<span style="color: #202020;">i</span>.<span style="color: #202020;">combine_len</span> <span style="color: #339933;">=</span> <span style="color: #993333;">sizeof</span> msg.<span style="color: #202020;">i</span><span style="color: #339933;">;</span>
    msg.<span style="color: #202020;">i</span>.<span style="color: #202020;">offset</span> <span style="color: #339933;">=</span> offset<span style="color: #339933;">;</span>
    msg.<span style="color: #202020;">i</span>.<span style="color: #202020;">whence</span> <span style="color: #339933;">=</span> whence<span style="color: #339933;">;</span>
    msg.<span style="color: #202020;">i</span>.<span style="color: #202020;">zero</span> <span style="color: #339933;">=</span> <span style="color: #0000dd;">0</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span>MsgSend<span style="color: #009900;">&#40;</span>fd<span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>amp<span style="color: #339933;">;</span>msg.<span style="color: #202020;">i</span><span style="color: #339933;">,</span> <span style="color: #993333;">sizeof</span> msg.<span style="color: #202020;">i</span><span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>amp<span style="color: #339933;">;</span>off<span style="color: #339933;">,</span> <span style="color: #993333;">sizeof</span> off<span style="color: #009900;">&#41;</span> <span style="color: #339933;">==</span> <span style="color: #339933;">-</span><span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #b1b100;">return</span> <span style="color: #339933;">-</span><span style="color: #0000dd;">1</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
    <span style="color: #b1b100;">return</span> off<span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
off64_t tell64<span style="color: #009900;">&#40;</span><span style="color: #993333;">int</span> fd<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #b1b100;">return</span> lseek64<span style="color: #009900;">&#40;</span>fd<span style="color: #339933;">,</span> <span style="color: #0000dd;">0</span><span style="color: #339933;">,</span> SEEK_CUR<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
off_t lseek<span style="color: #009900;">&#40;</span><span style="color: #993333;">int</span> fd<span style="color: #339933;">,</span> off_t offset<span style="color: #339933;">,</span> <span style="color: #993333;">int</span> whence<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #b1b100;">return</span> lseek64<span style="color: #009900;">&#40;</span>fd<span style="color: #339933;">,</span> offset<span style="color: #339933;">,</span> whence<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
off_t tell<span style="color: #009900;">&#40;</span><span style="color: #993333;">int</span> fd<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #b1b100;">return</span> lseek64<span style="color: #009900;">&#40;</span>fd<span style="color: #339933;">,</span> <span style="color: #0000dd;">0</span><span style="color: #339933;">,</span> SEEK_CUR<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>



<p>Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/170.html' rel='bookmark' title='Permanent Link: 进程间通信的通道与连接'>进程间通信的通道与连接</a> <small>　　在UNIX系统中，消息传递是沿着通道与连接传送的，而不是直接的从线程到线程。一个线程要接收消息就先要创建一个通道，另外一个线程如果想要向这个线程传递消息就需要创建一个连接附加到该通道上。 　　通道是消息内核调用所必须的，服务器线程在调用MsgReceive()函数就是使用这个通道来接收消息的。连接由客户线程创建并用于“连接”服务器线程创建的可用通道。一旦连接确定，客户线程就可以使用MsgSend()函数发送消息了。如果一个进程中有多个线程连接到同一个通道，为了效率所有的连接就被映射到同一个内核对象。在一个进程中的通道与连接是使用一个小整数进行命名的。客户连接直接映射为文件描述符。 　　从架构上来说，这是一个关键点。通过将客户连接直接映射为文件描述符，就消除了使用另一层变换的必要。我们不必“指出”根据文件描述符向哪里发送消息。相反，我们可以很简单的将消息直接发送给“文件描述符”（也就是连接ID）。 　　这里可以使用的函数如下： 　　ChannelCreate() 创建一个通道来接收消息；　　ChannelDestroy() 清除一个通道；　　ConnectAttach() 创建一个连接来发送消息；　　ConnectDetach() 释放一个连接。 　　作为服务器端的进程可以使用事件循环来接收与处理消息，代码如下所示：...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='Permanent Link: 进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/09/28/177.html' rel='bookmark' title='Permanent Link: 进程间通信的共享内存'>进程间通信的共享内存</a> <small>　　共享内存提供了进程间通信所能实现的最高带宽。一个共享内存对象创建之后，可以访问这个对象的进程就能够使用指针直接对其读写。这就意味着，共享内存访问本身就是非同步的。如果一个进程更新共享内存的一个区域，就必须特别小心不要让其他进程读取或更新同一块区域。即使是最简单的读取操作时，其他进程仍然有可能读到变化与不稳定的数据。 　　为了解决这个问题，共享内存就常与其他同步原(synchronization primitives)结合在一起使用以使进程之间的内存更新原子化。如果更新的间距很小，同步原就会限制自己固有的使用共享内存的高带宽。共享内存用于以块的模式更新大量的数据是最有效的。 　　信号量(semaphores)与互斥体(mutexes)都是适用与共享内存结合使用的同步原。信号量是在创建进程间同步的POSIX实时标准时引入的。互斥体则是在创建线程同步的POSIX标准是引入的。互斥体也可以在不同进程中的线程之间使用。POSIX将其作为一个可选的能力，我们在这里则是支持的。一般来说，互斥体要比信号量效率更高。 用于消息传递的共享内存 　　共享内存与消息传递可以结合起来以提供支持以下功能的IPC： 非常高的性能(共享内存) 同步(消息传递) 网络透明(消息传递) &nbsp;...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2009/09/25/163.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
