<?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>Soul Of Free Loop &#187; 编程</title>
	<atom:link href="https://zohead.com/archives/tag/programming/feed" rel="self" type="application/rss+xml" />
	<link>https://zohead.com</link>
	<description>Uranus Zhou&#039;s Blog</description>
	<lastBuildDate>Sat, 19 Jul 2025 15:42:46 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.8</generator>
	<item>
		<title>使用libc封装库修改程序绑定端口</title>
		<link>https://zohead.com/archives/libc-bind-wrapper/</link>
		<comments>https://zohead.com/archives/libc-bind-wrapper/#comments</comments>
		<pubDate>Mon, 11 Aug 2014 14:15:01 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[网络技术]]></category>
		<category><![CDATA[bind.so]]></category>
		<category><![CDATA[libc]]></category>
		<category><![CDATA[socket]]></category>
		<category><![CDATA[wrapper]]></category>
		<category><![CDATA[端口]]></category>
		<category><![CDATA[绑定]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[网络]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=764</guid>
		<description><![CDATA[本文同步自（最佳显示效果请点击）：https://zohead.com/archives/libc-bind-wrapper/ 最近在使用一个第三方程序的时候发现程序绑定的 UDP 端口和现有 Linux 系统中的程序有冲突，系统自带的程序又不好修改端口，而第三方程序更没有源码或者配置文件来指定端口。 这种情况下想到可以用 libc 的封装库自己实现 bind 之类的函数来修改端口号，而网上也找到了 Daniel Ryde 类似的实现： http://www.ryde.net/code/bind.c.txt 编译这个 bind 库可以通过环境变量指定绑定的本地 IP 地址，但不支持端口号修改， [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文同步自（最佳显示效果请点击）：<a href="https://zohead.com/archives/libc-bind-wrapper/" target="_blank">https://zohead.com/archives/libc-bind-wrapper/</a></p>
<p>最近在使用一个第三方程序的时候发现程序绑定的 UDP 端口和现有 Linux 系统中的程序有冲突，系统自带的程序又不好修改端口，而第三方程序更没有源码或者配置文件来指定端口。</p>
<p>这种情况下想到可以用 libc 的封装库自己实现 bind 之类的函数来修改端口号，而网上也找到了 Daniel Ryde 类似的实现：</p>
<p><a href="http://www.ryde.net/code/bind.c.txt" target="_blank">http://www.ryde.net/code/bind.c.txt</a></p>
<p>编译这个 bind 库可以通过环境变量指定绑定的本地 IP 地址，但不支持端口号修改，而且是直接修改程序所有绑定主机地址不好过滤定制。因此，我对这个 bind 的封装库做了以下改进：</p>
<ul>
<li>支持 socket 类型过滤，可以指定是 TCP、UDP socket 等；</li>
<li>支持指定是本地监听请求还是连接远程的请求；</li>
<li>支持修改本地使用的 IP 地址及端口号；</li>
<li>支持通过环境变量过滤掉某些端口</li>
</ul>
<p>支持上面这些新增的功能之后，我们就可以根据实际情况只修改特定请求的绑定地址或者绑定端口以满足需求。</p>
<p>我修改的 bind 封装库代码如下，也比较简单：</p>
<pre class="brush: cpp; title: bind.c; notranslate">
/*
   Copyright (C) 2000  Daniel Ryde

   Modified by Uranus Zhou (2014)

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
*/
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;dlfcn.h&gt;
#include &lt;errno.h&gt;
#include &lt;string.h&gt;

int (*real_bind)(int, const struct sockaddr *, socklen_t);
int (*real_connect)(int, const struct sockaddr *, socklen_t);

char *bind_addr_env, *connect_bind_addr_env, *bind_port_env, *bind_type_env, *exclude_ports_env, *connect_port_env, *connect_type_env, *connect_bind_port_env;
unsigned long int bind_addr_saddr;
struct sockaddr_in local_sockaddr_in[] = { 0 };
int bind_port_ns = -1, connect_port_ns = -1;
int bind_type = -1, connect_type = -1;

static int get_sock_type(const char *str)
{
	if (strcasecmp(str, &quot;TCP&quot;) == 0)
		return SOCK_STREAM;
	else if (strcasecmp(str, &quot;UDP&quot;) == 0)
		return SOCK_DGRAM;
	else if (strcasecmp(str, &quot;RAW&quot;) == 0)
		return SOCK_RAW;
	else if (strcasecmp(str, &quot;PACKET&quot;) == 0)
		return SOCK_PACKET;
	else
		return -1;
}

void _init (void)
{
	const char *err;

	real_bind = dlsym (RTLD_NEXT, &quot;bind&quot;);
	if ((err = dlerror ()) != NULL) {
		fprintf (stderr, &quot;dlsym (bind): %s\n&quot;, err);
	}

	real_connect = dlsym (RTLD_NEXT, &quot;connect&quot;);
	if ((err = dlerror ()) != NULL) {
		fprintf (stderr, &quot;dlsym (connect): %s\n&quot;, err);
	}

	if (bind_addr_env = getenv (&quot;BIND_ADDR&quot;))
		bind_addr_saddr = inet_addr (bind_addr_env);

	if (bind_port_env = getenv (&quot;BIND_PORT&quot;))
		bind_port_ns = htons (atoi(bind_port_env));

	if (bind_type_env = getenv (&quot;BIND_TYPE&quot;))
		bind_type = get_sock_type(bind_type_env);

	exclude_ports_env = getenv (&quot;EXCLUDE_PORTS&quot;);

	if (connect_port_env = getenv (&quot;CONNECT_PORT&quot;))
		connect_port_ns = htons (atoi(connect_port_env));

	if (connect_type_env = getenv (&quot;CONNECT_TYPE&quot;))
		connect_type = get_sock_type(connect_type_env);

	if (connect_bind_addr_env = getenv (&quot;CONNECT_BIND_ADDR&quot;)) {
		local_sockaddr_in-&gt;sin_family = AF_INET;
		local_sockaddr_in-&gt;sin_addr.s_addr = inet_addr (connect_bind_addr_env);
		local_sockaddr_in-&gt;sin_port = htons (0);
	}

	if (connect_bind_port_env = getenv (&quot;CONNECT_BIND_PORT&quot;))
		local_sockaddr_in-&gt;sin_port = htons (atoi(connect_bind_port_env));
}

static int check_port(int port)
{
	char *tmp = exclude_ports_env, *str = NULL;
	if (tmp == NULL) return 0;

	while (1) {
		char szPort[50] = {0};
		str = strchr(tmp, ',');
		if (str == NULL)
			strncpy(szPort, tmp, sizeof(szPort));
		else
			strncpy(szPort, tmp, str - tmp);
		if (atoi(szPort) == port) return 1;
		if (str == NULL) break;
		tmp = str + 1;
	}

	return 0;
}

int bind (int fd, const struct sockaddr *sk, socklen_t sl)
{
	static struct sockaddr_in *lsk_in;

	lsk_in = (struct sockaddr_in *)sk;
	if (lsk_in-&gt;sin_family == AF_INET || lsk_in-&gt;sin_family == AF_INET6) {
		int type, length = sizeof(int);

		getsockopt(fd, SOL_SOCKET, SO_TYPE, &amp;type, &amp;length);

		if (check_port(ntohs(lsk_in-&gt;sin_port)) == 0 &amp;&amp; (bind_type_env == NULL || bind_type == type)) {
			// change bind address
			if (lsk_in-&gt;sin_addr.s_addr == htonl (INADDR_ANY) &amp;&amp; bind_addr_env)
				lsk_in-&gt;sin_addr.s_addr = bind_addr_saddr;

			// change bind port
			if (bind_port_env)
				lsk_in-&gt;sin_port = bind_port_ns;
		}
	}
	return real_bind (fd, sk, sl);
}

int connect (int fd, const struct sockaddr *sk, socklen_t sl)
{
	static struct sockaddr_in *rsk_in;

	rsk_in = (struct sockaddr_in *)sk;
	if (rsk_in-&gt;sin_family == AF_INET || rsk_in-&gt;sin_family == AF_INET6) {
		int type, length = sizeof(int);

		getsockopt(fd, SOL_SOCKET, SO_TYPE, &amp;type, &amp;length);

		if ((connect_port_env == NULL || connect_port_ns == rsk_in-&gt;sin_port) &amp;&amp; (connect_type_env == NULL || connect_type == type)) {
			// change connect bind address or bind port
			if (connect_bind_addr_env)
				real_bind (fd, (struct sockaddr *)local_sockaddr_in, sizeof (struct sockaddr));
		}
	}
	return real_connect (fd, sk, sl);
}
</pre>
<p>编译方法：</p>
<pre><strong>gcc -nostartfiles -fpic -shared bind.c -o bind.so -ldl -D_GNU_SOURCE</strong></pre>
<p>运行指定程序时需要通过 LD_PRELOAD 预先加载 bind.so 库，并通过环境变量指定如何过滤修改网络参数：</p>
<ul>
<li>BIND_ADDR 指定本地监听的 IP 地址；</li>
<li>BIND_PORT 指定本地监听的端口；</li>
<li>BIND_TYPE 指定本地监听的 socket 类型（TCP、UDP、RAW、PACKET）；</li>
<li>CONNECT_PORT 指定连接远程主机的端口；</li>
<li>CONNECT_TYPE 指定远程主机 socket 连接类型（与 BIND_TYPE 类似）；</li>
<li>CONNECT_BIND_ADDR 指定连接远程主机时本地使用的 IP 地址；</li>
<li>CONNECT_BIND_PORT 指定连接远程主机时本地使用的端口；</li>
<li>EXCLUDE_PORTS 需要排除的端口列表（以逗号隔开）</li>
</ul>
<p>这里举个例子说明如何使用 bind.so：</p>
<p>假设第三方程序为 testapp，testapp 启动时会本地监听 UDP 775 端口，另外还会在本地 eth0 网卡上监听一个随机的 UDP 端口。现在我们需要将后面这个随机的 UDP 端口固定为 666，而且需要指定使用 eth2 网卡，就可以这样运行 testapp 程序：</p>
<pre><strong>BIND_ADDR="eth2-ip" BIND_PORT=666 BIND_TYPE=UDP EXCLUDE_PORTS=775 LD_PRELOAD=bind.so testapp</strong></pre>
<p>上面的命令中指定了本地 IP 地址（eth2-ip）、绑定端口（666）、socket 类型（UDP），并且使用 EXCLUDE_PORTS 环境变量过滤了固定监听的 775 端口，这样 testapp 程序运行使用的随机 UDP 端口就固定在 666 上了。</p>
<p>我修改的 bind.c 源代码可以从我的 Gist 下载：</p>
<p><a href="https://gist.github.com/zohead/9950663ca01952c940eb" target="_blank">https://gist.github.com/zohead/9950663ca01952c940eb</a></p>
<p>实际使用如果有任何问题欢迎提出指正哦，玩的开心 ^_^</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/libc-bind-wrapper/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Linux kernel学习-内存管理</title>
		<link>https://zohead.com/archives/linux-kernel-learning-memory-management/</link>
		<comments>https://zohead.com/archives/linux-kernel-learning-memory-management/#comments</comments>
		<pubDate>Sat, 30 Jun 2012 19:03:50 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[代码分析]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[kmalloc]]></category>
		<category><![CDATA[kmap]]></category>
		<category><![CDATA[page]]></category>
		<category><![CDATA[内存]]></category>
		<category><![CDATA[分配]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[释放]]></category>
		<category><![CDATA[高端内存]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=242</guid>
		<description><![CDATA[本文同步自（如浏览不正常请点击跳转）：https://zohead.com/archives/linux-kernel-learning-memory-management/ 接着之前的 Linux kernel 学习步伐，来到极其重要的内存管理部分，继续本文内容，需要先了解内存寻址的基础知识，见之前的 [内存寻址] 博文。 1、内存页及内存区域： 正如之前所说，Linux kernel 使用物理页作为内存管理的基本单位，其中重要的线程地址和物理地址的转换操作由页单元 MMU 来完成，系统的页表也由 MMU 来维护。kernel 使用 struct page 来表示一个物理页，它的定义在 in [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文同步自（如浏览不正常请点击跳转）：<a href="https://zohead.com/archives/linux-kernel-learning-memory-management/" target="_blank">https://zohead.com/archives/linux-kernel-learning-memory-management/</a></p>
<p>接着之前的 Linux kernel 学习步伐，来到极其重要的内存管理部分，继续本文内容，需要先了解内存寻址的基础知识，见之前的 [<a href="https://zohead.com/archives/linux-kernel-learning-memory-addressing/" target="_blank">内存寻址</a>] 博文。</p>
<p><strong><span style="color: #ff0000;">1、内存页及内存区域：</span></strong></p>
<p>正如之前所说，Linux kernel 使用物理页作为内存管理的基本单位，其中重要的线程地址和物理地址的转换操作由页单元 MMU 来完成，系统的页表也由 MMU 来维护。kernel 使用 struct page 来表示一个物理页，它的定义在 include/linux/mm_types.h 头文件中：</p>
<pre class="brush: cpp; title: include/linux/mm_types.h; notranslate">
struct page {
	unsigned long flags;		/* Atomic flags, some possibly
					 * updated asynchronously */
	atomic_t _count;		/* Usage count, see below. */
	union {
		atomic_t _mapcount;	/* Count of ptes mapped in mms,
					 * to show when page is mapped
					 * &amp; limit reverse map searches.
					 */
		struct {		/* SLUB */
			u16 inuse;
			u16 objects;
		};
	};
	union {
	    struct {
		unsigned long private;		/* Mapping-private opaque data:
					 	 * usually used for buffer_heads
						 * if PagePrivate set; used for
						 * swp_entry_t if PageSwapCache;
						 * indicates order in the buddy
						 * system if PG_buddy is set.
						 */
		struct address_space *mapping;	/* If low bit clear, points to
						 * inode address_space, or NULL.
						 * If page mapped as anonymous
						 * memory, low bit is set, and
						 * it points to anon_vma object:
						 * see PAGE_MAPPING_ANON below.
						 */
	    };
#if USE_SPLIT_PTLOCKS
	    spinlock_t ptl;
#endif
	    struct kmem_cache *slab;	/* SLUB: Pointer to slab */
	    struct page *first_page;	/* Compound tail pages */
	};
	union {
		pgoff_t index;		/* Our offset within mapping. */
		void *freelist;		/* SLUB: freelist req. slab lock */
	};
	struct list_head lru;		/* Pageout list, eg. active_list
					 * protected by zone-&gt;lru_lock !
					 */
	/*
	 * On machines where all RAM is mapped into kernel address space,
	 * we can simply calculate the virtual address. On machines with
	 * highmem some memory is mapped into kernel virtual memory
	 * dynamically, so we need a place to store that address.
	 * Note that this field could be 16 bits on x86 ... <img src="https://zohead.com/wp-includes/images/smilies/icon_wink.gif" alt=";)" class="wp-smiley" /> 
	 *
	 * Architectures with slow multiplication can define
	 * WANT_PAGE_VIRTUAL in asm/page.h
	 */
#if defined(WANT_PAGE_VIRTUAL)
	void *virtual;			/* Kernel virtual address (NULL if
					   not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */
#ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS
	unsigned long debug_flags;	/* Use atomic bitops on this */
#endif

#ifdef CONFIG_KMEMCHECK
	/*
	 * kmemcheck wants to track the status of each byte in a page; this
	 * is a pointer to such a status block. NULL if not tracked.
	 */
	void *shadow;
#endif
};
</pre>
<p>其中的 flags 用于表示页的状态（是否为脏或者被锁定等），_count 即为页的引用计数，kernel 一般使用 page_count 宏调用 atomic_read 函数原子的读取此值，page_count 返回 0 表示此页可用。如果一个页被作为 page cache 使用，则 page 的 mapping 字段指向映射的 inode 的 address_space 对象，如果页被作为私有数据（作为 buffer_heads 缓冲、buddy 系统等），则 private 常包含对应的信息。注意其中的 virtual 字段为页的虚拟地址，结合之前的知识，对于高端内存来说，其并没有被固定映射到 kernel 地址空间中，因此如果 virtual 字段为 NULL，则表示此页必须被动态映射。</p>
<p>kernel 使用 page 结构记录系统中的所有页，因此 struct page 的大小应该要尽量小以减少内存占用，另外 kernel 必须知道页是否空闲，如果不空闲则拥有者是谁。</p>
<p>由于实际硬件限制，Linux kernel 不可能使用全部的物理内存，kernel 为此将内存划分为不同的区域，一个区域中的内存属性应该也相同。kernel 中常见的内存区域有 ZONE_DMA（可用于 DMA 的页）、ZONE_DMA32（与 ZONE_DMA 类似，但只对 32 位设备可用）、ZONE_NORMAL、ZONE_HIGHMEM（并没有被固定映射的高端内存区域），这些内存区域一般都是硬件相关的，例如在 x86 架构下，ZONE_DMA 的范围为 0MB - 16MB，ZONE_HIGHMEM 为高于 896MB 的物理内存，而在 x86_64 架构下 ZONE_HIGHMEM 则为空。需要注意的是内存的分配不会跨域这些不同的内存区域。内存区域在 kernel 中由 struct zone 结构来表示，其中的 name 字段即为内存区域名称。</p>
<p><strong><span style="color: #ff0000;">2、获取页：</span></strong></p>
<p>分配和释放内存是 Linux kernel 中极其重要又用的极多的接口。先看看 kernel 提供的直接获取以内存页面为单位的 alloc_pages 函数：</p>
<p><em><span style="color: #008000;">struct page * alloc_pages(gfp_t gfp_mask, unsigned int order)</span></em></p>
<p>此函数是最基本的用于分配大小为 2^order 并且连续的物理页的函数，其返回分配到的第一个页面的 page 指针。</p>
<p>来看看比较重要的 gfp_t 类型的 gfp_mask 值：</p>
<p>gfp_t 实际上就是 unsigned int 类型，gfp_mask 常用于指定行为方式、区域方式、类型等信息。常见的行为方式标志有：__GFP_WAIT（标志分配器可以睡眠，明显不适用于中断上下文中）、__GFP_IO（分配器可以启动磁盘 I/O）等。区域方式指定内存从哪里分配，对应的就有：__GFP_DMA、__GFP_DMA32、__GFP_HIGHMEM（从高端内存或普通内存中分配）。类型标志则用于简化分配时的指定操作，常见的有：GFP_ATOMIC（高优先级并不可睡眠，常用于中断、中断下半部、持有自旋锁等环境中）、GFP_NOIO（表示分配可中断但不可以发起 I/O 操作）、GFP_NOFS（分配时不可发起文件 I/O 操作）、GFP_KERNEL（最常见的分配标志，常用于可以睡眠的进程上下文中）、GFP_USER（用于分配内存给用户进程）、GFP_DMA 等。</p>
<p>需要注意的是对 __get_free_pages 和 kmalloc 函数（下面会分别说明）不能指定 __GFP_HIGHMEM 标志，因为它们都是直接返回的虚拟地址，而非 page 结构指针，如果指定了 __GFP_HIGHMEM，则他们可能分配到的内存并没有被映射到 kernel 地址空间，因此这样得不到虚拟地址。只有 alloc_page 函数可以分配高端内存，这个限制在下面的 __get_free_pages 函数的实现中可以看到。</p>
<p>使用 page_address 函数可以将 page 指针转换为虚拟地址（非物理地址）。实际使用中经常会用到 __get_free_pages 函数直接在分配页时直接得到虚拟地址，其参数为 alloc_pages 完全一样，看看它的实现就一目了然了：</p>
<pre class="brush: cpp; title: mm/page_alloc.c; notranslate">
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{
	struct page *page;

	/*
	 * __get_free_pages() returns a 32-bit address, which cannot represent
	 * a highmem page
	 */
	VM_BUG_ON((gfp_mask &amp; __GFP_HIGHMEM) != 0);

	page = alloc_pages(gfp_mask, order);
	if (!page)
		return 0;
	return (unsigned long) page_address(page);
}
</pre>
<p>另外 kernel 还 “好心” 的提供了两个只分配一个页的函数：alloc_page 和 __get_free_page，可以想象只是把 order 参数设为 0 而已。你可以使用 get_zeroed_page 函数分配一个页并自动清零（gfp_mask 指定 __GFP_ZERO）。</p>
<p>对应的释放页可以用 __free_pages（page 指针为参数）、free_pages（虚拟地址为参数）、free_page（只释放一个页）这些函数。</p>
<p>下面是常用的分配非整数倍页大小的内存的函数。首先是最常用的 kmalloc 函数：</p>
<p><em><span style="color: #008000;">void *kmalloc(size_t size, gfp_t flags)</span></em></p>
<p>kmalloc 用于分配最少指定的 size 字节大小的内存（实际分配的可能比 size 多），这与用户空间的 malloc 函数很相似，但需要注意的是 kmalloc 分配的内存物理地址是连续的，这非常重要。</p>
<p>相应的释放内存函数是 kfree：</p>
<p><em><span style="color: #008000;">void kfree(const void *objp)</span></em></p>
<p>kfree 用于释放 kmalloc 分配的内存，注意如果使用 kfree 在不是的 kmalloc 分配的内存地址或者已经 kfree 过的地址上，都可能导致 kernel 出错。</p>
<p>紧接着就是大名鼎鼎的 vmalloc 函数了。它与 kmalloc 类似，但它分配的内存只是虚拟连续的而物理地址却不一定连续，这也类似于用户空间的 malloc 函数的效果。vmalloc 由于需要做页表转换之类的操作，性能比 kmalloc 差，而且 vmalloc 得到的页还必须由单独的页来做映射，对 TLB 缓存的效率也会有影响（有关 TLB 缓存参考之前的文章 [<a href="https://zohead.com/archives/linux-kernel-learning-memory-addressing/" target="_blank">内存寻址</a>]），由于这些原因，vmalloc 在 kernel 中用到的机会并不是很多，其常用于分配大量的内存，常见的一个例子就是内核模块的代码就是通过 vmalloc 加载到 kernel 中的。vmalloc 的原型为：</p>
<p><em><span style="color: #008000;">void * vmalloc(unsigned long size)</span></em></p>
<p>与之对应的，使用 vfree 释放分配的内存。另外 vmalloc 和 vfree 都是可以睡眠的，因此它们对中断上下文是不适用的。</p>
<p><strong><span style="color: #ff0000;">3、Slab分配器：</span></strong></p>
<p>Slab 也是 Linux kernel 中非常重要的组成部分，它用于简化内存的分配和释放，它相当于一个可用内存列表，里面包含一堆已经分配好的数据结构，当 kernel 需要分配一个数据结构时，可以直接从这个可用内存列表中取出而节省分配的时间，不需要的时候又可以还给这个列表而不需要释放，因此这个列表用于缓存经常访问的某种类型的数据。为了统一管理和释放，Linux kernel 引入 Slab 分配器作为通用的数据结构缓存层给经常访问的数据结构使用。需要说明的是 kmalloc 就是在 Slab 分配器基础上实现的。</p>
<p>这里简单对 Slab 分配器做个介绍，有关其细节请参考这篇 PDF 文档：</p>
<p><a href="http://students.mimuw.edu.pl/SO-MSUI/Wyklady/06_pamiec/bonwick.pdf" target="_blank">The Slab Allocator: An Object-Caching Kernel Memory Allocator</a></p>
<p>Slab 层将不同的对象划分到名为 cache 的不同组中，每个组存储不同类型的数据，也就是每种数据类型都有一个 cache。每个 cache 然后被划分为多个 slab，slab 由一个或多个连续的物理页组成（通常只有一个页），每个 slab 又包含一些数量的对象，也就是实际缓存的数据。每个 slab 的状态可以是这三个中的一个：满、部分满、空。当 kernel 请求一个新对象时，优先从状态为 部分满 的 slab 中取，如果没有则从状态为 空 的 slab 中分配，如果没有状态为 空 的 slab 了就创建一个，可以看到这种策略可以相对的减少内存碎片。</p>
<p>kernel 中常用到的 struct inode 结构就是一个典型的例子，它在 VFS 等地方被用到的非常多，因此 kernel 中增加一个名为 inode_cachep 的 cache 用于缓存 inode 结构。</p>
<p>每个 cache 由 kmem_cache 结构来表示，它的 struct kmem_list3 *nodelists[MAX_NUMNODES] 类型字段即为该 cache 包含的所有 slab。每个 slab 由 struct slab 结构来表示，看看 kmem_list3 和 slab 结构的定义：</p>
<pre class="brush: cpp; title: mm/slab.c; notranslate">
struct slab {
	struct list_head list;
	unsigned long colouroff;
	void *s_mem;		/* including colour offset */
	unsigned int inuse;	/* num of objs active in slab */
	kmem_bufctl_t free;
	unsigned short nodeid;
};

struct kmem_list3 {
	struct list_head slabs_partial;	/* partial list first, better asm code */
	struct list_head slabs_full;
	struct list_head slabs_free;
	unsigned long free_objects;
	unsigned int free_limit;
	unsigned int colour_next;	/* Per-node cache coloring */
	spinlock_t list_lock;
	struct array_cache *shared;	/* shared per node */
	struct array_cache **alien;	/* on other nodes */
	unsigned long next_reap;	/* updated without locking */
	int free_touched;		/* updated without locking */
};
</pre>
<p>多个 slab 可以分别链接到 kmem_list3 的 满（slabs_full）、部分满（slabs_partial）、空（slabs_free）3 个链表中。</p>
<p>Slab 分配器调用 kmem_getpages 函数分配新的 slab（关于 cache 的创建下面会提到），kmem_getpages 会调用 __get_free_pages 函数分配所需的内存用于保持 cache，因此 kmem_getpages 一般在当 部分满（partial） 和 空（free）slab 的情况下调用，来看看它的实现：</p>
<pre class="brush: cpp; title: mm/slab.c; notranslate">
static void *kmem_getpages(struct kmem_cache *cachep, gfp_t flags, int nodeid)
{
	struct page *page;
	int nr_pages;
	int i;

#ifndef CONFIG_MMU
	/*
	 * Nommu uses slab's for process anonymous memory allocations, and thus
	 * requires __GFP_COMP to properly refcount higher order allocations
	 */
	flags |= __GFP_COMP;
#endif

	flags |= cachep-&gt;gfpflags;
	if (cachep-&gt;flags &amp; SLAB_RECLAIM_ACCOUNT)
		flags |= __GFP_RECLAIMABLE;

	page = alloc_pages_exact_node(nodeid, flags | __GFP_NOTRACK, cachep-&gt;gfporder);
	if (!page)
		return NULL;

	nr_pages = (1 &lt;&lt; cachep-&gt;gfporder);
	if (cachep-&gt;flags &amp; SLAB_RECLAIM_ACCOUNT)
		add_zone_page_state(page_zone(page),
			NR_SLAB_RECLAIMABLE, nr_pages);
	else
		add_zone_page_state(page_zone(page),
			NR_SLAB_UNRECLAIMABLE, nr_pages);
	for (i = 0; i &lt; nr_pages; i++)
		__SetPageSlab(page + i);

	if (kmemcheck_enabled &amp;&amp; !(cachep-&gt;flags &amp; SLAB_NOTRACK)) {
		kmemcheck_alloc_shadow(page, cachep-&gt;gfporder, flags, nodeid);

		if (cachep-&gt;ctor)
			kmemcheck_mark_uninitialized_pages(page, nr_pages);
		else
			kmemcheck_mark_unallocated_pages(page, nr_pages);
	}

	return page_address(page);
}
</pre>
<p>第一个参数 cachep 为需要分配页的 cache，cachep-&gt;gfporder 指定要分配的大小，上面的代码中对于 NUMA 架构做了必要的处理。</p>
<p>kmem_getpages 分配的内存通过 kmem_freepages 释放，它调用 free_pages 释放页，kmem_freepages 一般在系统检测到内存不足时调用或者在销毁 cache 时显示调用。</p>
<p>下面重点来看看 Slab 分配器如何使用。</p>
<p>使用 kmem_cache_create 函数创建新的 cache，其定义为：</p>
<p><em><span style="color: #008000;">struct kmem_cache * kmem_cache_create (const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *))</span></em></p>
<p>第一个 name 参数指定 cache 的名称，size 为 cache 中对象的大小，align 为对象的对齐（一般为 0），flags 控制 cache 的行为，最后一个参数 ctor 为对象的构造函数，cache 分配新页时会调用此构造函数，现在一般将 ctor 值设为 NULL。</p>
<p>cache 的标志可以是下面常用几种标志的 OR 值：</p>
<p><strong>SLAB_HWCACHE_ALIGN</strong>：对 cache 中的每个对象做对齐处理，对齐之后可以提高 cache line 的访问性能，但由于要浪费内存空间，因此一般只在对性能有很高要求的场合使用；<br />
<strong>SLAB_POISON</strong>：以固定的值填充 slab（默认 0xa5a5a5a5）；<br />
<strong>SLAB_PANIC</strong>：如果分配失败，kernel 直接 panic；<br />
<strong>SLAB_CACHE_DMA</strong> ：指定 Slab 层在 ZONE_DMA 上分配每个 slab。</p>
<p>kmem_cache_create 如果成功返回 struct kmem_cache 结构指针，注意由于 kmem_cache_create 函数可能会睡眠，因此不能在中断上下文中使用。</p>
<p>使用 kmem_cache_destroy 函数销毁 kmem_cache_create 返回的 cache，此函数一般在模块退出时调用，你也可以在很多模块的初始化中找到 kmem_cache_create。同样由于会睡眠，kmem_cache_destroy 也不能在中断上下文中使用。</p>
<p>cache 被创建之后，就可以调用 kmem_cache_alloc 函数从 cache 中取得对象，其定义为：</p>
<p><em><span style="color: #008000;">void * kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)</span></em></p>
<p>此函数直接返回对象的指针，如果 cache 中所有 slab 都没有空闲的对象了，Slab 层就需要调用 kmem_getpages 获取新的页。</p>
<p>如果一个对象不再需要使用了，可以调用 kmem_cache_free 将其回收到 slab 中：</p>
<p><em><span style="color: #008000;">void kmem_cache_free(struct kmem_cache *cachep, void *objp)</span></em></p>
<p>需要注意的就是 kmem_cache_free 和 kmem_cache_destroy 不能混淆。</p>
<p><strong><span style="color: #ff0000;">4、高端内存映射：</span></strong></p>
<p>由于高端内存不是被固定映射到 kernel 地址空间中，因此 alloc_pages 函数使用时如果指定了 __GFP_HIGHMEM 标志，则它返回的 page 很可能没有有效的虚拟地址。</p>
<p>使用 kmap 函数可以将一个 page 固定的映射到 kernel 地址空间中：</p>
<p><em><span style="color: #008000;">void *kmap(struct page *page)</span></em></p>
<p>注意此函数对高端内存和低端内存都是适用的，如果 page 在低端内存，则直接返回页的虚拟地址，否则需要创建内存映射，由于 kmap 可能会睡眠，因此不能在中断上下文中使用。</p>
<p>被映射的高端内存不需要时应使用 kunmap 函数删除映射。</p>
<p>另外对于不能睡眠的进程环境，Linux kernel 又提供了临时的高端内存映射方法。kernel 可以原子地映射一个高端内存页到 kernel 中的保留映射集中的一个，此保留映射集也是专门用于中断上下文等不能睡眠的地方映射高端内存页的需要。临时高端内存映射函数为 kmap_atomic，看看它在 x86 下的实现：</p>
<pre class="brush: cpp; title: arch/x86/mm/highmem_32.c; notranslate">
void *kmap_atomic_prot(struct page *page, enum km_type type, pgprot_t prot)
{
	enum fixed_addresses idx;
	unsigned long vaddr;

	/* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */
	pagefault_disable();

	if (!PageHighMem(page))
		return page_address(page);

	debug_kmap_atomic(type);

	idx = type + KM_TYPE_NR*smp_processor_id();
	vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
	BUG_ON(!pte_none(*(kmap_pte-idx)));
	set_pte(kmap_pte-idx, mk_pte(page, prot));

	return (void *)vaddr;
}

void *kmap_atomic(struct page *page, enum km_type type)
{
	return kmap_atomic_prot(page, type, kmap_prot);
}
</pre>
<p>kmap_atomic 实际调用 kmap_atomic_prot 实现临时映射，kmap_atomic_prot 中同样会先做判断，如果要映射的页不在高端内存则直接返回虚拟地址，然后根据 type 和当前处理器 ID 计算得到 fixmap 的索引，并调用 __fix_to_virt 将 fixmap 索引转换为虚拟地址，有关 fixmap 机制见之前的 [<a href="https://zohead.com/archives/linux-kernel-learning-memory-addressing/" target="_blank">内存寻址</a>] 博文。</p>
<p>kmap_atomic 函数的 type 参数用于临时映射的用途，此函数会禁用内核抢占，因为临时映射是和每个处理器相关的，它是直接调用 pagefault_disable 函数禁止 page fault handler，其中会自动禁用内核抢占，看看 pagefault_disable 的实现：</p>
<pre class="brush: cpp; title: include/linux/uaccess.h; notranslate">
static inline void pagefault_disable(void)
{
	inc_preempt_count();
	/*
	 * make sure to have issued the store before a pagefault
	 * can hit.
	 */
	barrier();
}
</pre>
<p>临时高端内存映射可以使用 kunmap_atomic 函数删除，它会启用内核抢占，同时它也不会睡眠，需要注意的是此次的临时高端内存映射在下一次临时映射高端内存时就会无效。</p>
<p>本文中如果有任何问题，欢迎提出指正哦，玩的开心~~~ ^_^</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/linux-kernel-learning-memory-management/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Linux kernel kfifo分析</title>
		<link>https://zohead.com/archives/linux-kernel-kfifo/</link>
		<comments>https://zohead.com/archives/linux-kernel-kfifo/#comments</comments>
		<pubDate>Mon, 11 Jun 2012 17:32:15 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[代码分析]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[FIFO]]></category>
		<category><![CDATA[kfifo]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[队列]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=226</guid>
		<description><![CDATA[本文同步自（如浏览不正常请点击跳转）：https://zohead.com/archives/linux-kernel-kfifo/ kfifo 是 Linux kernel 中的一个通用队列实现，对于 kernel 中常见的 FIFO 队列应用还是很有用的，本文主要简单介绍分析下 Linux kernel kfifo。实际上 ChinaUnix 上有个 kfifo 的分析文章，但已经比较老（基于 Linux 2.6.10），而且我现在用的 2.6.34 版本 kernel 中 kfifo 实现有很多改动，故自己简单写下，ChinaUnix 上的 kfifo 介绍帖子在这里： http://b [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文同步自（如浏览不正常请点击跳转）：<a href="https://zohead.com/archives/linux-kernel-kfifo/" target="_blank">https://zohead.com/archives/linux-kernel-kfifo/</a></p>
<p>kfifo 是 Linux kernel 中的一个通用队列实现，对于 kernel 中常见的 FIFO 队列应用还是很有用的，本文主要简单介绍分析下 Linux kernel kfifo。实际上 ChinaUnix 上有个 kfifo 的分析文章，但已经比较老（基于 Linux 2.6.10），而且我现在用的 2.6.34 版本 kernel 中 kfifo 实现有很多改动，故自己简单写下，ChinaUnix 上的 kfifo 介绍帖子在这里：</p>
<p><a href="http://bbs.chinaunix.net/thread-1994832-1-1.html" target="_blank">http://bbs.chinaunix.net/thread-1994832-1-1.html</a></p>
<p>kfifo 定义在 include/linux/kfifo.h 头文件中，我们经常使用的就是 kfifo 结构，看看它的定义：</p>
<pre class="brush: cpp; title: include/linux/kfifo.h; notranslate">
struct kfifo {
	unsigned char *buffer;	/* the buffer holding the data */
	unsigned int size;	/* the size of the allocated buffer */
	unsigned int in;	/* data is added at offset (in % size) */
	unsigned int out;	/* data is extracted from off. (out % size) */
};
</pre>
<p>kfifo 也像其它队列那样提供了两个主要操作：入队列（in） 和 出队列（out），对应于上面结构中的 in 和 out 两个偏移量，in 偏移量为下次入队列的位置，out 为下次出队列的位置，很容易也能想到 out 值必须小于等于 in 值，当 out 值等于 in 值时表示队列为空，kfifo 中 buffer 为队列的空间，size 为空间大小，必须为 2 的 k 次幂值（原因在下面说明）。当然如果 in 值等于队列长度了，就表示队列已经满了。</p>
<p>先看看 kfifo 最简单的一些操作实现，在 kernel/kfifo.c 文件中：</p>
<pre class="brush: cpp; title: kernel/kfifo.c; notranslate">
static void _kfifo_init(struct kfifo *fifo, void *buffer,
		unsigned int size)
{
	fifo-&gt;buffer = buffer;
	fifo-&gt;size = size;

	kfifo_reset(fifo);
}

/**
 * kfifo_init - initialize a FIFO using a preallocated buffer
 * @fifo: the fifo to assign the buffer
 * @buffer: the preallocated buffer to be used.
 * @size: the size of the internal buffer, this has to be a power of 2.
 *
 */
void kfifo_init(struct kfifo *fifo, void *buffer, unsigned int size)
{
	/* size must be a power of 2 */
	BUG_ON(!is_power_of_2(size));

	_kfifo_init(fifo, buffer, size);
}
EXPORT_SYMBOL(kfifo_init);

/**
 * kfifo_alloc - allocates a new FIFO internal buffer
 * @fifo: the fifo to assign then new buffer
 * @size: the size of the buffer to be allocated, this have to be a power of 2.
 * @gfp_mask: get_free_pages mask, passed to kmalloc()
 *
 * This function dynamically allocates a new fifo internal buffer
 *
 * The size will be rounded-up to a power of 2.
 * The buffer will be release with kfifo_free().
 * Return 0 if no error, otherwise the an error code
 */
int kfifo_alloc(struct kfifo *fifo, unsigned int size, gfp_t gfp_mask)
{
	unsigned char *buffer;

	/*
	 * round up to the next power of 2, since our 'let the indices
	 * wrap' technique works only in this case.
	 */
	if (!is_power_of_2(size)) {
		BUG_ON(size &gt; 0x80000000);
		size = roundup_pow_of_two(size);
	}

	buffer = kmalloc(size, gfp_mask);
	if (!buffer) {
		_kfifo_init(fifo, NULL, 0);
		return -ENOMEM;
	}

	_kfifo_init(fifo, buffer, size);

	return 0;
}
EXPORT_SYMBOL(kfifo_alloc);

/**
 * kfifo_free - frees the FIFO internal buffer
 * @fifo: the fifo to be freed.
 */
void kfifo_free(struct kfifo *fifo)
{
	kfree(fifo-&gt;buffer);
	_kfifo_init(fifo, NULL, 0);
}
EXPORT_SYMBOL(kfifo_free);
</pre>
<p>调用 kfifo_alloc 可以自动分配空间并初始化，你也可以调用 kfifo_init 函数使用自己的空间来初始化队列，可以看到这两个函数中都用 is_power_of_2 做了检查队列空间的操作。kfifo_free 释放队列，它会调用 _kfifo_init 函数（参数为 NULL 和 0 清空队列），调用 kfifo_reset 可以重置队列（将 in 和 out 都设为 0）。你也可以用 DECLARE_KFIFO 和 INIT_KFIFO 静态定义一个 kfifo 队列，尽管这不太会被用到。</p>
<p>调用 kfifo_in 函数将数据加入队列，kfifo_out 将数据从队列中取出并从队列中删除（增加 out 值），Linux 还提供了 kfifo_out_peek 函数从队列中取数据但并不删除（不增加 out 值）。kfifo_in 中会先调用 __kfifo_in_data 将输入加入队列，然后调用 __kfifo_add_in 增加 in 的值，kfifo_out 相反则调用 __kfifo_out_data 和 __kfifo_add_out 函数取出数据并删除。</p>
<p>kfifo 中同时提供了 kfifo_from_user 函数用户将用户空间的数据加入到队列中，它会先调用 __kfifo_from_user_data 将用户空间的数据加入队列，再调用 __kfifo_add_in 增加 in 的值。看看 __kfifo_from_user_data 的实现：</p>
<pre class="brush: cpp; title: kernel/kfifo.c; notranslate">
static inline int __kfifo_from_user_data(struct kfifo *fifo,
	 const void __user *from, unsigned int len, unsigned int off,
	 unsigned *lenout)
{
	unsigned int l;
	int ret;

	/*
	 * Ensure that we sample the fifo-&gt;out index -before- we
	 * start putting bytes into the kfifo.
	 */

	smp_mb();

	off = __kfifo_off(fifo, fifo-&gt;in + off);

	/* first put the data starting from fifo-&gt;in to buffer end */
	l = min(len, fifo-&gt;size - off);
	ret = copy_from_user(fifo-&gt;buffer + off, from, l);
	if (unlikely(ret)) {
		*lenout = ret;
		return -EFAULT;
	}
	*lenout = l;

	/* then put the rest (if any) at the beginning of the buffer */
	ret = copy_from_user(fifo-&gt;buffer, from + l, len - l);
	*lenout += ret ? ret : len - l;
	return ret ? -EFAULT : 0;
}
</pre>
<p>可以看到 __kfifo_from_user_data 中是直接调用 copy_from_user 将用户空间的数据拷贝到 kfifo 队列的空间中。相应的也有 kfifo_to_user 函数将队列中的数据取出到用户空间的地址，他就调用 copy_to_user 将队列中数据拷贝到用户空间。</p>
<p>需要注意的是 __kfifo_from_user_data 中用到的 __kfifo_off 函数：</p>
<pre class="brush: cpp; title: include/linux/kfifo.h; notranslate">
static inline unsigned int __kfifo_off(struct kfifo *fifo, unsigned int off)
{
	return off &amp; (fifo-&gt;size - 1);
}
</pre>
<p>__kfifo_off 是根据指定的偏移量得到索引值，由这里也可以看出为什么队列的大小为什么必须是 2 的 k 次幂值，否则无法得到正确的值。而且从代码中可以看到 __kfifo_from_user_data、__kfifo_in_n、__kfifo_in_rec 等函数中都用到了 __kfifo_off 函数指定加入队列时的偏移量。</p>
<p>另外从 include/linux/kfifo.h 中你也可以看到新的 kfifo 实现中默认 EXPORT 了非常多的 API 函数给 kernel 开发者使用。</p>
<p>以上为个人分析结果，有任何问题欢迎指正哦 ^_^</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/linux-kernel-kfifo/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>未能看到整个森林-编程学习中所犯的错误</title>
		<link>https://zohead.com/archives/fail-to-see-the-big-picture/</link>
		<comments>https://zohead.com/archives/fail-to-see-the-big-picture/#comments</comments>
		<pubDate>Sat, 19 May 2012 10:37:18 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[学习感悟]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[DSL]]></category>
		<category><![CDATA[LOP]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[技巧]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[错误]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=173</guid>
		<description><![CDATA[本文同步自（如浏览不正常请点击跳转）：https://zohead.com/archives/fail-to-see-the-big-picture/ 备注：本文根据 pongba 大哥的这篇E文文章翻译并结合自己体会总结而来，pongba 的E文原文请猛击这里： http://blog.csdn.net/pongba/article/details/2143245 人类的一个普遍的天性是容易被自己感兴趣的东西所吸引。 不论是本文要说的编程学习还是日常事务都是这样，包括美女之类（哈哈），这似乎是一个难以打破的公理。人类自文明开始以来就对非凡的自然现象下的本质非常好奇，我们渴望理解，渴望知道原因 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文同步自（如浏览不正常请点击跳转）：<a href="https://zohead.com/archives/fail-to-see-the-big-picture/" target="_blank">https://zohead.com/archives/fail-to-see-the-big-picture/</a></p>
<p>备注：本文根据 pongba 大哥的这篇E文文章翻译并结合自己体会总结而来，pongba 的E文原文请猛击这里：</p>
<p><a href="http://blog.csdn.net/pongba/article/details/2143245" target="_blank">http://blog.csdn.net/pongba/article/details/2143245</a></p>
<p><strong><span style="color: #ff0000;">人类的一个普遍的天性是容易被自己感兴趣的东西所吸引。</span></strong></p>
<p>不论是本文要说的编程学习还是日常事务都是这样，包括美女之类（哈哈），这似乎是一个难以打破的公理。人类自文明开始以来就对非凡的自然现象下的本质非常好奇，我们渴望理解，渴望知道原因。人类天性就是用来解决问题的，我们热衷于解决问题，热衷于发现问题的本质。不过悲哀的是，我们也是问题的主要创造者。</p>
<p>具体说到编程学习这一块，pongba 的原文中用 interesting（感兴趣） 和 mundane（平凡普通的）这两个词来区分编程学习中的两类知识。</p>
<p>我们最开始学习编程时用到的最经典的 hello world 就是 interesting 的一种，看到自己敲的一段字符能让计算机打印出来 hello world 确实能激发我们的兴趣。这就是所谓的 Under the Hood，这是一个在英文技术文章里经常见到的词，原意是钻进魔术师的帐篷，屏住呼吸，瞪大眼睛，把那些奇妙的魔法看个通透，让自己的理解和技艺获得巨幅的提升，在IT界里就是深入理解的意思。</p>
<p>你在学会设计程序和了解程序能正确运行的原因之后，接下来你会做什么？你会继续写程序，发现你所用的编程语言的越来越多的细节。你会越来越了解你用的编程语言，能知道该语言能方便的做些什么，哪些不能很方便的实现。接下来各种语言的窍门就开始进入你的脑海，注意这些窍门最吸引人的地方在于能让你做到本来做不到的事情，能让你荷尔蒙迸发，让你很 happy。</p>
<p>从程序设计语言的角度来看，我们热衷于解决的问题往往是我们自己创造的。例如，最近有一种争论关于设计模式是语言中缺失的一种特性。首先我们创造一门程序设计语言，由于一些设计时没有预料到的缺点，在使用中发现了，我们使用包括设计模式在内的一些语言窍门来解决它。但随着时间推移，我们就会发现这些模式不但没有价值，反而变成一种沉重的负担，这时通常会把这些作为新特性加入到语言中。</p>
<p>通常我们在解决以前的语言造成的问题过程中，我们通常又会造成新的问题。例如，现在总有 DSL 和 GPL 的争论，注意这里的 GPL 不是 GPL 开放授权协议，而是 Gerneral-purpose Language（通用语言），DSL 是 Domain-specific Language（领域专用语言）。DSL在很多人心目中是“非程序员的编程语言”，其首要目的是使程序尽可能接近领域中遇到的问题，消除不必要的间接性和复杂性，而其最终受众一般不是普通的程序员。一方面，将领域专用的一些特性加入到语言中，对那些需要对特定领域编程的人来说会非常便利；而另一方面，它会限制语言的使用范围。相对于DSL，GPL的最大优势在于可以为理论上无限的应用领域服务。GPL最大的妥协在于当面对领域相关的问题时，它只相当于一个 second-class language，这是为什么微软要搞一个CLR（通用语言运行时，Common Language Runtime）运行环境，也是为什么 Martin Fowler 要倡导面向语言编程LOP（Language-oriented Programming）了。</p>
<p>因此，在这总结一下，我们创造了各种语言抽象概念以使语言更加易用，但周尔反复的是我们总是在解决一个问题时创造时另一个新的问题。由于我们的程序设计语言都存在着不可避免的缺陷，这样语言窍门之类的东西就会登堂入室，并偷走我们的关注点（原文如此，嘿嘿），这也是为什么市面上有如此多的编程语言技巧书，语言陷阱介绍之类的，而且销量似乎都甚好。你可以看看任何C++编程学习的推荐书列表中，都不乏这样的例子。</p>
<p>然而到底是什么导致我们在编程学习时如在一堆树木里迷失，而导致没有看到整个森林？为了什么我们要学习这些奇淫技巧呢？实际上我们不是真正的需要，但我们内心里趋向于学习这些技巧，因为像文章开头说的，我们天生就是问题解决者，我们喜欢解决问题，即使这些问题是我们自己创造的。但这些奇淫技巧实际上只要在真正需要时按需学习即可，我们被这些东西吸引的原因在于：</p>
<p>1、我们喜欢新事物，如果什么东西是新事物就很有趣；<br />
2、我们喜欢赶时髦（jump on the bandwagon）。</p>
<p><strong><span style="color: #ff0000;">这就引出人类的第二个普遍的天性：赶时髦，如果所有人都做一件事，那我无论如何也得做。</span></strong></p>
<p>不光是一些公司或者团体使用这个策略引诱我们去赶时髦，我们还热衷于创造自己的潮流。每当有新的语言或者技巧出来的时候，我们总是欢呼雀跃，总是被这些新带来的特性的光晕笼罩，而忘了它实际包含的问题，我们总是把它当做是万能灵药，开始万般饥渴地学习它。程序员是一种聪明的动物（原谅我如此直白，哈哈），不过有时显得过于聪明。他们总是渴望于新的事物（在任何编程论坛上找一圈就能得到验证，你会发现成千上万的程序语言技术细节的问题，学习这些东西是永无止境的，但程序员就是如此地欲罢不能），就像野兽永远不能停止对于食物的饥渴一样。</p>
<p>下面说说程序员普遍不爱的平凡的东西，什么是大多数程序员不喜欢的东西？</p>
<p>大多数程序员不爱的东西包括：编程原则，从小的编码规则（例如：永远给变量起一个有意义的名字）到大的项目设计原则（例如：在写代码之前先写测试文档）都有，还有文档的编制之类的，这些都是比较乏味的，不会显得古怪有味道，显得没有挑战性，显得没有那么酷。我们无法向外界展示遵从一些愚蠢的规则是多么聪明的一件事。我们尤其钟爱的是写一些疯狂的技巧代码或使用一些耀眼的模式以使别人都不知道我们在做什么（或者每个人都知道我们在做什么）。</p>
<p><strong><span style="color: #ff0000;">接下来是人类的第三个天性：自私的偏见，我们热爱我们所做的，或者我们是谁，我们讨厌与之对抗或相反的东西。</span></strong></p>
<p>不管你是否愿意承认这点，我们都有过这个体验。当我们对某些语言或平台非常熟悉时，我们就容易产生自私的偏见，它会影响我们想学习和不想学习的东西。你应该可以在一些论坛上感受到关于编程语言的争论是如此普遍。我们总是被蒙蔽了双眼，没有看到自己所用的语言或平台上的缺点和其它语言上的优点。我们限制了学习新的语言的能力，从某种意义上来说，我们限制了自己的潜能。</p>
<p><strong><span style="color: #ff0000;">翻译的总结：</span></strong></p>
<p>一方面，大多数时候我们学的东西有点太多了。我们像飞蛾扑火般被新事物吸引，我们经常是在学习周围的人在学习的，或者是别人告诉我们要学习的。但如果我们抓牢了一些本质的知识，其它的东西就完全可以按需学习。别再沉迷于技术技巧，除非它是必备的或者你马上就要用到的。因为要学习的技术技巧总是无穷无尽的，你应该将你的时间花在更有用的东西上（学些本质知识，学习编程思想，或者学习另一门编程语言）。</p>
<p>但另一方面，我们学的东西又太少了。我们总是忽视看起来乏味但又非常重要的东西，例如以下观点（可能很多人都有过）：</p>
<p>测试？ --- 就像做爱前戴套一样不爽；<br />
重构？ --- 为什么要做这种不能带来新功能也不酷的东西；<br />
防御式编程？ --- 对不起，我知道自己在写什么；<br />
API设计？ --- 拜托，我在写这么华丽的代码的时候去考虑别人怎么使用我的代码也太TMD难了；<br />
新语言？ --- 你是说我现在用的这个不够好？没看到我能随意折腾这门语言来实现我想做的？</p>
<p>这是我首次完整翻译别人写的E文文章，也加了一些自己的观点，希望读者和我都能在未来看到编程学习中的整个森林 ^_^，有任何错误欢迎指正咯。</p>
<p>参考：<br />
1、DSL领域专用语言：<br />
<a href="http://en.wikipedia.org/wiki/Domain-specific_programming_language" target="_blank">http://en.wikipedia.org/wiki/Domain-specific_programming_language</a><br />
2、LOP面向语言编程：<br />
<a href="http://en.wikipedia.org/wiki/Language-oriented_programming" target="_blank">http://en.wikipedia.org/wiki/Language-oriented_programming</a></p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/fail-to-see-the-big-picture/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>WordPress友言近期评论Widget更新（v1.1）</title>
		<link>https://zohead.com/archives/wordpress-uyan-recent-comment-widget-v1-1/</link>
		<comments>https://zohead.com/archives/wordpress-uyan-recent-comment-widget-v1-1/#comments</comments>
		<pubDate>Sat, 12 May 2012 11:38:27 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[友言]]></category>
		<category><![CDATA[小工具]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[评论]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=152</guid>
		<description><![CDATA[本文同步自（如浏览不正常请点击跳转）：https://zohead.com/archives/wordpress-uyan-recent-comment-widget-v1-1 前两天写了一个友言近期评论的 WordPress Widget，初步测试还能用，但多次进行刷新，会出现不能正常登录之类的问题（Failed to authentication with uyan.cc、Invalid comments data from uyan.cc 之类的报错），而且原来每次都需要登录友言的后台管理严重影响速度，我也比较怕怕这样不断发请求会导致友言把我的脚本和博客给封掉，嘿嘿。 由于友言没有公开  [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文同步自（如浏览不正常请点击跳转）：<a href="https://zohead.com/archives/wordpress-uyan-recent-comment-widget-v1-1" target="_blank">https://zohead.com/archives/wordpress-uyan-recent-comment-widget-v1-1</a></p>
<p>前两天写了一个友言近期评论的 WordPress Widget，初步测试还能用，但多次进行刷新，会出现不能正常登录之类的问题（Failed to authentication with uyan.cc、Invalid comments data from uyan.cc 之类的报错），而且原来每次都需要登录友言的后台管理严重影响速度，我也比较怕怕这样不断发请求会导致友言把我的脚本和博客给封掉，嘿嘿。</p>
<p>由于友言没有公开 API 给我等码农来调用，因此今天又花了点时间做了一些改进，弄了个 1.1 版本，原介绍文章在这：</p>
<p><a href="https://zohead.com/archives/wordpress-uyan-recent-comment-widget/" target="_blank">https://zohead.com/archives/wordpress-uyan-recent-comment-widget/</a></p>
<p><strong><span style="color: #ff0000;">1.1版本更新说明：</span></strong></p>
<p><em>1、登录友言后台管理，得到近期评论等地方增加错误处理，如果服务端返回的 JSON 数据不对则报错</em><br />
<em> 2、减少 PHP 本身的 error message 输出</em><br />
<em> 3、将得到近期评论的功能分离为一个函数单独调用</em><br />
<em> 4、登录成功之后保存得到的 cookies 到同目录下的 uyan_cookie.php 文件中方便下次直接使用，而无需再重复登录，如果 cookie 过期下次获取评论时失败则再重新登录</em><br />
<em> 5、发查询近期评论请求时增加友言本身的管理地址作为 referer</em></p>
<p>此次增加了一个 uyan_cookie.php 文件用于保存 cookie，从下面的链接下载解压缩之后，请将此文件也上传到同一目录中（不上传的话 PHP 可能会报 require_once 出错），此文件默认为空，详细代码就不贴了，直接在下面下载查看吧。友言 的登录邮箱、密码、域名之类的修改请参考原始的介绍文章了。</p>
<p>这个是 1.1 版本的下载地址：</p>
<p><a href="http://miseal.googlecode.com/files/uyan_comments_v1.1.zip" target="_blank">http://miseal.googlecode.com/files/uyan_comments_v1.1.zip</a></p>
<p>当前的通过临时文件保存 cookie 方式应该还有改进空间，有任何问题欢迎指正咯 ^_^</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/wordpress-uyan-recent-comment-widget-v1-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP实现WordPress友言近期评论Widget</title>
		<link>https://zohead.com/archives/wordpress-uyan-recent-comment-widget/</link>
		<comments>https://zohead.com/archives/wordpress-uyan-recent-comment-widget/#comments</comments>
		<pubDate>Fri, 11 May 2012 16:16:35 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[post-to-host]]></category>
		<category><![CDATA[友言]]></category>
		<category><![CDATA[小工具]]></category>
		<category><![CDATA[社交网络]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[评论]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=120</guid>
		<description><![CDATA[本文同步自（如浏览不正常请点击跳转）：https://zohead.com/archives/wordpress-uyan-recent-comment-widget/ 现在有很多人都使用 WordPress 来搭建自己的博客系统，其中有一些是像我这样使用 友言 这个社交评论插件来替代系统原始的评论框的，友言 评论框插件对一些主流的社交网站支持比较好，很是推荐，友言官网： http://uyan.cc/ 之前我写过一个修改 友言 插件实现完美与 WordPress Mobile Pack 插件配合实现移动版博客的文章，在这里仅供参考： https://zohead.com/archives/m [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文同步自（如浏览不正常请点击跳转）：<a href="https://zohead.com/archives/wordpress-uyan-recent-comment-widget/" target="_blank">https://zohead.com/archives/wordpress-uyan-recent-comment-widget/</a></p>
<p>现在有很多人都使用 WordPress 来搭建自己的博客系统，其中有一些是像我这样使用 友言 这个社交评论插件来替代系统原始的评论框的，友言 评论框插件对一些主流的社交网站支持比较好，很是推荐，友言官网：</p>
<p><a href="http://uyan.cc/" target="_blank">http://uyan.cc/</a></p>
<p>之前我写过一个修改 友言 插件实现完美与 WordPress Mobile Pack 插件配合实现移动版博客的文章，在这里仅供参考：</p>
<p><a href="https://zohead.com/archives/modify-plugin-wordpress-mobile-pack/" target="_blank">https://zohead.com/archives/modify-plugin-wordpress-mobile-pack/</a></p>
<p>但现在发现还是有一点不足，由于使用了友言评论框替代 WordPress 本身的，导致 WordPress 的 ”近期评论“ Widget（小工具）显示不了，因此小折腾一个晚上，靠着还依稀记得的 PHP 编程经历，写了一个简单的显示 友言 近期评论的 WordPress Widget，先看效果图（本博客右侧就有 ^_^）：</p>
<p><a href="http://zohead.com/wp-content/uploads/wordpress-uyan-recent-comment-widget.png" target="_blank"><img src="http://zohead.com/wp-content/uploads/wordpress-uyan-recent-comment-widget.png" alt="友言近期评论Widget效果图" width="228" height="152" /></a></p>
<p><span style="color: #f00;"><strong>原理及代码：<br />
</strong></span></p>
<p>实现原理比较简单，先用工具分析 友言 评论的后台登录和评论显示之类的 HTTP 包信息，然后用 PHP 的 fsockopen 来自己发送 GET、POST 请求实现在 WordPress 上列举显示已经存在的友言评论（<span style="color: #006400;">备注：默认只显示已经经过审核的评论</span>）。</p>
<p>由于中间有几步 GET 和 POST 请求，就找了个现成的 <strong>post-to-host</strong> 这个很小的 PHP 脚本来发 GET 和 POST 请求，下载地址：</p>
<p><a href="http://code.google.com/p/post-to-host/" target="_blank">http://code.google.com/p/post-to-host/</a></p>
<p>下面是主程序 <span style="color: #006400;"><strong>uyan_comments.php</strong></span> 的代码：</p>
<pre class="brush: php; highlight: [3,4,5,6,8,13,36,40,72]; title: uyan_comments.php; notranslate">
&lt;?php
// global site information
$email = &quot;xxxx login email xxxx&quot;;
$password = &quot;xxxx encrypted password xxxx&quot;;
$domain = &quot;xxxx domain name xxxx&quot;;
$maxcomments = 10;

require_once(&quot;post_to_host.php&quot;);

header(&quot;Content-Type:text/html; charset=utf-8&quot;);

// Login to uyan.cc
$url = &quot;http://uyan.cc/index.php/youyan_login/userAutoLoginCrossDomain?email=&quot; . $email . &quot;&amp;loginPassword=&quot; . $password . &quot;&amp;rem=1&amp;domain=&quot; . $domain;

$ret_str = post_to_host($url, array(), array(), &quot;&quot;, $ret_head, 0);
$ret_str = trim($ret_str, &quot;()&quot;);

if ($ret_str == null || $ret_str == 'noData' || $ret_str == '&quot;noData&quot;') {
	echo(&quot;Failed to authentication with uyan.cc&quot;);
	return;
}

$ret_json = json_decode($ret_str, true);

// check response JSON data
if (!array_key_exists('uid', $ret_json) || !array_key_exists('uname', $ret_json)) {
	echo(&quot;Invalid data from uyan.cc&quot;);
	return;
}

// delete unneeded 'auth' cookie key
$arr_cookie = get_cookies_from_heads($ret_head);
unset($arr_cookie['auth']);

// need this to set uid and uname
$url = &quot;http://uyan.cc/setting?uid=&quot; . $ret_json['uid'] . &quot;&amp;domain=&quot; . $domain . &quot;&amp;uname=&quot; . $ret_json['uname'];
$ret_str = post_to_host($url, array(), $arr_cookie, &quot;&quot;, $ret_head, 0);

// get comment
$url = &quot;http://uyan.cc/youyan_admin/getMoreCommentsByDomain/0&quot;;
$get_comment_params = array('currentMore' =&gt; '0', 'normalCommentToogle' =&gt; '1', 'readyCommentToogle' =&gt; '0', 'trashCommentToogle' =&gt; '0', 'delCommentToogle' =&gt; '0');

$ret_str = post_to_host($url, $get_comment_params, $arr_cookie, &quot;&quot;, $ret_head);

$comment_json = json_decode($ret_str, true);
$nr_comments = 0;

echo(&quot;&lt;ul&gt;&quot;);

foreach ($comment_json as $comment) {
	$display_name = &quot;&quot;;
	$display_title = &quot;&quot;;

	if ($maxcomments &gt;= 0 &amp;&amp; $nr_comments &gt;= $maxcomments) break;

	// must with valid URL and page title
	if (!array_key_exists('page_url', $comment) || !array_key_exists('page_title', $comment) || strlen($comment['page_url']) &lt;= 0 || strlen($comment['page_title']) &lt;= 0)
		continue;

	// must with a valid display name
	if (array_key_exists('comment_author', $comment) &amp;&amp; strlen($comment['comment_author']) &gt; 0)
		$display_name = $comment['comment_author'];
	else if (array_key_exists('show_name', $comment) &amp;&amp; strlen($comment['show_name']) &gt; 0)
		$display_name = $comment['show_name'];
	else
		continue;

	$display_title = $comment['page_title'];
	$pos = strpos($display_title, ' | ');
	if ($pos &gt;= 0) $display_title = substr($display_title, 0, $pos);

	echo('&lt;li&gt;' . $display_name . '&amp;nbsp;&lt;strong&gt;&lt;font color=&quot;#0000FF&quot;&gt;&amp;gt;&amp;gt;&lt;/font&gt;&lt;/strong&gt;&amp;nbsp;&lt;a href=&quot;' . $comment['page_url'] . '&quot;&gt;' . $display_title . '&lt;/a&gt;&lt;/li&gt;');

	$nr_comments++;
}

echo(&quot;&lt;/ul&gt;&quot;);
?&gt;
</pre>
<p>代码本身比较简单，有些 PHP 基础就可以看懂了，把 <span style="color: #006400;"><strong>uyan_comments.php</strong></span> 文件最上面的 <span style="color: #006400;"><strong>$email</strong></span>、<span style="color: #006400;"><strong>$password</strong></span>、<span style="color: #006400;"><strong>$domain</strong></span>、<span style="color: #006400;"><strong>$maxcomments</strong></span> 改为实际的 友言 后台管理的登录邮箱、密码、你的域名、显示的最多评论数（备注：默认为10条，如果改为小于0的值则不限制显示的评论条数），就可以使用了。你应该已经发现这个 <span style="color: #006400;"><strong>uyan_comments.php</strong></span> 其实和 WordPress 没太大关系，完全也可以直接单独使用。</p>
<p>需要注意的是为了避免使用明文密码而可能导致的问题（安全第一 ^_^），上面的 <span style="color: #006400;"><strong>$password</strong></span> 是 友言 后台管理时实际用到的加密过的密码。这个加密过的密码可以通过 Firefox 的 Live HTTP headers 插件之类的抓取 HTTP 协议头的插件或工具来得到。</p>
<p><span style="color: #f00;"><strong>得到友言的加密登录密码：</strong></span></p>
<p>下面以 Firefox 的 Live HTTP headers 插件为例说明如何得到 友言 的实际加密的密码，打开 Live HTTP headers，该插件会自动开始抓取，然后用正确的邮箱和密码登录 友言 的后台管理，停止 Live HTTP headers 的抓取，在输出里就能找到地址为如下格式的 GET 请求，请求参数中就有加密的密码：</p>
<p><strong>http://uyan.cc/index.php/youyan_login/userAutoLoginCrossDomain?callback=jsonpxxxxxxx&amp;email=xxxxx@xxxxx.com&amp;loginPassword=<span style="color: #b22222;">xxxxxxxxxxxxxxxxx</span>&amp;rem=1&amp;domain=xxx.com<br />
</strong></p>
<p>其中的 <span style="color: #006400;"><strong>email</strong></span> 段就是登录邮箱，<span style="color: #006400;"><strong>loginPassword</strong></span> 段即为加密的密码，保存下该密码，修改 <span style="color: #006400;"><strong>uyan_comments.php</strong></span> 文件中的 <span style="color: #006400;"><strong>$email</strong></span> 和 <span style="color: #006400;"><strong>$password</strong></span> 值。</p>
<p>Live HTTP headers 的抓取截图如下所示（后面的未显示完整）：</p>
<p><a href="http://zohead.com/wp-content/uploads/uyan-login-http-headers.png" target="_blank"><img src="http://zohead.com/wp-content/uploads/uyan-login-http-headers.png" alt="友言后台管理的登录HTTP包" width="588" height="478" /></a></p>
<p><span style="color: #f00;"><strong>如何加入 WordPress Widget 列表中：</strong></span></p>
<p>你如果有真正实现一个 WordPress Widget 的心思，可以用本代码加上 WordPress 的 register_widget 之类的接口来实现。无奈我是一个超级懒人，懒人就有懒人的办法，下面介绍的就是懒人的办法，哈哈。</p>
<p>首先下载本文最下面下载链接中的 <span style="color: #006400;"><strong>post_to_host.php</strong></span> 和 <span style="color: #006400;"><strong>uyan_comments.php</strong></span>，将 <span style="color: #006400;"><strong>uyan_comments.php</strong></span> 中对应的 登录邮箱、密码、域名 改掉（参考上面），将这两个文件上传到 WordPress 根目录中（位置也可以自己修改），然后给 WordPress 安装 <strong>PHP Code Widget</strong> 插件，这是一个通用 Widget，添加之后，可以自行添加 文本、HTML、PHP 代码等，比较方便，插件地址：</p>
<p><a href="http://wordpress.org/extend/plugins/php-code-widget/" target="_blank">http://wordpress.org/extend/plugins/php-code-widget/</a></p>
<p>安装之后，在 WordPress 管理后台的 外观 - 小工具 里就能看到名为 “PHP Code”  的小工具，将其托至右侧的 “第一小工具区域”，输入自定义的标题，然后加入以下代码保存即可（如果上传的位置不在 WordPress 根目录那请自行修改）：</p>
<pre class="brush: php; highlight: [2]; title: 小工具的PHP代码; notranslate">
&lt;?php
include_once(&quot;uyan_comments.php&quot;);
?&gt;
</pre>
<p>重新访问 WordPress，如果上面的 登录邮箱、密码、域名 设定都正确的话，应该就可以出现类似上面效果图的评论列表。</p>
<p><span style="color: #f00;"><strong>不足和改进：</strong></span></p>
<p>1、uyan_comments.php 每次访问时都需要登录 友言 管理后台，请求评论列表，因此速度会有些影响，这个有空再改进了。</p>
<p>2、由于需要在 Web服务器中使用 PHP 的 fsockopen 来发 GET、POST 请求得到评论列表，因此可能对 WordPress 博客的访问速度造成一些影响，如果 PHP 空间在国内还可以，像我这样空间在国外的就稍微悲剧点了，所以建议安装 WP Super Cache WordPress 之类的插件来实现更好的缓存加速，将影响尽量降低。</p>
<p><span style="color: #f00;"><strong>下载地址（115网盘）：</strong></span></p>
<p><a href="http://115.com/file/beezjk3b" target="_blank">http://115.com/file/beezjk3b</a></p>
<p>写博客好累，准备休息，HOHO，本文件为个人作品，有任何问题欢迎指正。 ^_^</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/wordpress-uyan-recent-comment-widget/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>bind2nd普通二元函数时无法使用引用类型参数的问题</title>
		<link>https://zohead.com/archives/cplusplus-bind2nd-reference/</link>
		<comments>https://zohead.com/archives/cplusplus-bind2nd-reference/#comments</comments>
		<pubDate>Wed, 09 May 2012 17:32:03 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[boost]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[bind2nd]]></category>
		<category><![CDATA[find_if]]></category>
		<category><![CDATA[ptr_fun]]></category>
		<category><![CDATA[STL]]></category>
		<category><![CDATA[引用]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=115</guid>
		<description><![CDATA[本文同步自（如浏览不正常请点击跳转）：https://zohead.com/archives/cplusplus-bind2nd-reference/ 在使用 STL 的 find_if、count_if 等函数时会发现这些函数使用的参数不是普通的函数指针，而是 functional 函数对象，实际使用函数对象时可以自己定义一个仿函数来实现（实现带参数的 operator () 即可），这个相对比较简单就不写出来了。但有些情况需要直接使用普通的二元函数指针，这时可以使用 ptr_fun 将函数指针转换为函数对象作为 find_if、count_if 等的参数。 先看一个能正常工作的二元函数不使 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文同步自（如浏览不正常请点击跳转）：<a href="https://zohead.com/archives/cplusplus-bind2nd-reference/" target="_blank">https://zohead.com/archives/cplusplus-bind2nd-reference/</a></p>
<p>在使用 STL 的 find_if、count_if 等函数时会发现这些函数使用的参数不是普通的函数指针，而是 functional 函数对象，实际使用函数对象时可以自己定义一个仿函数来实现（实现带参数的 operator () 即可），这个相对比较简单就不写出来了。但有些情况需要直接使用普通的二元函数指针，这时可以使用 ptr_fun 将函数指针转换为函数对象作为 find_if、count_if 等的参数。</p>
<p>先看一个能正常工作的二元函数不使用引用类型参数的代码：</p>
<pre class="brush: cpp; highlight: [15,24,27,43]; title: cat &gt; test.cpp; notranslate">
#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;functional&gt;

using namespace std;

class sss
{
public:
	explicit sss(int val) : value(val) {}

	sss(const sss&amp; org)
	{
		cout &lt;&lt; &quot;---copy &quot; &lt;&lt; &amp;org &lt;&lt; &quot; to &quot; &lt;&lt; this &lt;&lt; endl;
		value = org.value;
	}

	virtual ~sss() {}

	int value;
};

bool find_sss(sss s_chk, int val)
{
	bool ret = (s_chk.value == val);
	s_chk.value = 222;
	return ret;
}

int main(int argc, char ** argv)
{
	vector&lt;sss&gt; vvv;
	vector&lt;sss&gt;::iterator iii;

	vvv.push_back(sss(11));
	vvv.push_back(sss(12));
	vvv.push_back(sss(13));
	vvv.push_back(sss(14));
	vvv.push_back(sss(15));

	cout &lt;&lt; &quot;before find_if&quot; &lt;&lt; endl;
	iii = find_if(vvv.begin(), vvv.end(), bind2nd(ptr_fun(find_sss), 13));

	if (iii == vvv.end())
		cout &lt;&lt; &quot;not found&quot; &lt;&lt; endl;
	else
		cout &lt;&lt; &quot;index: &quot; &lt;&lt; (iii - vvv.begin()) &lt;&lt; &quot;, value: &quot; &lt;&lt; iii-&gt;value &lt;&lt; endl;

	return 0;
}
</pre>
<p>程序很简单，从 vector 中查找符合条件的 sss 对象，find_sss 就是要转换的二元函数指针，第一个参数是 sss 类，通过 ptr_fun 可以使本代码正常工作。</p>
<p>通过下面的运行输出能看出调用 find_sss 时进行了拷贝构造（本程序的编译环境为：Windows 7 32bit, Mingw gcc 3.4.5，Visual Studio 2010中稍有不同，主要在前面的拷贝次数上）：<br />
<strong><span style="color: #800000;"><br />
---copy 0x22ff10 to 0x552a58<br />
---copy 0x552a58 to 0x552ad8<br />
---copy 0x22ff10 to 0x552ae0<br />
---copy 0x552ad8 to 0x552af0<br />
---copy 0x552ae0 to 0x552af8<br />
---copy 0x22ff10 to 0x552b00<br />
---copy 0x22ff10 to 0x552b08<br />
---copy 0x552af0 to 0x552b18<br />
---copy 0x552af8 to 0x552b20<br />
---copy 0x552b00 to 0x552b28<br />
---copy 0x552b08 to 0x552b30<br />
---copy 0x22ff10 to 0x552b38<br />
before find_if<br />
---copy 0x552b18 to 0x22fdd0<br />
---copy 0x22fdd0 to 0x22fd40<br />
---copy 0x552b20 to 0x22fdd0<br />
---copy 0x22fdd0 to 0x22fd40<br />
---copy 0x552b28 to 0x22fdd0<br />
---copy 0x22fdd0 to 0x22fd40<br />
index: 2, value: 13<br />
</span></strong></p>
<p>接下来就是实际碰到的问题了，如果将 find_sss 的第一个参数改为 sss 的引用，即第 24 行改为：</p>
<p><span style="color: #006400;"><em><strong><span style="color: #008000;">bool find_sss(sss&amp; s_chk, int val)</span><br />
</strong></em></span></p>
<p>上面的代码就会编译出错（以 Visual Studio 2010 的错误输出为例）：</p>
<hr size="1" />
<p><span style="color: #0000ff;">C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xfunctional(341) : error C2535: “bool std::binder2nd&lt;_Fn2&gt;::operator ()(sss &amp;) const”: 已经定义或声明成员函数<br />
with<br />
[<br />
_Fn2=std::pointer_to_binary_function&lt;sss &amp;,int,bool,bool (__cdecl *)(sss &amp;,int)&gt;<br />
]<br />
C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xfunctional(335) : 参见“std::binder2nd&lt;_Fn2&gt;::operator ()”的声明<br />
with<br />
[<br />
_Fn2=std::pointer_to_binary_function&lt;sss &amp;,int,bool,bool (__cdecl *)(sss &amp;,int)&gt;<br />
]<br />
test.cpp(43): 参见对正在编译的类 模板 实例化“std::binder2nd&lt;_Fn2&gt;”的引用<br />
with<br />
[<br />
_Fn2=std::pointer_to_binary_function&lt;sss &amp;,int,bool,bool (__cdecl *)(sss &amp;,int)&gt;<br />
]<br />
</span></p>
<hr size="1" />
<p>网上的码农和攻城师们基本都认为是 STL 本身的问题，传递引用类型参数会造成 reference to reference 问题。</p>
<p>几番尝试之后，发现的解决方法如下：</p>
<p><strong>1、Visual Studio 2010下的不完美解决方法：</strong></p>
<p>将第 43 行改为：</p>
<p><strong><span style="color: #006400;">    iii = find_if(vvv.begin(), vvv.end(), bind2nd(pointer_to_binary_function&lt;sss, int, bool, bool(*)(sss&amp;, int)&gt;(find_sss), 13));<br />
</span></strong></p>
<p>这样通过自己给 pointer_to_binary_function 设置模板参数避免编译出错，实际运行中会发现 find_if 查找可以正常工作了，但第 27 行中修改引用类的值会没有效果，因为这种方式不是真正的引用，仍然有拷贝构造，由于需要用到这些二元函数的场合一般不需要修改数据，使用起来没有太大问题。</p>
<p><strong>2、gcc下的不完美解决方法：</strong></p>
<p>如果 gcc 下用上面的改动，你会发现由于 gcc 下 pointer_to_binary_function 只有 3 个参数，无法顺利修改编译，但可以这样折中，将第 43 行改为：</p>
<p><strong><span style="color: #006400;">    iii = find_if(vvv.begin(), vvv.end(), bind2nd(ptr_fun((bool(*)(sss, int)) find_sss), 13));<br />
</span></strong></p>
<p>通过强制类型转换来实现，稍显恶心，查找可以正常工作，但依然是拷贝构造。需要注意此方法如果在 Visual Studio 中使用会出错。</p>
<p><strong>3、终极解决方案- 使用 boost 库：</strong></p>
<p>首先头文件中增加：</p>
<p><strong><span style="color: #006400;">#include &lt;boost/functional.hpp&gt;</span></strong></p>
<p>然后原来的第 43 行改为：</p>
<p><strong><span style="color: #006400;">    iii = find_if(vvv.begin(), vvv.end(), boost::bind2nd(boost::ptr_fun(find_sss), 13));<br />
</span></strong></p>
<p>编译运行之后发现 find_if 查找可以正常工作，而且现在是真正的引用，只能说 boost 的 functional 相比 STL 的实在是好强大，哈哈。</p>
<p>运行输出如下（使用 Mingw gcc 3.4.5 编译），find_if 找到的值已经被 find_sss 修改：</p>
<p><strong><span style="color: #800000;">---copy 0x22ff10 to 0x7f2a58<br />
---copy 0x7f2a58 to 0x7f2ad8<br />
---copy 0x22ff10 to 0x7f2ae0<br />
---copy 0x7f2ad8 to 0x7f2af0<br />
---copy 0x7f2ae0 to 0x7f2af8<br />
---copy 0x22ff10 to 0x7f2b00<br />
---copy 0x22ff10 to 0x7f2b08<br />
---copy 0x7f2af0 to 0x7f2b18<br />
---copy 0x7f2af8 to 0x7f2b20<br />
---copy 0x7f2b00 to 0x7f2b28<br />
---copy 0x7f2b08 to 0x7f2b30<br />
---copy 0x22ff10 to 0x7f2b38<br />
before find_if<br />
<span style="color: #f00;">index: 2, value: 222</span><br />
</span></strong></p>
<p>以上仅为个人小心得，有任何问题欢迎指正，玩的开心咯 ^_^</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/cplusplus-bind2nd-reference/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>C++虚基类的实现验证</title>
		<link>https://zohead.com/archives/cplusplus-virtual-base-class/</link>
		<comments>https://zohead.com/archives/cplusplus-virtual-base-class/#comments</comments>
		<pubDate>Mon, 07 May 2012 13:36:07 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[虚基类]]></category>
		<category><![CDATA[虚继承]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=111</guid>
		<description><![CDATA[本文同步自（如浏览不正常请点击跳转）：https://zohead.com/archives/cplusplus-virtual-base-class/ 在C++的某些应用环境中，如果有多个派生类同时继承同一个基类，但只要想基类成员的一份拷贝，这样可以把对基类的继承声明为虚拟（virtual）的，以达到需要的目的，用 virtual 限定的继承即为虚继承，对应的基类即为虚基类，没有则为非虚基类。在派生类产生的对象中，同名的虚基类只产生一个虚基类对象，非虚基类则有各自的对象，需要注意虚基类和抽象类的区分。 另外特别需要注意的是一个基类即可以被用作虚基类，也可以被用作非虚基类，这纯粹看是否是虚继承 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文同步自（如浏览不正常请点击跳转）：<a href="https://zohead.com/archives/cplusplus-virtual-base-class/" target="_blank"><span id="sample-permalink">https://zohead.com/archives/<span id="editable-post-name" title="临时固定链接，点击可更改这部分。">cplusplus-virtual-base-class</span>/</span></a></p>
<p>在C++的某些应用环境中，如果有多个派生类同时继承同一个基类，但只要想基类成员的一份拷贝，这样可以把对基类的继承声明为虚拟（virtual）的，以达到需要的目的，用 virtual 限定的继承即为虚继承，对应的基类即为虚基类，没有则为非虚基类。在派生类产生的对象中，同名的虚基类只产生一个虚基类对象，非虚基类则有各自的对象，需要注意虚基类和抽象类的区分。</p>
<p>另外特别需要注意的是一个基类即可以被用作虚基类，也可以被用作非虚基类，这纯粹看是否是虚继承（是否有 virtual 限定）。虚基类子对象是由最远派生类（也就是建立对象时所指定的类）的构造函数调用虚基类的构造函数进行初始化。如果一个派生类继承了多个基类，其中既有虚基类，也有非虚基类，则虚基类构造函数优先于非虚基类的构造函数，而不管实际书写顺序，而且如果该派生类的构造函数初始化列表中同时有对虚基类和非虚基类构造函数的调用，同样也是虚基类优先。</p>
<p>有关虚基类的其它说明请参考其它书籍咯，下面是测试的代码：</p>
<pre style="border:0px;"><pre class="brush: cpp; highlight: [8,22,31,40,43]; title: cat &gt; test.cpp; notranslate">
#include &lt;iostream&gt;

using namespace std;

class Base
{
public:
	explicit Base(int val) : value(val)
	{
		cout &lt;&lt; &quot;init Base &quot; &lt;&lt; this &lt;&lt; &quot;, value:&quot; &lt;&lt; value &lt;&lt; endl;
	}

	~Base()
	{
		cout &lt;&lt; &quot;destory Base &quot; &lt;&lt; this &lt;&lt; &quot;, value:&quot; &lt;&lt; value &lt;&lt; endl;
	}

private:
	int value;
};

class Base2 : public virtual Base
{
public:
	Base2(int val) : Base(val)
	{
		cout &lt;&lt; &quot;init Base2&quot; &lt;&lt; endl;
	}
};

class Base3 : public virtual Base
{
public:
	Base3(int val) : Base(val)
	{
		cout &lt;&lt; &quot;init Base3&quot; &lt;&lt; endl;
	}
};

class Base4 : public Base2, public virtual Base3
{
public:
	Base4(int val) : Base2(val), Base3(val), Base(val)
	{
	}
};

int main(int argc, char ** argv)
{
	Base4 base_4(22);

	return 0;
}
</pre>
<p>比较简单，Base2 和 Base3 分别派生自 Base，而且都是虚继承，Base4 同时派生自 Base2 和 Base3，但 Base3 前有 virtual，表示 Base4 对 Base3 是虚继承，Base4 对 Base2 是非虚继承。另外代码中比较特殊的一点是 Base 基类中去掉了默认构造函数，改为一个 int 参数的构造函数（第 8 行），因此 Base2 和 Base3 的构造函数的初始化列表中都需要增加对基类 Base 构造函数的调用，同样 Base4 需要调用 Base、Base2 和 Base3。</p>
<p>主程序中只是生成一个 Base4 的对象，上述代码的运行输出为：</p>
<p><strong><span style="color:#b22222;">init Base 0x22ff34, value:22<br />
	init Base3<br />
	init Base2<br />
	destory Base 0x22ff34, value:22<br />
	</span></strong></p>
<p>由上面的输出可以验证虚基类的概念：</p>
<p>由于 Base2 和 Base3 都是对 Base 的虚继承，虽然 Base4 中分别调用了 Base2 和 Base3 的构造函数，但虚继承就表示基类 Base 只有一份拷贝，Base 只需要构造一次。另外由于 Base4 对 Base2 是非虚继承，对 Base3 是虚继承，尽管 Base2 看起来在前面调用，但由于 Base3 对 Base4 是虚基类，它的构造函数就需要先于 Base2 对调用，因此 &rdquo;init Base3&ldquo; 在 &rdquo;init Base2&ldquo; 前面。</p>
<p>接着对代码稍作改动，将 Base2 和 Base3 都改为非虚继承，Base4 中去掉对 Base 基类构造函数的调用：</p>
<p><span style="color:#006400;"><em>第22行：class Base2 : public Base<br />
	第31行：class Base3 : public Base<br />
	第43行：&nbsp;&nbsp;&nbsp; Base4(int val) : Base2(val), Base3(val)<br />
	</em></span></p>
<p>编译运行输出，结果为：</p>
<p><span style="color:#b22222;"><strong>init Base 0x22ff38, value:22<br />
	init Base3<br />
	init Base 0x22ff34, value:22<br />
	init Base2<br />
	destory Base 0x22ff34, value:22<br />
	destory Base 0x22ff38, value:22<br />
	</strong></span></p>
<p>可以看到由于 Base2 和 Base3 都不是非虚继承，Base 就被构造了两次。</p>
<p>在上面的基础上再做个小改动，把 Base4 对 Base3 的派生也改为非虚继承：</p>
<p><span style="color:#006400;"><em>第40行：class Base4 : public Base2, public Base3<br />
	</em></span></p>
<p>再次运行输出，结果即为：</p>
<p><span style="color:#b22222;"><strong>init Base 0x22ff30, value:22<br />
	init Base2<br />
	init Base 0x22ff34, value:22<br />
	init Base3<br />
	destory Base 0x22ff34, value:22<br />
	destory Base 0x22ff30, value:22<br />
	</strong></span></p>
<p>由于 Base4 对 Base2 和 Base3 都不是虚继承了，对 Base2 和 Base3 的构造函数调用就按实际顺序了。</p>
<p>这样虚基类的概念就算是比较清楚咯。 ^_^</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/cplusplus-virtual-base-class/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>vector的push_back拷贝构造和空间占用分析</title>
		<link>https://zohead.com/archives/vector-push-back-space-copy/</link>
		<comments>https://zohead.com/archives/vector-push-back-space-copy/#comments</comments>
		<pubDate>Fri, 04 May 2012 18:30:56 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[push_back]]></category>
		<category><![CDATA[STL]]></category>
		<category><![CDATA[拷贝]]></category>
		<category><![CDATA[构造]]></category>
		<category><![CDATA[析构]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=104</guid>
		<description><![CDATA[本文同步自：https://zohead.com/archives/vector-push-back-space-copy/ 这两天在实际程序中使用 STL 的 vector push_back 类对象时出现问题，偶尔发现 vector 在 push_back 时的调用类对象的拷贝构造函数和析构函数有点特别，简单做下分析。 程序代码： 功能很简单，main 中定义一个 sss 类对象和对应的 vector，然后在循环中改类成员的值，并依次 push_back 到 vector 中，类的构造函数、析构函数、拷贝构造函数中都加了对应的打印输出。循环运行了5次，往 vector 中增加了5个类成员。 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文同步自：<a href="https://zohead.com/archives/vector-push-back-space-copy/" target="_blank">https://zohead.com/archives/vector-push-back-space-copy/</a></p>
<p>这两天在实际程序中使用 STL 的 vector push_back 类对象时出现问题，偶尔发现 vector 在 push_back 时的调用类对象的拷贝构造函数和析构函数有点特别，简单做下分析。</p>
<p><span style="color: #f00;"><strong>程序代码：</strong></span></p>
<p><pre class="brush: cpp; highlight: [11,16,22,36]; title: cat &gt; test.cpp; notranslate">
#include &lt;iostream&gt;
#include &lt;vector&gt;

using namespace std;

struct sss
{
public:
	explicit sss(int val) : value(val)
	{
		cout &lt;&lt; &quot;---init sss &quot; &lt;&lt; this &lt;&lt; &quot;, value:&quot; &lt;&lt; value &lt;&lt; endl;
	}

	sss(const sss&amp; org)
	{
		cout &lt;&lt; &quot;---copy &quot; &lt;&lt; &amp;org &lt;&lt; &quot; to &quot; &lt;&lt; this &lt;&lt; endl;
		value = org.value;
	}

	~sss()
	{
		cout &lt;&lt; &quot;---destory sss &quot; &lt;&lt; this &lt;&lt; &quot;, value:&quot; &lt;&lt; value &lt;&lt; endl;
	}
 
	int value;
};

int main(int argc, char ** argv)
{
	sss s_tmp(11);
	int i = 0;
	vector&lt;sss&gt; vvv;

	for (i = 0; i &lt; 5; i++) {
		s_tmp.value++;
		vvv.push_back(s_tmp);
		cout &lt;&lt; &quot;size: &quot; &lt;&lt; vvv.size() &lt;&lt; &quot;, capacity: &quot; &lt;&lt; vvv.capacity() &lt;&lt; endl;
	}

	return 0;
}
</pre>
</p>
<p>功能很简单，main 中定义一个 sss 类对象和对应的 vector，然后在循环中改类成员的值，并依次 push_back 到 vector 中，类的构造函数、析构函数、拷贝构造函数中都加了对应的打印输出。循环运行了5次，往 vector 中增加了5个类成员。</p>
<p>实际运行输出如下：</p>
<hr size="1" />
<p>---init sss 0x22ff20, value:11<br /> ---copy 0x22ff20 to 0x5d2a58<br /> size: 1, capacity: 1<br /> ---copy 0x5d2a58 to 0x5d2ad8<br /> ---copy 0x22ff20 to 0x5d2adc<br /> ---destory sss 0x5d2a58, value:12<br /> size: 2, capacity: 2<br /> ---copy 0x5d2ad8 to 0x5d2ae8<br /> ---copy 0x5d2adc to 0x5d2aec<br /> ---copy 0x22ff20 to 0x5d2af0<br /> ---destory sss 0x5d2ad8, value:12<br /> ---destory sss 0x5d2adc, value:13<br /> size: 3, capacity: 4<br /> ---copy 0x22ff20 to 0x5d2af4<br /> size: 4, capacity: 4<br /> ---copy 0x5d2ae8 to 0x5d2b00<br /> ---copy 0x5d2aec to 0x5d2b04<br /> ---copy 0x5d2af0 to 0x5d2b08<br /> ---copy 0x5d2af4 to 0x5d2b0c<br /> ---copy 0x22ff20 to 0x5d2b10<br /> ---destory sss 0x5d2ae8, value:12<br /> ---destory sss 0x5d2aec, value:13<br /> ---destory sss 0x5d2af0, value:14<br /> ---destory sss 0x5d2af4, value:15<br /> size: 5, capacity: 8<br /> ---destory sss 0x5d2b00, value:12<br /> ---destory sss 0x5d2b04, value:13<br /> ---destory sss 0x5d2b08, value:14<br /> ---destory sss 0x5d2b0c, value:15<br /> ---destory sss 0x5d2b10, value:16<br /> ---destory sss 0x22ff20, value:16</p>
<hr size="1" />
<p><span style="color: #f00;"><strong>结果分析：<br /> </strong></span></p>
<p>vector 每次调用 push_back 时都会拷贝一个新的参数指定的 sss 类对象，这会调用 sss 的拷贝构造函数，第一次的 copy 正常，而且 vector 的实际容量也由 0  变为 1。</p>
<p>第二次调用 push_back，通过输出会发现调用了两次拷贝构造函数，一次析构函数，原来 vector 此时判断容量不够，将容量扩大为原来的两倍，变为 2，并将原来的元素再次拷贝一份存放到新的内存空间，然后拷贝新加的类对象，最后再释放原来的元素。</p>
<p>第三次调用 push_back 时，vector 自动扩大为4，因此拷贝构造函数调用了3次，析构函数调用了2次，程序最终退出了时就析构了 5 次加本身的 sss 类对象一共 6 次。</p>
<p><span style="color: #f00;"><strong>参考：</strong></span></p>
<p>由此看来，vector 的 push_back 在发现空间不足时自动将空间以 2 的指数增长：0 -&gt; 1 -&gt; 2 -&gt; 4 -&gt; 8 -&gt; 16 -&gt; 32 ...</p>
<p>查找资料后得知，如此设计的主要目的是为了尽可能的减小时间复杂度；如果每次都按实际的大小来增加 vector 的空间，会造成时间复杂度很高，降低 push_back 的速度。</p>
<p>另外关于 push_back 为什么会执行拷贝构造函数，push_back 的原型为：</p>
<p><em><span style="color: #008000;">void push_back(const _Ty&amp; _Val)</span></em></p>
<p>参数是以引用方式传递，按说不会拷贝，但 push_back 实际实现中判断空间不足时是调用 insert 函数添加元素：</p>
<p><em><span style="color: #008000;">void push_back(const _Ty&amp; _Val)</span></em><br /> <em><span style="color: #008000;">{</span></em><br /> <em><span style="color: #008000;">   // insert element at end</span></em><br /> <em><span style="color: #008000;">   if (size() &lt; capacity())</span></em><br /> <em><span style="color: #008000;">   #if _HAS_ITERATOR_DEBUGGING</span></em><br /> <em><span style="color: #008000;">   {</span></em><br /> <em><span style="color: #008000;">      // room at end, construct it there</span></em><br /> <em><span style="color: #008000;">      _Orphan_range(_Mylast, _Mylast);</span></em><br /> <em><span style="color: #008000;">      _Mylast = _Ufill(_Mylast, 1, _Val);</span></em><br /> <em><span style="color: #008000;">   }</span></em><br /> <em><span style="color: #008000;">   #else /* _HAS_ITERATOR_DEBUGGING */</span></em><br /> <em><span style="color: #008000;">      _Mylast = _Ufill(_Mylast, 1, _Val);</span></em><br /> <em><span style="color: #008000;">   #endif /* _HAS_ITERATOR_DEBUGGING */</span></em><br /> <em><span style="color: #008000;">   else</span></em><br /> <em><span style="color: #008000;">      insert(end(), _Val);</span></em><br /> <em><span style="color: #008000;">}</span></em></p>
<p><span style="color: #f00;"><strong>更新：</strong></span></p>
<p>2012-05-10：</p>
<p>近期在 Visual Studio 2010 中发现 vector 的实际空间增加顺序为：1 - 2 - 3 - 4 - 6 - 9 - 13 - 19 - 28 - 42 - 63 - 94 - 141 - 211 ...，有空时再继续研究。</p>
<p>以上只是个人粗略分析，有任何问题欢迎指正，玩的开心咯 ^_^</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/vector-push-back-space-copy/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Linux下出现 pam_get_item 的原因分析</title>
		<link>https://zohead.com/archives/linux-pam-get-item-error-library/</link>
		<comments>https://zohead.com/archives/linux-pam-get-item-error-library/#comments</comments>
		<pubDate>Wed, 11 Apr 2012 16:35:09 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[dlopen]]></category>
		<category><![CDATA[PAM]]></category>
		<category><![CDATA[pam_get_item]]></category>
		<category><![CDATA[RTLD_GLOBAL]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=90</guid>
		<description><![CDATA[本文博客链接：https://zohead.com/archives/linux-pam-get-item-error-library/ 近日在将一个 RHEL6 上的服务程序移植到老的 Fedora Core 2 Linux 上时出现了一些问题，该程序的功能为预先加载几个动态库，这几个动态库中再需要根据不同的需求做 PAM 用户验证操作，程序在 RHEL6 上编译和使用都没有问题，换到 Fedora Core 2 环境之后，编译时一切正常，但在运行时出现比较奇怪的 pam_get_item 错误： pam: PAM [dlerror: /lib/security/../../lib/secu [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文博客链接：<a href="https://zohead.com/archives/linux-pam-get-item-error-library/" target="_blank">https://zohead.com/archives/linux-pam-get-item-error-library/</a></p>
<p>	近日在将一个 RHEL6 上的服务程序移植到老的 Fedora Core 2 Linux 上时出现了一些问题，该程序的功能为预先加载几个动态库，这几个动态库中再需要根据不同的需求做 PAM 用户验证操作，程序在 RHEL6 上编译和使用都没有问题，换到 Fedora Core 2 环境之后，编译时一切正常，但在运行时出现比较奇怪的 pam_get_item 错误：</p>
<p>	<span style="color:#b22222;">pam: PAM [dlerror: /lib/security/../../lib/security/pam_env.so: undefined symbol: pam_get_item]<br />
	pam: PAM [dlerror: /lib/security/../../lib/security/pam_unix.so: undefined symbol: pam_get_item]<br />
	pam: PAM [dlerror: /lib/security/../../lib/security/pam_succeed_if.so: undefined symbol: pam_get_item]<br />
	...</span></p>
<p>	程序所加载的动态库中使用的 PAM 配置文件 <span style="color:#0000cd;">/etc/pam.d/system-auth</span> 里就用到了 <span style="color:#800080;">pam_env.so</span>、<span style="color:#800080;">pam_unix.so</span>、<span style="color:#800080;">pam_succeed_if.so</span> 等几个 PAM 模块。</p>
<p>	但实际系统中还有很多服务都使用 <span style="color:#0000cd;">/etc/pam.d/system-auth</span> 这个 PAM 配置来进行用户验证，都能正常工作，因此做下简单分析。</p>
<p>	由于此服务程序太复杂，就打定写个简单的模拟程序来进行分析。</p>
<p>	<strong>1、动态库部分（checkpam.c）：</strong></p>
<p>	PAM 验证的实现，调用 PAM 函数进行验证，具体 PAM 函数的调用就不详细写出了。</p>
<p><font color="#006400">#include &lt;stdio.h&gt;<br />
	#include &lt;stdlib.h&gt;<br />
	#include &lt;string.h&gt;<br />
	#include &lt;security/pam_appl.h&gt;</p>
<p>	int check_pam(const char * user, const char * pass)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; int ret = -1;</p>
<p>	&nbsp;&nbsp;&nbsp; pam_start(&quot;system-auth&quot;, user, ..., ...);<br />
	&nbsp;&nbsp;&nbsp; ...<br />
	&nbsp;&nbsp;&nbsp; // 实际 PAM 代码省略，成功返回0，失败返回1 //<br />
	&nbsp;&nbsp;&nbsp; ...<br />
	&nbsp;&nbsp;&nbsp; pam_end(...);</p>
<p>	&nbsp;&nbsp;&nbsp; return ret;<br />
	}</font></p>
<p>	和实际服务程序一致，通过 system-auth PAM 配置进行验证，编译生成动态库：</p>
<p>	cc -c checkpam.c<br />
	cc -shared -fPIC -lpam -o libcheckpam.so checkpam.o</p>
<p>	ldd libcheckpam.so，查看此动态库的依赖列表：</p>
<p>	<em>[root@linux root]# ldd libcheckpam.so<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; linux-gate.so.1 =&gt;&nbsp; (0x00c67000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libpam.so.0 =&gt; /lib/libpam.so.0 (0x0025b000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libc.so.6 =&gt; /lib/tls/libc.so.6 (0x0041d000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libdl.so.2 =&gt; /lib/libdl.so.2 (0x00153000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /lib/ld-linux.so.2 =&gt; /lib/ld-linux.so.2 (0x00876000)</em><br />
	<strong><br />
	2、主程序部分（pamtest.c）：</strong></p>
<p>	加载动态库，调用 PAM 进行用户验证，比较丑陋，只需能验证，HOHO。</p>
<p><font color="#006400">#include &lt;stdio.h&gt;<br />
	#include &lt;stdlib.h&gt;<br />
	#include &lt;string.h&gt;<br />
	#include &lt;dlfcn.h&gt;</p>
<p>	int main(int argc, char ** argv)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; int ret = 0;<br />
	&nbsp;&nbsp;&nbsp; void * lpso = NULL;<br />
	&nbsp;&nbsp;&nbsp; int (* lpfunc)(const char *, const char *) = NULL;</p>
<p>	&nbsp;&nbsp;&nbsp; lpso = dlopen(&quot;./libcheckpam.so&quot;, RTLD_LAZY);<br />
	&nbsp;&nbsp;&nbsp; if (lpso == NULL) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf(&quot;Library load failed: %s.\n&quot;, dlerror());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return 1;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; lpfunc = dlsym(lpso, &quot;check_pam&quot;);<br />
	&nbsp;&nbsp;&nbsp; if (lpfunc == NULL) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf(&quot;Load symbol failed: %s.\n&quot;, dlerror());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; dlclose(lpso);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return 2;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; ret = lpfunc(argv[1], argv[2], NULL);</p>
<p>	&nbsp;&nbsp;&nbsp; dlclose(lpso);</p>
<p>	&nbsp;&nbsp;&nbsp; printf(&quot;pam ret: %d.\n&quot;, ret);</p>
<p>	&nbsp;&nbsp;&nbsp; return ret;<br />
	}</font></p>
<p>	功能很简单，dlopen 加载刚才生成的 libcheckpam.so，然后 dlsym 找到 check_pam 函数，接着调用函数进行用户验证。</p>
<p>	编译程序：</p>
<p>	cc -o pamtest pamtest.c -ldl</p>
<p>	ldd pamtest，查看程序的依赖列表，可以看到此时已不需要 pam 库，和实际使用的服务程序的实现方式一致了：</p>
<p>	<em>[root@linux root]# ldd pamtest<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; linux-gate.so.1 =&gt;&nbsp; (0x00631000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libdl.so.2 =&gt; /lib/libdl.so.2 (0x009ac000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libc.so.6 =&gt; /lib/tls/libc.so.6 (0x0088f000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /lib/ld-linux.so.2 =&gt; /lib/ld-linux.so.2 (0x00876000)</em></p>
<p>	然后运行 ./pamtest username password，参数分别指定用户名和密码，在 Fedora Core 2 上不管指定的用户名和密码是否正确，始终返回错误，并在系统日志中产生如文章开头所述的错误，而在 RHEL6 上就可以得到正确的结果。</p>
<p><span style="color:#f00;"><strong>原因分析：</strong></span></p>
<p>	经过查看 man 帮助并简单分析，终于找到原因，dlopen 的参数是这个问题的元凶之一。</p>
<p>	节选 man dlopen 的说明如下：</p>
<p>	The&nbsp; value&nbsp; of flag can be either RTLD_LAZY or RTLD_NOW. When RTLD_NOW is specified, or the environment variable LD_BIND_NOW is set to a&nbsp; nonempty&nbsp; string, all undefined symbols in the library are resolved before dlopen() returns. If this cannot be done, an error is returned.&nbsp; Otherwise binding is lazy: symbol values are first resolved when needed.</p>
<p>	Optionally,&nbsp; RTLD_GLOBAL&nbsp; may&nbsp; be&nbsp; or&rsquo;ed&nbsp; into&nbsp; flag, in which case the external symbols defined in the library will be made available for symbol&nbsp; resolution&nbsp; of&nbsp; subsequently&nbsp; loaded&nbsp; libraries. (The converse of RTLD_GLOBAL is RTLD_LOCAL. This is the default.)</p>
<p>	简单说就是如果指定了 RTLD_NOW 参数，在调用 dlopen 时就解析所有未定义符号，如果出错就 dlopen 失败，RTLD_LAZY 则不这样。<br />
	比较重要的是 RTLD_GLOBAL 参数，如果这个设置上，则 dlopen 加载的动态库的外部符号可以被后续加载的库解析到。</p>
<p>	关于库加载时的符号解析还有一段：</p>
<p>	External references in the library are resolved using the libraries&nbsp; in that&nbsp; library&rsquo;s&nbsp; dependency&nbsp; list&nbsp; and&nbsp; any&nbsp; other libraries previously opened with the RTLD_GLOBAL flag.&nbsp; If the executable&nbsp; was&nbsp; linked&nbsp; with the&nbsp; flag&nbsp; &quot;-rdynamic&quot; (or, synonymously, &quot;--export-dynamic&quot;), then the global symbols in the executable will also be used&nbsp; to&nbsp; resolve&nbsp; references in a dynamically loaded library.</p>
<p>	由于本例中没有使用 -rdynamic 参数，因此看前面一部分，库中的外部引用首先从库的依赖列表（也就是万能的 ldd 中的结果，哈哈）中查找，然后再从之前用了 RTLD_GLOBAL 参数加载的库中查找。</p>
<p>	具体到本例中，此测试程序使用的 dlopen 的参数为单独的 RTLD_LAZY，没有指定 RTLD_GLOBAL，PAM 验证时假设首先走 pam_env.so，这个 pam_env.so 也是在 pam_start、pam_end 等函数过程中通过 dlopen 动态加载的。</p>
<p>	虽然我们的 libcheckpam.so 动态库在编译时有依赖 libpam 库，但由于 pamtest 程序在 dlopen 时未指定 RTLD_GLOBAL，libpam 的符号不能被后续加载的 pam_env.so 等 PAM 库解析到，所以就出现文章开头的那些错误。</p>
<p>	<span style="color:#f00;"><strong>解决方法：</strong></span></p>
<p>	因此有几种方法让此程序能在 Fedora Core 2 系统中正常运行，以下三种都已经经过测试确认过：</p>
<p>	1、编译 pamtest 时指定依赖 pam 库，让 pam_env.so 等 PAM 库能解析到：<br />
	&nbsp;&nbsp;&nbsp; cc -o pamtest pamtest.c -ldl -lpam<br />
	2、加载 libcheckpam.so 时增加 RTLD_GLOBAL 参数，将 pamtest.c 中 dlopen 行改为：<br />
	&nbsp;&nbsp;&nbsp; lpso = dlopen(&quot;./libcheckpam.so&quot;, RTLD_LAZY | RTLD_GLOBAL);<br />
	3、如果碰到比较特殊的情况，pamtest 无法更改，就可以在运行 pamtest 前用 LD_PRELOAD 预先加载 libpam：<br />
	&nbsp;&nbsp;&nbsp; export LD_PRELOAD=&quot;/lib/libpam.so.0&quot;<br />
	&nbsp;&nbsp;&nbsp; ./pamtest username password</p>
<p>	另外提下为什么在 RHEL6 下没问题，Fedora Core 2 下有问题，看下两个系统下 pam_env.so 的依赖列表：</p>
<p>	<strong>Fedora Core 2 下</strong></p>
<p>	<em>[root@linux root]# ldd /lib/security/pam_env.so<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; linux-gate.so.1 =&gt;&nbsp; (0x00674000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libc.so.6 =&gt; /lib/tls/libc.so.6 (0x00111000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /lib/ld-linux.so.2 =&gt; /lib/ld-linux.so.2 (0x00876000)</em></p>
<p>	<strong>RHEL6 下</strong></p>
<p>	<em>[root@localhost ~]# ldd /lib64/security/pam_env.so<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; linux-vdso.so.1 =&gt;&nbsp; (0x00007fff171ff000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libpam.so.0 =&gt; /lib64/libpam.so.0 (0x00007f6656472000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libaudit.so.1 =&gt; /lib64/libaudit.so.1 (0x00007f665625a000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libdl.so.2 =&gt; /lib64/libdl.so.2 (0x00007f6656056000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libcrypt.so.1 =&gt; /lib64/libcrypt.so.1 (0x00007f6655e1f000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libc.so.6 =&gt; /lib64/libc.so.6 (0x00007f6655a8d000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /lib64/ld-linux-x86-64.so.2 (0x000000328c400000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libfreebl3.so =&gt; /lib64/libfreebl3.so (0x00007f665582b000)</em></p>
<p>	显然在 RHEL6 下，pam_env.so 本身就已经依赖了 libpam，这样 libpam 的符号肯定能被解析到，ldd 看看 /lib64/security/pam*.so 你会所有 PAM 动态库都已经依赖 libpam 库，这应该是新的系统中做的改动了。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/linux-pam-get-item-error-library/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
