新一代操作系统的构想

当一堆机器通过puppet、pssh这样的软件在管理的时候,你真想,如果它们共同运行一个操作系统,由那个操作系统统一替你协调这些机器该多好,而这个操作系统不能是openstack那种以机器(运行Linux、Windows这样的操作系统的环境)为单位的,而应该是以我的“程序”为单位的。软件架构,不能太过离散。

日志、定时器事件、用户、安装包……为什么不统一规划呢?以前的OS是面向单机的,现在的OS是面向群集的,这可能就是Elastos该出手的地方。

 | 230 views | 0 comments | 2 flags | 

【转】Learn haskell in Y Minutes

Learn X in Y minutes

http://learnxinyminutes.com/docs/zh-cn/haskell-cn/

Where X=haskell

Get the code: learn-haskell-zh.hs

Haskell 被设计成一种实用的纯函数式编程语言。它因为 monads 及其类型系统而出名,但是我回归到它本身因为。Haskell 使得编程对于我而言是一种真正的快乐。

-- 单行注释以两个破折号开头
{- 多行注释像这样
   被一个闭合的块包围
-}

----------------------------------------------------
-- 1. 简单的数据类型和操作符
----------------------------------------------------

-- 你有数字
3 -- 3
-- 数学计算就像你所期待的那样
1 + 1 -- 2
8 - 1 -- 7
10 * 2 -- 20
35 / 5 -- 7.0

-- 默认除法不是整除
35 / 4 -- 8.75

-- 整除
35 `div` 4 -- 8

-- 布尔值也简单
True
False

-- 布尔操作
not True -- False
not False -- True
1 == 1 -- True
1 /= 1 -- False
1 < 10 -- True

-- 在上述的例子中,`not` 是一个接受一个值的函数。
-- Haskell 不需要括号来调用函数。。。所有的参数
-- 都只是在函数名之后列出来。因此,通常的函数调用模式是:
-- func arg1 arg2 arg3...
-- 查看关于函数的章节以获得如何写你自己的函数的相关信息。

-- 字符串和字符
"This is a string."
'a' -- 字符
'对于字符串你不能使用单引号。' -- 错误!

-- 连结字符串
"Hello " ++ "world!" -- "Hello world!"

-- 一个字符串是一系列字符
"This is a string" !! 0 -- 'T'

----------------------------------------------------
-- 列表和元组
----------------------------------------------------

-- 一个列表中的每一个元素都必须是相同的类型
-- 下面两个列表一样
[1, 2, 3, 4, 5]
[1..5]

-- 在 Haskell 你可以拥有含有无限元素的列表
[1..] -- 一个含有所有自然数的列表

-- 因为 Haskell 有“懒惰计算”,所以无限元素的列表可以正常运作。这意味着
-- Haskell 可以只在它需要的时候计算。所以你可以请求
-- 列表中的第1000个元素,Haskell 会返回给你

[1..] !! 999 -- 1000

-- Haskell 计算了列表中 1 - 1000 个元素。。。但是
-- 这个无限元素的列表中剩下的元素还不存在! Haskell 不会
-- 真正地计算它们知道它需要。

<FS>- 连接两个列表
[1..5] ++ [6..10]

-- 往列表头增加元素
0:[1..5] -- [0, 1, 2, 3, 4, 5]

-- 列表中的下标
[0..] !! 5 -- 5

-- 更多列表操作
head [1..5] -- 1
tail [1..5] -- [2, 3, 4, 5]
init [1..5] -- [1, 2, 3, 4]
last [1..5] -- 5

-- 列表推导
[x*2 | x <- [1..5]] -- [2, 4, 6, 8, 10]

-- 附带条件
[x*2 | x <-[1..5], x*2 > 4] -- [6, 8, 10]

-- 元组中的每一个元素可以是不同类型的,但是一个元组
-- 的长度是固定的
-- 一个元组
("haskell", 1)

-- 获取元组中的元素
fst ("haskell", 1) -- "haskell"
snd ("haskell", 1) -- 1

----------------------------------------------------
-- 3. 函数
----------------------------------------------------
-- 一个接受两个变量的简单函数
add a b = a + b

-- 注意,如果你使用 ghci (Hakell 解释器)
-- 你将需要使用 `let`,也就是
-- let add a b = a + b

-- 使用函数
add 1 2 -- 3

-- 你也可以把函数放置在两个参数之间
-- 附带倒引号:
1 `add` 2 -- 3

-- 你也可以定义不带字符的函数!这使得
-- 你定义自己的操作符!这里有一个操作符
-- 来做整除
(//) a b = a `div` b
35 // 4 -- 8

-- 守卫:一个简单的方法在函数里做分支
fib x
  | x < 2 = x
  | otherwise = fib (x - 1) + fib (x - 2)

-- 模式匹配是类型的。这里有三种不同的 fib 
-- 定义。Haskell 将自动调用第一个
-- 匹配值的模式的函数。
fib 1 = 1
fib 2 = 2
fib x = fib (x - 1) + fib (x - 2)

-- 元组的模式匹配:
foo (x, y) = (x + 1, y + 2)

-- 列表的模式匹配。这里 `x` 是列表中第一个元素,
-- 并且 `xs` 是列表剩余的部分。我们可以写
-- 自己的 map 函数:
myMap func [] = []
myMap func (x:xs) = func x:(myMap func xs)

-- 编写出来的匿名函数带有一个反斜杠,后面跟着
-- 所有的参数。
myMap (\x -> x + 2) [1..5] -- [3, 4, 5, 6, 7]

-- 使用 fold (在一些语言称为`inject`)随着一个匿名的
-- 函数。foldl1 意味着左折叠(fold left), 并且使用列表中第一个值
-- 作为累加器的初始化值。
foldl1 (\acc x -> acc + x) [1..5] -- 15

----------------------------------------------------
-- 4. 更多的函数
----------------------------------------------------

-- 柯里化(currying):如果你不传递函数中所有的参数,
-- 它就变成“柯里化的”。这意味着,它返回一个接受剩余参数的函数。

add a b = a + b
foo = add 10 -- foo 现在是一个接受一个数并对其加 10 的函数
foo 5 -- 15

-- 另外一种方式去做同样的事
foo = (+10)
foo 5 -- 15

-- 函数组合
-- (.) 函数把其它函数链接到一起
-- 举个列子,这里 foo 是一个接受一个值的函数。它对接受的值加 10,
-- 并对结果乘以 5,之后返回最后的值。
foo = (*5) . (+10)

-- (5 + 10) * 5 = 75
foo 5 -- 75

-- 修复优先级
-- Haskell 有另外一个函数称为 `$`。它改变优先级
-- 使得其左侧的每一个操作先计算然后应用到
-- 右侧的每一个操作。你可以使用 `.` 和 `$` 来除去很多
-- 括号:

-- before
(even (fib 7)) -- true

-- after
even . fib $ 7 -- true

----------------------------------------------------
-- 5. 类型签名
----------------------------------------------------

-- Haskell 有一个非常强壮的类型系统,一切都有一个类型签名。

-- 一些基本的类型:
5 :: Integer
"hello" :: String
True :: Bool

-- 函数也有类型。
-- `not` 接受一个布尔型返回一个布尔型:
-- not :: Bool -> Bool

-- 这是接受两个参数的函数:
-- add :: Integer -> Integer -> Integer

-- 当你定义一个值,在其上写明它的类型是一个好实践:
double :: Integer -> Integer
double x = x * 2

----------------------------------------------------
-- 6. 控制流和 If 语句
----------------------------------------------------

-- if 语句
haskell = if 1 == 1 then "awesome" else "awful" -- haskell = "awesome"

-- if 语句也可以有多行,缩进是很重要的
haskell = if 1 == 1
            then "awesome"
            else "awful"

-- case 语句:这里是你可以怎样去解析命令行参数
case args of
  "help" -> printHelp
  "start" -> startProgram
  _ -> putStrLn "bad args"

-- Haskell 没有循环因为它使用递归取代之。
-- map 应用一个函数到一个数组中的每一个元素

map (*2) [1..5] -- [2, 4, 6, 8, 10]

-- 你可以使用 map 来编写 for 函数
for array func = map func array

-- 然后使用它
for [0..5] $ \i -> show i

-- 我们也可以像这样写:
for [0..5] show

-- 你可以使用 foldl 或者 foldr 来分解列表
-- foldl <fn> <initial value> <list>
foldl (\x y -> 2*x + y) 4 [1,2,3] -- 43

-- 这和下面是一样的
(2 * (2 * (2 * 4 + 1) + 2) + 3)

-- foldl 是左手边的,foldr 是右手边的-
foldr (\x y -> 2*x + y) 4 [1,2,3] -- 16

-- 这和下面是一样的
(2 * 3 + (2 * 2 + (2 * 1 + 4)))

----------------------------------------------------
-- 7. 数据类型
----------------------------------------------------

-- 这里展示在 Haskell 中你怎样编写自己的数据类型

data Color = Red | Blue | Green

-- 现在你可以在函数中使用它:

say :: Color -> String
say Red = "You are Red!"
say Blue = "You are Blue!"
say Green =  "You are Green!"

-- 你的数据类型也可以有参数:

data Maybe a = Nothing | Just a

-- 类型 Maybe 的所有
Just "hello"    -- of type `Maybe String`
Just 1          -- of type `Maybe Int`
Nothing         -- of type `Maybe a` for any `a`

----------------------------------------------------
-- 8. Haskell IO
----------------------------------------------------

-- 虽然在没有解释 monads 的情况下 IO不能被完全地解释,
-- 着手解释到位并不难。

-- 当一个 Haskell 程序被执行,函数 `main` 就被调用。
-- 它必须返回一个类型 `IO ()` 的值。举个列子:

main :: IO ()
main = putStrLn $ "Hello, sky! " ++ (say Blue) 
-- putStrLn has type String -> IO ()

-- 如果你能实现你的程序依照函数从 String 到 String,那样编写 IO 是最简单的。
-- 函数
--    interact :: (String -> String) -> IO ()
-- 输入一些文本,在其上运行一个函数,并打印出输出

countLines :: String -> String
countLines = show . length . lines

main' = interact countLines

-- 你可以考虑一个 `IO()` 类型的值,当做一系列计算机所完成的动作的代表,
-- 就像一个以命令式语言编写的计算机程序。我们可以使用 `do` 符号来把动作链接到一起。
-- 举个列子:

sayHello :: IO ()
sayHello = do 
   putStrLn "What is your name?"
   name <- getLine -- this gets a line and gives it the name "input"
   putStrLn $ "Hello, " ++ name

-- 练习:编写只读取一行输入的 `interact`

-- 然而,`sayHello` 中的代码将不会被执行。唯一被执行的动作是 `main` 的值。
-- 为了运行 `sayHello`,注释上面 `main` 的定义,并代替它:
--   main = sayHello

-- 让我们来更好地理解刚才所使用的函数 `getLine` 是怎样工作的。它的类型是:
--    getLine :: IO String
-- 你可以考虑一个 `IO a` 类型的值,代表一个当被执行的时候
-- 将产生一个 `a` 类型的值的计算机程序(除了它所做的任何事之外)。我们可以保存和重用这个值通过 `<-`。
-- 我们也可以写自己的 `IO String` 类型的动作:

action :: IO String
action = do
   putStrLn "This is a line. Duh"
   input1 <- getLine 
   input2 <- getLine
   -- The type of the `do` statement is that of its last line.
   -- `return` is not a keyword, but merely a function 
   return (input1 ++ "\n" ++ input2) -- return :: String -> IO String

-- 我们可以使用这个动作就像我们使用 `getLine`:

main'' = do
    putStrLn "I will echo two lines!"
    result <- action 
    putStrLn result
    putStrLn "This was all, folks!"

-- `IO` 类型是一个 "monad" 的例子。Haskell 使用一个 monad 来做 IO的方式允许它是一门纯函数式语言。
-- 任何与外界交互的函数(也就是 IO) 都在它的类型签名处做一个 `IO` 标志
-- 着让我们推出 什么样的函数是“纯洁的”(不与外界交互,不修改状态) 和 什么样的函数不是 “纯洁的”

-- 这是一个强有力的特征,因为并发地运行纯函数是简单的;因此,Haskell 中并发是非常简单的。

----------------------------------------------------
-- 9. The Haskell REPL
----------------------------------------------------

-- 键入 `ghci` 开始 repl。
-- 现在你可以键入 Haskell 代码。
-- 任何新值都需要通过 `let` 来创建:

let foo = 5

-- 你可以查看任何值的类型,通过命令 `:t`:

>:t foo
foo :: Integer

-- 你也可以运行任何 `IO ()`类型的动作

> sayHello
What is your name?
Friend!
Hello, Friend!

还有很多关于 Haskell,包括类型类和 monads。这些是使得编码 Haskell 是如此有趣的主意。我用一个最后的 Haskell 例子来结束:一个 Haskell 的快排实现:

qsort [] = []
qsort (p:xs) = qsort lesser ++ [p] ++ qsort greater
    where lesser  = filter (< p) xs
          greater = filter (>= p) xs

安装 Haskell 是简单的。你可以从这里获得它。

你可以从优秀的 Learn you a Haskell 或者 Real World Haskell 找到优雅不少的入门介绍。

 | 34 views | 0 comments | 0 flags | 

【转】Linux下的lds链接脚本基础

http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml

今天在看uboot引导Linux部分,发现要对链接脚本深入了解,才能知道各个目标文件的内存分布映像,下面是我看到的一些资料
0. Contents
1. 概论
2. 基本概念
3. 脚本格式
4. 简单例子
5. 简单脚本命令
6. 对符号的赋值
7. SECTIONS命令
8. MEMORY命令
9. PHDRS命令
10. VERSION命令
11. 脚本内的表达式
12. 暗含的连接脚本
1. 概论
每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但你也可以用连接命令做一些其他事情.
连接器有个默认的内置连接脚本, 可用ld –verbose查看. 连接选项-r和-N可以影响默认的连接脚本(如何影响?).
-T选项用以指定自己的链接脚本, 它将代替默认的连接脚本。你也可以使用以增加自定义的链接命令.
以下没有特殊说明,连接器指的是静态连接器.
2. 基本概念
链接器把一个或多个输入文件合成一个输出文件.
输入文件: 目标文件或链接脚本文件.
输出文件: 目标文件或可执行文件.
目标文件(包括可执行文件)具有固定的格式, 在UNIX或GNU/Linux平台下, 一般为ELF格式. 若想了解更多, 可参考 UNIX/Linux平台可执行文件格式分析
有时把输入文件内的section称为输入section(input section), 把输出文件内的section称为输出section(output sectin).
目标文件的每个section至少包含两个信息: 名字和大小. 大部分section还包含与它相关联的一块数据, 称为section contents(section内容). 一个section可被标记为“loadable(可加载的)”或“allocatable(可分配的)”.
loadable section: 在输出文件运行时, 相应的section内容将被载入进程地址空间中.
allocatable section: 内容为空的section可被标记为“可分配的”. 在输出文件运行时, 在进程地址空间中空出大小同section指定大小的部分. 某些情况下, 这块内存必须被置零.
如果一个section不是“可加载的”或“可分配的”, 那么该section通常包含了调试信息. 可用objdump -h命令查看相关信息.
每个“可加载的”或“可分配的”输出section通常包含两个地址: VMA(virtual memory address虚拟内存地址或程序地址空间地址)和LMA(load memory address加载内存地址或进程地址空间地址). 通常VMA和LMA是相同的.
在目标文件中, loadable或allocatable的输出section有两种地址: VMA(virtual Memory Address)和LMA(Load Memory Address). VMA是执行输出文件时section所在的地址, 而LMA是加载输出文件时section所在的地址. 一般而言, 某section的VMA == LMA. 但在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的flash中(由LMA指定), 而在运行时将位于flash中的输出文件复制到SDRAM中(由VMA指定).
可这样来理解VMA和LMA, 假设:
(1) .data section对应的VMA地址是0×08050000, 该section内包含了3个32位全局变量, i、j和k, 分别为1,2,3.
(2) .text section内包含由”printf( “j=%d “, j );”程序片段产生的代码.
连接时指定.data section的VMA为0×08050000, 产生的printf指令是将地址为0×08050004处的4字节内容作为一个整数打印出来。
如果.data section的LMA为0×08050000,显然结果是j=2
如果.data section的LMA为0×08050004,显然结果是j=1
还可这样理解LMA:
.text section内容的开始处包含如下两条指令(intel i386指令是10字节,每行对应5字节):
jmp 0×08048285
movl $0×1,%eax
如果.text section的LMA为0×08048280, 那么在进程地址空间内0×08048280处为“jmp 0×08048285”指令, 0×08048285处为movl $0×1,%eax指令. 假设某指令跳转到地址0×08048280, 显然它的执行将导致%eax寄存器被赋值为1.
如果.text section的LMA为0×08048285, 那么在进程地址空间内0×08048285处为“jmp 0×08048285”指令, 0×0804828a处为movl $0×1,%eax指令. 假设某指令跳转到地址0×08048285, 显然它的执行又跳转到进程地址空间内0×08048285处, 造成死循环.
符号(symbol): 每个目标文件都有符号表(SYMBOL TABLE), 包含已定义的符号(对应全局变量和static变量和定义的函数的名字)和未定义符号(未定义的函数的名字和引用但没定义的符号)信息.
符号值: 每个符号对应一个地址, 即符号值(这与c程序内变量的值不一样, 某种情况下可以把它看成变量的地址). 可用nm命令查看它们. (nm的使用方法可参考本blog的GNU binutils笔记)
3. 脚本格式
链接脚本由一系列命令组成, 每个命令由一个关键字(一般在其后紧跟相关参数)或一条对符号的赋值语句组成. 命令由分号‘;’分隔开.
文件名或格式名内如果包含分号’;’或其他分隔符, 则要用引号‘”’将名字全称引用起来. 无法处理含引号的文件名.
/* */之间的是注释。
4. 简单例子
在介绍链接描述文件的命令之前, 先看看下述的简单例子:
以下脚本将输出文件的text section定位在0×10000, data section定位在0×8000000:
SECTIONS
{
. = 0×10000;
.text : { *(.text) }
. = 0×8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
解释一下上述的例子:
. = 0×10000 : 把定位器符号置为0×10000 (若不指定, 则该符号的初始值为0).
.text : { *(.text) } : 将所有(*符号代表任意输入文件)输入文件的.text section合并成一个.text section, 该section的地址由定位器符号的值指定, 即0×10000.
. = 0×8000000 :把定位器符号置为0×8000000
.data : { *(.data) } : 将所有输入文件的.data section合并成一个.data section, 该section的地址被置为0×8000000.
.bss : { *(.bss) } : 将所有输入文件的.bss section合并成一个.bss section,该section的地址被置为0×8000000+.data section的大小.
连接器每读完一个section描述后, 将定位器符号的值*增加*该section的大小. 注意: 此处没有考虑对齐约束.
5. 简单脚本命令
- 1 -
ENTRY(SYMBOL) : 将符号SYMBOL的值设置成入口地址。
入口地址(entry point): 进程执行的第一条用户空间的指令在进程地址空间的地址)
ld有多种方法设置进程入口地址, 按一下顺序: (编号越前, 优先级越高)
1, ld命令行的-e选项
2, 连接脚本的ENTRY(SYMBOL)命令
3, 如果定义了start符号, 使用start符号值
4, 如果存在.text section, 使用.text section的第一字节的位置值
5, 使用值0
- 2 -INCLUDE filename : 包含其他名为filename的链接脚本
相当于c程序内的的#include指令, 用以包含另一个链接脚本.
脚本搜索路径由-L选项指定. INCLUDE指令可以嵌套使用, 最大深度为10. 即: 文件1内INCLUDE文件2, 文件2内INCLUDE文件3… , 文件10内INCLUDE文件11. 那么文件11内不能再出现 INCLUDE指令了.
- 3 -INPUT(files): 将括号内的文件做为链接过程的输入文件
ld首先在当前目录下寻找该文件, 如果没找到, 则在由-L指定的搜索路径下搜索. file可以为 -lfile形式,就象命令行的-l选项一样. 如果该命令出现在暗含的脚本内, 则该命令内的file在链接过程中的顺序由该暗含的脚本在命令行内的顺序决定.
- 4 -GROUP(files) : 指定需要重复搜索符号定义的多个输入文件
file必须是库文件, 且file文件作为一组被ld重复扫描,直到不在有新的未定义的引用出现。
- 5 -OUTPUT(FILENAME) : 定义输出文件的名字
同ld的-o选项, 不过-o选项的优先级更高. 所以它可以用来定义默认的输出文件名. 如a.out
- 6 -SEARCH_DIR(PATH) :定义搜索路径,
同ld的-L选项, 不过由-L指定的路径要比它定义的优先被搜索。
- 7 -STARTUP(filename) : 指定filename为第一个输入文件
在链接过程中, 每个输入文件是有顺序的. 此命令设置文件filename为第一个输入文件。
- 8 – OUTPUT_FORMAT(BFDNAME) : 设置输出文件使用的BFD格式
同ld选项-o format BFDNAME, 不过ld选项优先级更高.
- 9 -OUTPUT_FORMAT(DEFAULT,BIG,LITTLE) : 定义三种输出文件的格式(大小端)
若有命令行选项-EB, 则使用第2个BFD格式; 若有命令行选项-EL,则使用第3个BFD格式.否则默认选第一个BFD格式.
TARGET(BFDNAME):设置输入文件的BFD格式
同ld选项-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令, 则最用一个TARGET命令设置的BFD格式将被作为输出文件的BFD格式.
另外还有一些:
ASSERT(EXP, MESSAGE):如果EXP不为真,终止连接过程
EXTERN(SYMBOL SYMBOL …):在输出文件中增加未定义的符号,如同连接器选项-u
FORCE_COMMON_ALLOCATION:为common symbol(通用符号)分配空间,即使用了-r连接选项也为其分配
NOCROSSREFS(SECTION SECTION …):检查列出的输出section,如果发现他们之间有相互引用,则报错。对于某些系统,特别是内存较紧张的嵌入式系统,某些section是不能同时存在内存中的,所以他们之间不能相互引用。
OUTPUT_ARCH(BFDARCH):设置输出文件的machine architecture(体系结构),BFDARCH为被BFD库使用的名字之一。可以用命令objdump -f查看。
可通过 man -S 1 ld查看ld的联机帮助, 里面也包括了对这些命令的介绍.
6. 对符号的赋值
在目标文件内定义的符号可以在链接脚本内被赋值. (注意和C语言中赋值的不同!) 此时该符号被定义为全局的. 每个符号都对应了一个地址, 此处的赋值是更改这个符号对应的地址.
e.g. 通过下面的程序查看变量a的地址:
/* a.c */
#include
int a = 100;
int main(void)
{
printf( “&a=0x%p “, &a );
return 0;
}/* a.lds */
a = 3;
$ gcc -Wall -o a-without-lds a.c
&a = 0×8049598
$ gcc -Wall -o a-with-lds a.c a.lds
&a = 0×3
注意: 对符号的赋值只对全局变量起作用!
一些简单的赋值语句
能使用任何c语言内的赋值操作:
SYMBOL = EXPRESSION ;
SYMBOL += EXPRESSION ;
SYMBOL -= EXPRESSION ;
SYMBOL *= EXPRESSION ;
SYMBOL /= EXPRESSION ;
SYMBOL >= EXPRESSION ;
SYMBOL &= EXPRESSION ;
SYMBOL |= EXPRESSION ;
除了第一类表达式外, 使用其他表达式需要SYMBOL被定义于某目标文件。
. 是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置(或某section内的偏移,如果它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用。
注意:赋值语句包含4个语法元素:符号名、操作符、表达式、分号;一个也不能少。
被赋值后,符号所属的section被设值为表达式EXPRESSION所属的SECTION(参看11. 脚本内的表达式)
赋值语句可以出现在连接脚本的三处地方:SECTIONS命令内,SECTIONS命令内的section描述内和全局位置;如下,
floating_point = 0; /* 全局位置 */
SECTIONS
{
.text :
{
*(.text)
_etext = .; /* section描述内 */
}
_bdata = (. + 3) & ~ 4; /* SECTIONS命令内 */
.data : { *(.data) }
}
PROVIDE关键字
该关键字用于定义这类符号:在目标文件内被引用,但没有在任何目标文件内被定义的符号。
例子:
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
当目标文件内引用了etext符号,确没有定义它时,etext符号对应的地址被定义为.text section之后的第一个字节的地址。
7. SECTIONS命令
SECTIONS命令告诉ld如何把输入文件的sections映射到输出文件的各个section: 如何将输入section合为输出section; 如何把输出section放入程序地址空间(VMA)和进程地址空间(LMA).该命令格式如下:
SECTIONS
{
SECTIONS-COMMAND
SECTIONS-COMMAND

}
SECTION-COMMAND有四种:
(1) ENTRY命令
(2) 符号赋值语句
(3) 一个输出section的描述(output section description)
(4) 一个section叠加描述(overlay description)
如果整个连接脚本内没有SECTIONS命令, 那么ld将所有同名输入section合成为一个输出section内, 各输入section的顺序为它们被连接器发现的顺序.
如果某输入section没有在SECTIONS命令中提到, 那么该section将被直接拷贝成输出section。
输出section描述
输出section描述具有如下格式:
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND

} [>REGION] [AT>LMA_REGION] [:PHDR

HDR ...] [=FILLEXP]
[ ]内的内容为可选选项, 一般不需要.
SECTION:section名字
SECTION左右的空白、圆括号、冒号是必须的,换行符和其他空格是可选的。
每个OUTPUT-SECTION-COMMAND为以下四种之一,
符号赋值语句
一个输入section描述
直接包含的数据值
一个特殊的输出section关键字
输出section名字(SECTION):
输出section名字必须符合输出文件格式要求,比如:a.out格式的文件只允许存在.text、.data和.bss section名。而有的格式只允许存在数字名字,那么此时应该用引号将所有名字内的数字组合在一起;另外,还有一些格式允许任何序列的字符存在于 section名字内,此时如果名字内包含特殊字符(比如空格、逗号等),那么需要用引号将其组合在一起。
输出section地址(ADDRESS):
ADDRESS是一个表达式,它的值用于设置VMA。如果没有该选项且有REGION选项,那么连接器将根据REGION设置VMA;如果也没有 REGION选项,那么连接器将根据定位符号‘.’的值设置该section的VMA,将定位符号的值调整到满足输出section对齐要求后的值,输出 section的对齐要求为:该输出section描述内用到的所有输入section的对齐要求中最严格的。
例子:
.text . : { *(.text) }

.text : { *(.text) }
这两个描述是截然不同的,第一个将.text section的VMA设置为定位符号的值,而第二个则是设置成定位符号的修调值,满足对齐要求后的。
ADDRESS可以是一个任意表达式,比如ALIGN(0×10)这将把该section的VMA设置成定位符号的修调值,满足16字节对齐后的。
注意:设置ADDRESS值,将更改定位符号的值。
输入section描述:
最常见的输出section描述命令是输入section描述。
输入section描述是最基本的连接脚本描述。
输入section描述基础:
基本语法:FILENAME([EXCLUDE_FILE (FILENAME1 FILENAME2 ...) SECTION1 SECTION2 ...)
FILENAME文件名,可以是一个特定的文件的名字,也可以是一个字符串模式。
SECTION名字,可以是一个特定的section名字,也可以是一个字符串模式
例子是最能说明问题的,
*(.text) :表示所有输入文件的.text section
(*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)) :表示除crtend.o、otherfile.o文件外的所有输入文件的.ctors section。
data.o(.data) :表示data.o文件的.data section
data.o :表示data.o文件的所有section
*(.text .data) :表示所有文件的.text section和.data section,顺序是:第一个文件的.text section,第一个文件的.data section,第二个文件的.text section,第二个文件的.data section,...
*(.text) *(.data) :表示所有文件的.text section和.data section,顺序是:第一个文件的.text section,第二个文件的.text section,...,最后一个文件的.text section,第一个文件的.data section,第二个文件的.data section,...,最后一个文件的.data section
下面看连接器是如何找到对应的文件的。
当FILENAME是一个特定的文件名时,连接器会查看它是否在连接命令行内出现或在INPUT命令中出现。
当FILENAME是一个字符串模式时,连接器仅仅只查看它是否在连接命令行内出现。
注意:如果连接器发现某文件在INPUT命令内出现,那么它会在-L指定的路径内搜寻该文件。
字符串模式内可存在以下通配符:
* :表示任意多个字符
? :表示任意一个字符
[CHARS] :表示任意一个CHARS内的字符,可用-号表示范围,如:a-z
:表示引用下一个紧跟的字符
在文件名内,通配符不匹配文件夹分隔符/,但当字符串模式仅包含通配符*时除外。
任何一个文件的任意section只能在SECTIONS命令内出现一次。看如下例子,
SECTIONS {
.data : { *(.data) }
.data1 : { data.o(.data) }
}
data.o文件的.data section在第一个OUTPUT-SECTION-COMMAND命令内被使用了,那么在第二个OUTPUT-SECTION-COMMAND命令内将不会再被使用,也就是说即使连接器不报错,输出文件的.data1 section的内容也是空的。
再次强调:连接器依次扫描每个OUTPUT-SECTION-COMMAND命令内的文件名,任何一个文件的任何一个section都只能使用一次。
读者可以用-M连接命令选项来产生一个map文件,它包含了所有输入section到输出section的组合信息。
再看个例子,
SECTIONS {
.text : { *(.text) }
.DATA : { [A-Z]*(.data) }
.data : { *(.data) }
.bss : { *(.bss) }
}
这个例子中说明,所有文件的输入.text section组成输出.text section;所有以大写字母开头的文件的.data section组成输出.DATA section,其他文件的.data section组成输出.data section;所有文件的输入.bss section组成输出.bss section。
可以用SORT()关键字对满足字符串模式的所有名字进行递增排序,如SORT(.text*)。
通用符号(common symbol)的输入section:
在许多目标文件格式中,通用符号并没有占用一个section。连接器认为:输入文件的所有通用符号在名为COMMON的section内。
例子,
.bss { *(.bss) *(COMMON) }
这个例子中将所有输入文件的所有通用符号放入输出.bss section内。可以看到COMMOM section的使用方法跟其他section的使用方法是一样的。
有些目标文件格式把通用符号分成几类。例如,在MIPS elf目标文件格式中,把通用符号分成standard common symbols(标准通用符号)和small common symbols(微通用符号,不知道这么译对不对?),此时连接器认为所有standard common symbols在COMMON section内,而small common symbols在.scommon section内。
在一些以前的连接脚本内可以看见[COMMON],相当于*(COMMON),不建议继续使用这种陈旧的方式。
输入section和垃圾回收:
在连接命令行内使用了选项–gc-sections后,连接器可能将某些它认为没用的section过滤掉,此时就有必要强制连接器保留一些特定的 section,可用KEEP()关键字达此目的。如KEEP(*(.text))或KEEP(SORT(*)(.text))
最后看个简单的输入section相关例子:
SECTIONS {
outputa 0×10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
foo.o (.input2)
foo1.o (.input1)
}
outputc :
{
*(.input1)
*(.input2)
}
}
本例中,将all.o文件的所有section和foo.o文件的所有(一个文件内可以有多个同名section).input1 section依次放入输出outputa section内,该section的VMA是0×10000;将foo.o文件的所有.input2 section和foo1.o文件的所有.input1 section依次放入输出outputb section内,该section的VMA是当前定位器符号的修调值(对齐后);将其他文件(非all.o、foo.o、foo1.o)文件的. input1 section和.input2 section放入输出outputc section内。
在输出section存放数据命令:
能够显示地在输出section内填入你想要填入的信息(这样是不是可以自己通过连接脚本写程序?当然是简单的程序)。
BYTE(EXPRESSION) 1 字节
SHORT(EXPRESSION) 2 字节
LOGN(EXPRESSION) 4 字节
QUAD(EXPRESSION) 8 字节
SQUAD(EXPRESSION) 64位处理器的代码时,8 字节
输出文件的字节顺序big endianness 或little endianness,可以由输出目标文件的格式决定;如果输出目标文件的格式不能决定字节顺序,那么字节顺序与第一个输入文件的字节顺序相同。
如:BYTE(1)、LANG(addr)。
注意,这些命令只能放在输出section描述内,其他地方不行。
错误:SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }
正确:SECTIONS { .text : { *(.text) LONG(1) } .data : { *(.data) } }
在当前输出section内可能存在未描述的存储区域(比如由于对齐造成的空隙),可以用FILL(EXPRESSION)命令决定这些存储区域的内容, EXPRESSION的前两字节有效,这两字节在必要时可以重复被使用以填充这类存储区域。如FILE(0×9090)。在输出section描述中可以有=FILEEXP属性,它的作用如同FILE()命令,但是FILE命令只作用于该FILE指令之后的section区域,而=FILEEXP属性作用于整个输出section区域,且FILE命令的优先级更高!!!
输出section内命令的关键字:
CREATE_OBJECT_SYMBOLS :为每个输入文件建立一个符号,符号名为输入文件的名字。每个符号所在的section是出现该关键字的section。
CONSTRUCTORS :与c++内的(全局对象的)构造函数和(全局对像的)析构函数相关,下面将它们简称为全局构造和全局析构。
对于a.out目标文件格式,连接器用一些不寻常的方法实现c++的全局构造和全局析构。当连接器生成的目标文件格式不支持任意section名字时,比如说ECOFF、XCOFF格式,连接器将通过名字来识别全局构造和全局析构,对于这些文件格式,连接器把与全局构造和全局析构的相关信息放入出现 CONSTRUCTORS关键字的输出section内。
符号__CTORS_LIST__表示全局构造信息的的开始处,__CTORS_END__表示全局构造信息的结束处。
符号__DTORS_LIST__表示全局构造信息的的开始处,__DTORS_END__表示全局构造信息的结束处。
这两块信息的开始处是一字长的信息,表示该块信息有多少项数据,然后以值为零的一字长数据结束。
一般来说,GNU C++在函数__main内安排全局构造代码的运行,而__main函数被初始化代码(在main函数调用之前执行)调用。是不是对于某些目标文件格式才这样???
对于支持任意section名的目标文件格式,比如COFF、ELF格式,GNU C++将全局构造和全局析构信息分别放入.ctors section和.dtors section内,然后在连接脚本内加入如下,
__CTOR_LIST__ = .;
LONG((__CTOR_END__ – __CTOR_LIST__) / 4 – 2)
*(.ctors)
LONG(0)
__CTOR_END__ = .;
__DTOR_LIST__ = .;
LONG((__DTOR_END__ – __DTOR_LIST__) / 4 – 2)
*(.dtors)
LONG(0)
__DTOR_END__ = .;
如果使用GNU C++提供的初始化优先级支持(它能控制每个全局构造函数调用的先后顺序),那么请在连接脚本内把CONSTRUCTORS替换成SORT (CONSTRUCTS),把*(.ctors)换成*(SORT(.ctors)),把*(.dtors)换成*(SORT(.dtors))。一般来说,默认的连接脚本已作好的这些工作。
输出section的丢弃:
例子,.foo { *(.foo) },如果没有任何一个输入文件包含.foo section,那么连接器将不会创建.foo输出section。但是如果在这些输出section描述内包含了非输入section描述命令(如符号赋值语句),那么连接器将总是创建该输出section。
有一个特殊的输出section,名为/DISCARD/,被该section引用的任何输入section将不会出现在输出文件内,这就是DISCARD的意思吧。如果/DISCARD/ section被它自己引用呢?想想看。
输出section属性:
终于讲到这里了,呵呵。
我们再回顾以下输出section描述的文法:
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND

} [>REGION] [AT>LMA_REGION] [:PHDR

HDR ...] [=FILLEXP]
前面我们浏览了SECTION、ADDRESS、OUTPUT-SECTION-COMMAND相关信息,下面我们将浏览其他属性。
TYPE :每个输出section都有一个类型,如果没有指定TYPE类型,那么连接器根据输出section引用的输入section的类型设置该输出section的类型。它可以为以下五种值,
NOLOAD :该section在程序运行时,不被载入内存。
DSECT,COPY,INFO,OVERLAY :这些类型很少被使用,为了向后兼容才被保留下来。这种类型的section必须被标记为“不可加载的”,以便在程序运行不为它们分配内存。
输出section的LMA :默认情况下,LMA等于VMA,但可以通过关键字AT()指定LMA。
用关键字AT()指定,括号内包含表达式,表达式的值用于设置LMA。如果不用AT()关键字,那么可用AT>LMA_REGION表达式设置指定该section加载地址的范围。
这个属性主要用于构件ROM境象。
例子,
SECTIONS
{
.text 0×1000 : { *(.text) _etext = . ; }
.mdata 0×2000 :
AT ( ADDR (.text) + SIZEOF (.text) )
{ _data = . ; *(.data); _edata = . ; }
.bss 0×3000 :
{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}
}
程序如下,
extern char _etext, _data, _edata, _bstart, _bend;
char *src = &_etext;
char *dst = &_data;
/* ROM has data at end of text; copy it. */
while (dst rom }
输出section所在的程序段:可以将输出section放入预先定义的程序段(program segment)内。如果某个输出section设置了它所在的一个或多个程序段,那么接下来定义的输出section的默认程序段与该输出 section的相同。除非再次显示地指定。例子,
PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } :text }
可以通过:NONE指定连接器不把该section放入任何程序段内。详情请查看PHDRS命令
输出section的填充模版:这个在前面提到过,任何输出section描述内的未指定的内存区域,连接器用该模版填充该区域。用法:=FILEEXP,前两字节有效,当区域大于两字节时,重复使用这两字节以将其填满。例子,
SECTIONS { .text : { *(.text) } =0×9090 }
覆盖图(overlay)描述:
覆盖图描述使两个或多个不同的section占用同一块程序地址空间。覆盖图管理代码负责将section的拷入和拷出。考虑这种情况,当某存储块的访问速度比其他存储块要快时,那么如果将section拷到该存储块来执行或访问,那么速度将会有所提高,覆盖图描述就很适合这种情形。文法如下,
SECTIONS {

OVERLAY [START] : [NOCROSSREFS] [AT ( LDADDR )]
{
SECNAME1
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND

} [:PHDR...] [=FILL]
SECNAME2
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND

} [:PHDR...] [=FILL]

} [>REGION] [:PHDR...] [=FILL]

}
由以上文法可以看出,同一覆盖图内的section具有相同的VMA。SECNAME2的LMA为SECTNAME1的LMA加上SECNAME1的大小,同理计算SECNAME2,3,4…的LMA。SECNAME1的LMA由LDADDR决定,如果它没有被指定,那么由START决定,如果它也没有被指定,那么由当前定位符号的值决定。
NOCROSSREFS关键字指定各section之间不能交叉引用,否则报错。
对于OVERLAY描述的每个section,连接器将定义两个符号__load_start_SECNAME和__load_stop_SECNAME,这两个符号的值分别代表SECNAME section的LMA地址的开始和结束。
连接器处理完OVERLAY描述语句后,将定位符号的值加上所有覆盖图内section大小的最大值。
看个例子吧,
SECTIONS{

OVERLAY 0×1000 : AT (0×4000)
{
.text0 { o1/*.o(.text) }
.text1 { o2/*.o(.text) }
}

}
.text0 section和.text1 section的VMA地址是0×1000,.text0 section加载于地址0×4000,.text1 section紧跟在其后。
程序代码,拷贝.text1 section代码,
extern char __load_start_text1, __load_stop_text1;
memcpy ((char *) 0×1000, &__load_start_text1,
&__load_stop_text1 – &__load_start_text1);
8. 内存区域命令
—————
注意:以下存储区域指的是在程序地址空间内的。
在默认情形下,连接器可以为section分配任意位置的存储区域。你也可以用MEMORY命令定义存储区域,并通过输出section描述的> REGION属性显示地将该输出section限定于某块存储区域,当存储区域大小不能满足要求时,连接器会报告该错误。
MEMORY命令的文法如下,
MEMORY {
NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2
NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2

}
NAME :存储区域的名字,这个名字可以与符号名、文件名、section名重复,因为它处于一个独立的名字空间。
ATTR :定义该存储区域的属性,在讲述SECTIONS命令时提到,当某输入section没有在SECTIONS命令内引用时,连接器会把该输入 section直接拷贝成输出section,然后将该输出section放入内存区域内。如果设置了内存区域设置了ATTR属性,那么该区域只接受满足该属性的section(怎么判断该section是否满足?输出section描述内好象没有记录该section的读写执行属性)。ATTR属性内可以出现以下7个字符,
R 只读section
W 读/写section
X 可执行section
A ‘可分配的’section
I 初始化了的section
L 同I
! 不满足该字符之后的任何一个属性的section
ORIGIN :关键字,区域的开始地址,可简写成org或o
LENGTH :关键字,区域的大小,可简写成len或l
例子,
MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 256K
ram (!rx) : org = 0×40000000, l = 4M
}
此例中,把在SECTIONS命令内*未*引用的且具有读属性或写属性的输入section放入rom区域内,把其他未引用的输入section放入 ram。如果某输出section要被放入某内存区域内,而该输出section又没有指明ADDRESS属性,那么连接器将该输出section放在该区域内下一个能使用位置。
9. PHDRS命令
————
该命令仅在产生ELF目标文件时有效。
ELF目标文件格式用program headers程序头(程序头内包含一个或多个segment程序段描述)来描述程序如何被载入内存。可以用objdump -p命令查看。
当在本地ELF系统运行ELF目标文件格式的程序时,系统加载器通过读取程序头信息以知道如何将程序加载到内存。要了解系统加载器如何解析程序头,请参考ELF ABI文档。
在连接脚本内不指定PHDRS命令时,连接器能够很好的创建程序头,但是有时需要更精确的描述程序头,那么PAHDRS命令就派上用场了。
注意:一旦在连接脚本内使用了PHDRS命令,那么连接器**仅会**创建PHDRS命令指定的信息,所以使用时须谨慎。
PHDRS命令文法如下,
PHDRS
{
NAME TYPE [ FILEHDR ] [ PHDRS ] [ AT ( ADDRESS ) ]
[ FLAGS ( FLAGS ) ] ;
}
其中FILEHDR、PHDRS、AT、FLAGS为关键字。
NAME :为程序段名,此名字可以与符号名、section名、文件名重复,因为它在一个独立的名字空间内。此名字只能在SECTIONS命令内使用。
一个程序段可以由多个‘可加载’的section组成。通过输出section描述的属性:PHDRS可以将输出section加入一个程序段,: PHDRS中的PHDRS为程序段名。在一个输出section描述内可以多次使用:PHDRS命令,也即可以将一个section加入多个程序段。
如果在一个输出section描述内指定了:PHDRS属性,那么其后的输出section描述将默认使用该属性,除非它也定义了:PHDRS属性。显然当多个输出section属于同一程序段时可简化书写。
在TYPE属性后存在FILEHDR关键字,表示该段包含ELF文件头信息;存在PHDRS关键字,表示该段包含ELF程序头信息。
TYPE可以是以下八种形式,
PT_NULL 0
表示未被使用的程序段
PT_LOAD 1
表示该程序段在程序运行时应该被加载
PT_DYNAMIC 2
表示该程序段包含动态连接信息
PT_INTERP 3
表示该程序段内包含程序加载器的名字,在linux下常见的程序加载器是ld-linux.so.2
PT_NOTE 4
表示该程序段内包含程序的说明信息
PT_SHLIB 5
一个保留的程序头类型,没有在ELF ABI文档内定义
PT_PHDR 6
表示该程序段包含程序头信息。
EXPRESSION 表达式值
以上每个类型都对应一个数字,该表达式定义一个用户自定的程序头。
AT(ADDRESS)属性定义该程序段的加载位置(LMA),该属性将**覆盖**该程序段内的section的AT()属性。
默认情况下,连接器会根据该程序段包含的section的属性(什么属性?好象在输出section描述内没有看到)设置FLAGS标志,该标志用于设置程序段描述的p_flags域。
下面看一个典型的PHDRS设置,
PHDRS
{
headers PT_PHDR PHDRS ;
interp PT_INTERP ;
text PT_LOAD FILEHDR PHDRS ;
data PT_LOAD ;
dynamic PT_DYNAMIC ;
}
SECTIONS
{
. = SIZEOF_HEADERS;
.interp : { *(.interp) } :text :interp
.text : { *(.text) } :text
.rodata : { *(.rodata) } /* defaults to :text */

. = . + 0×1000; /* move to a new page in memory */
.data : { *(.data) } :data
.dynamic : { *(.dynamic) } :data :dynamic

}
10. 版本号命令
————–
当使用ELF目标文件格式时,连接器支持带版本号的符号。
读者可以发现仅仅在共享库中,符号的版本号属性才有意义。
动态加载器使用符号的版本号为应用程序选择共享库内的一个函数的特定实现版本。
可以在连接脚本内直接使用版本号命令,也可以将版本号命令实现于一个特定版本号描述文件(用连接选项–version-script指定该文件)。
该命令的文法如下,
VERSION { version-script-commands }
以下内容直接拷贝于以前的文档,
===================== 开始 ==================================
内容简介
———
0 前提
1 带版本号的符号的定义
2 连接到带版本的符号
3 GNU扩充
4 我的疑问
5 英文搜索关键字
6 我的参考
0. 前提
– 只限于ELF文件格式
– 以下讨论用gcc
1. 带版本号的符号的定义(共享库内)
文件b.c内容如下,
int old_true()
{
return 1;
}
int new_true()
{
return 2;
}
写连接器的版本控制脚本,本例中为b.lds,内容如下
VER1.0{
new_true;
};
VER2.0{
};
$gcc -c b.c
$gcc -shared -Wl,–version-script=b.lds -o libb.so b.o
可以在{}内填入要绑定的符号,本例中new_true符号就与VER1.0绑定了。
那么如果有一个应用程序连接到该库的new_true符号,那么它连接的就是VER1.0版本的new_true符号
如果把b.lds更改为,
VER1.0{
};
VER2.0{
new_true;
};
然后在生成libb.so文件,在运行那个连接到VER1.0版本的new_true符号的应用程序,可以发现该应用程序不能运行了,
因为库内没有VER1.0版本的new_true,只有VER2.0版本的new_true。
2. 连接到带版本的符号
写一个简单的应用(名为app)连接到libb.so,应用符号new_true
假设libb.so的版本控制文件为,
VER1.0{
};
VER2.0{
new_true;
};
$ nm app | grep new_true
U new_true@@VER1.0
$
用nm命令发现app连接到VER1.0版本的new_true
3. GNU的扩充
它允许在程序文件内绑定 *符号* 到 *带版本号的别名符号*
文件b.c内容如下,
int old_true()
{
return 1;
}
int new_true()
{
return 2;
}
__asm__( “.symver old_true,true@VER1.0″ );
__asm__( “.symver new_true,true@@VER2.0″ );
其中,带版本号的别名符号是true,其默认的版本号为VER2.0
供连接器用的版本控制脚本b.lds内容如下,
VER1.0{
};
VER2.0{
};
版本控制文件内必须包含版本VER1.0和版本VER2.0的定义,因为在b.c文件内有对他们的引用
****** 假定libb.so与app.c在同一目录下 ********
以下应用程序app.c连接到该库,
int true();
int main()
{
printf( “%d “, true );
}
$ gcc app.c libb.so
$ LD_LIBRARY_PATH=. ./app
2
$ nm app | grep true
U true@@VER2.0
$
很明显,程序app使用的是VER2.0版本的别名符号true,如果在b.c内没有指明别名符号true的默认版本,
那么gcc app.c libb.so将出现连接错误,提示true没有定义。
也可以在程序内指定特定版本的别名符号true,程序如下,
__asm__( “.symver true,true@VER1.0″ );
int true();
int main()
{
printf( “%d “, true );
}
$ gcc app.c libb.so
$ LD_LIBRARY_PATH=. ./app
1
$ nm app | grep true
U true@VER1.0
$
显然,连接到了版本号为VER1.0的别名符号true。其中只有一个@表示,该版本不是默认的版本
我的疑问:
版本控制脚本文件中,各版本号节点之间的依赖关系
英文搜索关键字:
.symver
versioned symbol
version a shared library
参考:
info ld, Scripts node
===================== 结束 ==================================
11. 表达式
———-
表达式的文法与C语言的表达式文法一致,表达式的值都是整型,如果ld的运行主机和生成文件的目标机都是32位,则表达式是32位数据,否则是64位数据。
能够在表达式内使用符号的值,设置符号的值。
下面看六项表达式相关内容,
常表达式:
_fourk_1 = 4K; /* K、M单位 */
_fourk_2 = 4096; /* 整数 */
_fourk_3 = 0×1000; /* 16 进位 */
_fourk_4 = 01000; /* 8 进位 */
1K=1024 1M=1024*1024
符号名:
没有被引号””包围的符号,以字母、下划线或’.’开头,可包含字母、下划线、’.’和’-’。当符号名被引号包围时,符号名可以与关键字相同。如,
“SECTION”=9
“with a space” = “also with a space” + 10;
定位符号’.’:
只在SECTIONS命令内有效,代表一个程序地址空间内的地址。
注意:当定位符用在SECTIONS命令的输出section描述内时,它代表的是该section的当前**偏移**,而不是程序地址空间的绝对地址。
先看个例子,
SECTIONS
{
output :
{
file1(.text)
. = . + 1000;
file2(.text)
. += 1000;
file3(.text)
} = 0×1234;
}
其中由于对定位符的赋值而产生的空隙由0×1234填充。其他的内容应该容易理解吧。
再看个例子,
SECTIONS
{
. = 0×100
.text: {
*(.text)
. = 0×200
}
. = 0×500
.data: {
*(.data)
. += 0×600
}
} .text section在程序地址空间的开始位置是0x
表达式的操作符:
与C语言一致。
优先级 结合顺序 操作符
1 left ! – ~ (1)
2 left * / %
3 left + -
4 left >>  =
6 left &
7 left |
8 left &&
9 left ||
10 right ? :
11 right &= += -= *= /= (2)
(1)表示前缀符,(2)表示赋值符。
表达式的计算:
连接器延迟计算大部分表达式的值。
但是,对待与连接过程紧密相关的表达式,连接器会立即计算表达式,如果不能计算则报错。比如,对于section的VMA地址、内存区域块的开始地址和大小,与其相关的表达式应该立即被计算。
例子,
SECTIONS
{
.text 9+this_isnt_constant :
{ *(.text) }
}
这个例子中,9+this_isnt_constant表达式的值用于设置.text section的VMA地址,因此需要立即运算,但是由于this_isnt_constant变量的值不确定,所以此时连接器无法确立表达式的值,此时连接器会报错。
相对值与绝对值:
在输出section描述内的表达式,连接器取其相对值,相对与该section的开始位置的偏移
在SECTIONS命令内且非输出section描述内的表达式,连接器取其绝对值
通过ABSOLUTE关键字可以将相对值转化成绝对值,即在原来值的基础上加上表达式所在section的VMA值。
例子,
SECTIONS
{
.data : { *(.data) _edata = ABSOLUTE(.); }
}
该例子中,_edata符号的值是.data section的末尾位置(绝对值,在程序地址空间内)。
内建函数:
ABSOLUTE(EXP) :转换成绝对值
ADDR(SECTION) :返回某section的VMA值。
ALIGN(EXP) :返回定位符’.’的修调值,对齐后的值,(. + EXP – 1) & ~(EXP – 1)
BLOCK(EXP) :如同ALIGN(EXP),为了向前兼容。
DEFINED(SYMBOL) :如果符号SYMBOL在全局符号表内,且被定义了,那么返回1,否则返回0。例子,
SECTIONS { …
.text : {
begin = DEFINED(begin) ? begin : . ;

}

}
LOADADDR(SECTION) :返回三SECTION的LMA
MAX(EXP1,EXP2) :返回大者
MIN(EXP1,EXP2) :返回小者
NEXT(EXP) :返回下一个能被使用的地址,该地址是EXP的倍数,类似于ALIGN(EXP)。除非使用了MEMORY命令定义了一些非连续的内存块,否则NEXT(EXP)与ALIGH(EXP)一定相同。
SIZEOF(SECTION) :返回SECTION的大小。当SECTION没有被分配时,即此时SECTION的大小还不能确定时,连接器会报错。
SIZEOF_HEADERS :
sizeof_headers :返回输出文件的文件头大小(还是程序头大小),用以确定第一个section的开始地址(在文件内)。???
12. 暗含的连接脚本
输入文件可以是目标文件,也可以是连接脚本,此时的连接脚本被称为 暗含的连接脚本
如果连接器不认识某个输入文件,那么该文件被当作连接脚本被解析。更进一步,如果发现它的格式又不是连接脚本的格式,那么连接器报错。
一个暗含的连接脚本不会替换默认的连接脚本,仅仅是增加新的连接而已。
一般来说,暗含的连接脚本符号分配命令,或INPUT、GROUP、VERSION命令。
在连接命令行中,每个输入文件的顺序都被固定好了,暗含的连接脚本在连接命令行内占住一个位置,这个位置决定了由该连接脚本指定的输入文件在连接过程中的顺序。
典型的暗含的连接脚本是libc.so文件,在GNU/linux内一般存在/usr/lib目录下。

 | 28 views | 0 comments | 0 flags | 

Java为什么需要保留基本数据类型

http://www.importnew.com/11915.html

基本数据类型对以数值计算为主的应用程序来说是必不可少的。

自从1996年Java发布以来,基本数据类型就是Java语言的一部分。John Moore通过对使用基本类型和不使用基本类型做java基准测试给Java中为什么要保留基本数据类型做了一个很有力的说明。然后,他还在特定类型的应用中把Java和Scala、C++和JavaScript的性能做了对比。在这些应用中,使用基本数据类型应用性能会有很显著的不同。

问:影响买房最重要的三个因素是什么?
答:位置!位置!还是位置!!

这是个很古老但却经常被提及的谚语。意思是,当购买房产的时候,位置因素是绝对的主导因素。与此类似,考虑在Java中使用基本数据类型的3个最重要的因素,那就是性能!性能!还是性能!!房产与基本数据类型有两个不同之处。首先,位置主导几乎适用于所有买房的情况。但是使用基本类型带来的性能提升,对不同类型的应用程序来大不相同。其次,尽管其他的因素与位置相比在买房时都显得不太重要,但是也是应该被考虑的因素。而使用基本数据类型的原因却只有一个——那就是性能,并且只适用于那些使用后能提升性能的应用。

基本数据类型对大多数业务相关或网络应用程序没有太大的用处,这些应用一般是采用客户端/服务器模式,后端有数据库。然而,使用基本数据类型对那种以数值计算为主的应用提升性能有很大好处。

在Java语言中包含基本数据类型是最有争议的设计决定之一,关于这个决定讨论的文章和帖子比比皆是。2011年9月,Simon Ritter在JAX London上的演讲说,要认真考虑在Java未来的某个版本中删除基本数据类型。接下来,我会简要介绍基本数据类型和Java的双类型系统。通过示例代码和简单的基准测试说明为什么基本数据类型对于某些类型的应用程序是必须的。我也会对Java与Scala、C++和JavaScript性能做一些比较。

测量软件性能

软件性能经常用时间和空间来衡量。时间可以是实际的运行时间,比如3.7分钟,或者是基于输入规模的时间复杂度,比如O(n2)。对于空间的衡量也是如此。经常用内存消耗来衡量,有时候也会扩大到磁盘的使用。改善性能经常要做时间和空间的折衷,缩短时间经常会对空间造成损害,反之亦然。时间复杂度取决于算法,把包装类型切换成基本数据类型对结果不会有任何改变,但是使用基本数据类型取代对象类型能改善时间和空间的性能。

基本数据类型vs对象类型

当你阅读这篇文章的时候,可能已经知道了Java是双类型的系统,也就是基本数据类型和对象类型,简称基本类型和对象。Java中有8个预定义的基本类型,它们的名字都是保留的关键字。常见的基本类型有int、double和boolean。Java中所有其他的类型包括用户自定义的类型,它们必然也是对象类型(我说”必然”是因为数组类型有点例外,与基本类型比数组更像是对象类型)。每一个基本类型都有一个对应的对象包装类,比如int的包装类是Integer,double的包装类是Double,boolean的包装类是Boolean。

基本类型基于值,而对象类型则基于引用。与基本类型相关的争议都源于此。为了说明它们的不同,先来看一下两个声明语句。第一个语句使用的是基本类型,第二个使用的是包装类。

1
2
int n1 = 100;
Integer n2 = new Integer(100);

使用新添加到JDK5的特性自动装箱以后,第二个声明可以简化成:

1
Integer n2 = 100;

但是,底层的语义并没有发生改变。自动装箱简化了包装类的使用,减少了程序员的编码量,但是对运行时并没有任何的改变。

图1展示了基本类型n1和包装对象类型n2的区别。

 

图1. 基本类型vs对象类型的内存结构
图1. 基本类型vs对象类型的内存结构

 

n1持有一个整数的值,但是n2持有的是对一个对象的引用,即那个对象持有整数的值。除此之外,n2引用的对象也包含了一个对Double对象的引用。

基本数据类型存在的问题

在我试图说服你需要基本类型之前,首先我应该感谢不同意我观点的那些人。Sherman Alpert在”基本类型是有害的(Primitive types considered harmful)”这篇文章中说基本类型是有害的,因为“它们把函数式的语义混进了面向对象模型里面,让面向对象变得不纯。基本类型不是对象,但是它们却存在于以一流对象为根本的语言中”。基本类型和(包装类形式的)对象类型提供了两种处理逻辑上相似的类型的方式,但是在底层的语义上却有着非常大的不同。比如,两个实例如何来比较相等性?对于基本类型,使用==操作符,但是对于对象类型,更好的方式是调用equals()方法,而基本类型是没有这个操作的。相似的,在赋值和传参的语义上也是不同的。就连默认值也是不一样的,比如int的默认是值0,但是Integer的默认值是null。

关于这个话题的更多的背景可以参考Eric Bruno的博客”关于基本类型的讨论(A modern primitive discussion)”,里面总结了关于基本类型的正反两方面的意见。Stack Overflow上也有很多关于基本类型的讨论,包括”为什么人们仍然在Java中使用基本类型?(Why do people still use primitive types in Java?)”,”有没有只使用对象不使用基本类型的理由?(Is there a reason to always use Objects instead of primitives?)”。Programmers Stack Exchange上也有一个类似的叫做”Java中什么时候应该使用基本类型和对象类型?(When to use primitive vs class in Java?)”的讨论。

内存的使用

Java中的double总是占据内存的64个比特,但是引用类型的字节数取决于JVM。我的电脑运行64位Win7和64位JVM,因此在我的电脑上一个引用占用64个比特。根据图1,一个double比如n1要占用8个字节(64比特),一个Double比如n2要占用24个字节——对象的引用占8个字节,对象中的double的值占8个字节,对象中对Double对象的引用占8个字节。此外,Java需要使用额外的内存来支持对象的垃圾回收,但是基本类型不需要。下面让我们来验证下。

跟Glen McCluskey在”Java基本类型 VS 包装类型(Java primitive types vs. wrappers)”中使用的方式类似,列表1中的方法会测量一个n*n的double类型的矩阵(二维数组)所占的字节数。

列表1. 计算double类型的内存使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static long getBytesUsingPrimitives(int n)
  {
    System.gc();   // force garbage collection
    long memStart = Runtime.getRuntime().freeMemory();
    double[][] a = new double[n][n];
    // put some random values in the matrix
    for (int i = 0;  i < n;  ++i)
      {
        for (int j = 0; j < n;  ++j)
            a[i][j] = Math.random();
      }
    long memEnd = Runtime.getRuntime().freeMemory();
    return memStart - memEnd;
  }

修改列表1中的代码(没有列出来),改变矩阵的元素类型,我们也可以测量一个nn的Double类型的矩阵所占的字节数。在我的电脑上用10001000的矩阵来测试这两个方法,我得到了表1的结果。就像之前说的那样,基本类型版本的double矩阵中每一个元素占8个多字节,跟我预期的差不多,但是,对象类型版本的Double矩阵中每一个元素占28个字节还要多一点。因此,在这个例子中,Double的内存使用是double的3倍还要多,这对那些明白上面图1说的内存布局的人来说,并不是一件让人很吃惊的事情。

表1. double和Double的内存使用情况对比

版本 总字节数 平均字节数
使用double 8,380,768 8.381
使用Double 28,166,072 28.166

运行时性能

为了比较基本类型和对象类型的运行时性能,我们需要一个数值计算占主导的算法。本文中,我选择了矩阵相乘,然后计算1000*1000的矩阵相乘所需要的时间。我用一种很直观的方式来编码double类型的矩阵相乘,就像列表2中展示的那样。可能会有更快的方式来实现矩阵相乘(比如使用并发),但那个与本文无关。我需要的仅仅是两个很简单的方法,一个使用基本类型的double,另一个使用包装类Double。Double类型的矩阵相乘的代码跟列表2非常相似,仅仅是改了类型。

列表2. 两种类型的浮点数的矩阵相乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static double[][] multiply(double[][] a, double[][] b)
  {
    if (!checkArgs(a, b))
        throw new IllegalArgumentException("Matrices not compatible for multiplication");
    int nRows = a.length;
    int nCols = b[0].length;
    double[][] result = new double[nRows][nCols];
    for (int rowNum = 0;  rowNum < nRows;  ++rowNum)
      {
        for (int colNum = 0;  colNum < nCols;  ++colNum)
          {
                double sum = 0.0;
                for (int i = 0;  i < a[0].length;  ++i)
                    sum += a[rowNum][i]*b[i][colNum];
                result[rowNum][colNum] = sum;
          }
      }
    return result;
  }

在我的计算机上分别用两个方法对两个1000*1000的矩阵做多次乘法,测量执行时间。表2中列出了平均时间。可以看出来,在本例中,double的运行时性能是Double的四倍。这是一个不能忽略的很大的不同!

版本 秒数
使用double 11.31
使用Double 48.48

SciMark2.0基准测试

迄今为止,我使用了一个简单的矩阵相乘的例子来说明基本类型比对象类型有更高的计算性能。为了让我的观点更站得住脚,我会用更科学的基准点(来做测试)。SciMark 2.0是NIST的用来测试科学和数值计算能力的Java基准工具。我下载了它的源码,创建了2个版本的基准测试,一个是使用基本类型,另一个使用包装类。第二个测试中我把int替换成了Integer,把double替换成了Double,这样来看包装类带来的影响。这两个版本在本文的源码中都可以找到。

Java基准测试: 源码下载

SciMark基准测试会测量许多种常见计算的性能,然后用大概的Mflops(每秒百万浮点运算数)给出一个综合的分数。因此,对这样的基准测试来说数据越大越好。表3给出了这个基准测试在我的计算机上对每个版本多次运行的平均综合分数。就像表中展示的那样,这两个版本的SciMark基准测试的运行时性能跟上面矩阵相乘的结果是一致的,基本类型的性能几乎比包装类型快了5倍。

表3. SciMark基准测试的运行时性能

SciMark版本 性能(Mflops)
使用double 710.80
使用Double 143.73

你已经见过使用自己的基准测试和一个更科学的方式对Java程序做数值计算的一些方式,但是,Java和其他语言比起来会怎样呢?看下Java和其他三种编程语言Scala,C++,JavaScript的性能比较,然后我会做出结论。

Scala基准测试

Scale是运行在JVM上的编程语言,貌似因此变得很流行。Scale有统一的类型系统,也就是说它不区分基本类型和对象类型。根据Erik Osheim在Scala的数值类型(第一部分)中说的,Scala在可能的情况下会使用基本类型,但是在必须的时候会使用对象类型(Scala uses primitive types when possible but will use objects if necessary)。与此相似,Martin Odersky对Scale数组的描述说:“Scala的Array[Int]数组对应Java当中的int[],Array[Double]对应Java当中的double[]”。

难道这意味着Scale的统一类型系统和Java的基本类型的运行时性能差不多?让我们来看一下。

Scala性能改善

当我两年前第一次使用Scala运行矩阵相乘的基准测试的时候,平均差不多要用超过33秒的时间,性能大概在Java基本类型和对象类型之间。最近我又用新版本的Scale重新编译一下,然后我被新版本的重大性能改善所震惊了。

我用Scale不像用Java那样熟练,但是我尝试把Java版本的矩阵相乘的基准测试直接转化成Scale版本。结果如下面列表3所示。当我在我的计算机上执行Scale版本的基准测试的时候,平均花费12.30秒,这个跟Java使用基本类型时候的性能非常接近。结果比我预期的要好很多,这个也给Scale声明的对数值类型的处理做了很好的证明。

Scala基准测试:源码下载

列表3. Scala语言实现的矩阵相乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def multiply(a : Array[Array[Double]], b : Array[Array[Double]]) : Array[Array[Double]] =
  {
    if (!checkArgs(a, b))
        throw new IllegalArgumentException("Matrices not compatible for multiplication");
    val nRows : Int = a.length;
    val nCols : Int = b(0).length;
    var result = Array.ofDim[Double](nRows, nCols);
    for (rowNum <- 0 until nRows)
      {
        for (colNum <- 0 until nCols)
          {
            val sum : Double = 0.0;
            for (i <- 0  until a(0).length)
                sum += a(rowNum)(i)*b(i)(colNum);
            result(rowNum)(colNum) = sum;
          }
      }
    return result;
  }

代码转化的风险

就像James Roper指出的那样,当把一种语言直接转化成另一种语言的时候,总是存在风险的。因为缺少Scala的经验,使我意识到转换基准测试的代码有可能是可行的,这会强制Scala在运行时使用更高效的基本类型,同时还可以保留算法的基本特性。因为它用Java编写,所以折衷比较也是有意义的。

C++基准测试

因为C++是直接运行在物理机而非虚拟机上,大家自然会认为C++的速度比Java快。此外,为了确保索引是在数组声明的边界之内,Java会检查数组的访问,这对性能也有轻微的损害,C++不会做这样的检查(这是C++的一个特性,它可以导致缓冲区溢出,这可能会被黑客利用)。我发现,C++在处理基本的二维数组的时候多少有点尴尬,幸运的是,可以把这种尴尬隐藏到类内部的私有部分。我创建了一个简单的C++版本的Matrix类,重载了*操作符,用来做矩阵相乘,基本的矩阵相乘的算法是用Java版本直接转化过来的。列表4列出了C++的源码:

C++基准测试:源码下载

列表4.C++语言实现的矩阵相乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Matrix operator*(const Matrix& m1, const Matrix& m2) throw(invalid_argument)
  {
    if (!Matrix::checkArgs(m1, m2))
          throw invalid_argument("matrices not compatible for multiplication");
    Matrix result(m1.nRows, m2.nCols);
    for (int i = 0;  i < result.nRows;  ++i)
      {
        for (int j = 0;  j < result.nCols;  ++j)
          {
            double sum = 0.0;
            for (int k = 0;  k < m1.nCols;  ++k)
                sum += m1.p[i][k]*m2.p[k][j];
            result.p[i][j] = sum;
          }
      }
    return result;
  }

使用Eclipse CDT和MinGW C++编译器可以创建调试版和正式版的应用程序。为了测试C++的性能,我是多次运行正式版取平均值。正如预期的那样,在这个简单的测试中C++很明显要快得多,在我的计算机上平均是7.58秒。如果性能是选择一个编程语言的主要因素的话,那么C++是数值运算密集型应用的首选。

JavaScript基准测试

好吧,这个测试的结果让我感到震惊。因为Javascript是一种动态语言,我本以为它的性能是最差的,甚至要比Java包装类的性能还要差。但是实际上,Javascript的性能跟Java使用基本类型的性能很接近。为了测试Javascript的性能,我安装了Node.js——它是一个以效率著称的Javascript引擎。测试的平均结果是15.91秒。列表5展示了运行在Node.js下Javascript版本的矩阵相乘基准测试:

JavaScript基准测试:源码下载

列表5. JavaScript语言实现的矩阵相乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function multiply(a, b)
  {
    if (!checkArgs(a, b))
        throw ("Matrices not compatible for multiplication");
    var nRows = a.length;
    var nCols = b[0].length;
    var result = Array(nRows);
    for(var rowNum = 0;  rowNum < nRows; ++rowNum)
      {
        result[rowNum] = Array(nCols);
        for(var colNum = 0;  colNum < nCols;  ++colNum)
          {
            var sum = 0;
            for(var i = 0;  i < a[0].length;  ++i)
                sum += a[rowNum][i]*b[i][colNum];
            result[rowNum][colNum] = sum;
          }
      }
    return result;
  }

结论

当18年前Java第一次登上历史舞台的时候,对于以数值计算为主的应用程序从性能的角度来说,它并不是最好的语言。但时过境迁,随着其他领域技术的发展,比如即时编译(aka自适应或动态编译),当使用基本数据类型的时候,这类Java应用的性能已经可以匹敌编译成本地代码的那些语言。

并且,基本数据类型不需要垃圾回收,因此比对象类型多了另一个性能优势。表4总结了矩阵相乘基准测试在我的计算机上的运行时性能。还有其他的诸如可维护性可移植性和开发者擅长等因素让Java成为许多这类应用的很好地选择。

表4. 矩阵相乘基准测试的运行时性能

结果(以秒计)

C++ Java
(double)
Scala JavaScript Java
(Double)
7.58 11.31 12.30 15.91 48.48

就像前面讨论的那样,看上去Oracle似乎很严肃的考虑了是否在Java未来的版本中去掉基本数据类型。除非Java编译器能产生跟基本数据类型性能相当的代码,我认为把基本数据类型从Java中去掉会妨碍Java在某些类型应用中的使用,即那些以数值计算为主的应用。本文中,我对矩阵相乘做了基准测试,还用更科学的基准测试SciMark2.0来支持这一点。

附录

在本文发表在JavaWorld上几周以后,作者收到了来自Brian Goetz的邮件。Brian Goetz是Oracle的Java语言架构师,他说从Java中移除基本数据类型不在考虑范围之内。移除基本数据类型的说法源自于对Java未来版本的愿景讨论的误解或者是不准确的解释。

关于作者

John I. Moore, Jr.是Citadel的一位数学和计算机教授,他在工业领域和学术上都有很丰富的经验,在面向对象技术,软件工程和应用数学方面有独到的专长。在超过30年的时间里,他使用关系型数据库和很多高级语言来设计和开发软件,工作中广泛使用从1.1开始的Java的各个版本。他对计算机科学的很多高级话题都开设并教授了许多课程和研讨班。

了解更多:

1.Paul Krill在“Oracle的Java长期目标”写到Oracle对Java的长期的一些计划(JavaWorld,2012年3月)。那篇文章和相关的评论促使我写了这篇支持基本类型的文章。

Szymon Guz writes about his results in benchmarking primitive types and wrapper classes in “Primitives and objects benchmark in Java” (SimonOnSoftware, January 2011).
2.Szymon Guz在“Java中基本数据类型和对象类型的基准测试”(SimonOnSoftware,2011年1月)中写了对基本类型和包装类型做基准测试的结果。

3.C++编程准则和实践(Addison-Wesley, 2009)的支持站点上,C++的作者Bjarne提供了一个比本文要完善很多的矩阵类的实现。

4.John Rose,Brian Goetz和Guy Steele在“值的状态”一文中讨论了值的类型这个概念。值的类型可以被认为是没有标识的不变的用户自定义的类型集合,因此,可以把对象类型和基本类型的属性结合起来。值类型的好处是:像引用类型那样编码,像基本类型那样运行。

 | 133 views | 0 comments | 0 flags | 

jQuery的三击效果代码实现

http://www.gbtags.com/gb/share/3288.htm

大家可能常常需要在jQuery实现单机或者双击效果,是不是有时候需要三击效果呢?下面代码可以实现:

  1. $.event.special.tripleclick = {
  2. setup: function(data, namespaces) {
  3. var elem = this, $elem = jQuery(elem);
  4. $elem.bind(‘click’, jQuery.event.special.tripleclick.handler);
  5. },
  6. teardown: function(namespaces) {
  7. var elem = this, $elem = jQuery(elem);
  8. $elem.unbind(‘click’, jQuery.event.special.tripleclick.handler)
  9. },
  10. handler: function(event) {
  11. var elem = this, $elem = jQuery(elem), clicks = $elem.data(‘clicks’) || 0;
  12. clicks += 1;
  13. if ( clicks === 3 ) {
  14. clicks = 0;
  15. // set event type to “tripleclick”
  16. event.type = “tripleclick”;
  17. // let jQuery handle the triggering of “tripleclick” event handlers
  18. jQuery.event.handle.apply(this, arguments)
  19. }
  20. $elem.data(‘clicks’, clicks);
  21. }
  22. };

如何使用呢?

  1. $(“#whatever”).bind(“tripleclick”, function() {
  2. // do something
  3. }

是不是很好用? 别的地方搜刮来的代码,希望大家可以用的上!:D)

 | 164 views | 0 comments | 0 flags | 

程序的库设计

http://www.linuxeden.com/html/news/20140421/150949.html

最近在Stack Exchange上面看到一个帖子,是问程序库设计的指导原则的,“What guidelines should I follow while designing a library?”,有趣的是,很多人都在谈论面向设计,各路API设计,还有程序语言设计,唯独搜索“程序库设计”,无论中文还是英文,Google还是百度都找不到太多内容。但是我想,没有程序员会否认库设计的重要性吧,我想在这里结合这个帖子谈谈我的想法。

在这个帖子里面,votes最高的回答,提到了这样几类tips,我在下面简要叙述一下,其中基础的部分包括:

  • Pin Map,明确你期望库主要用来做什么,但不要把它定得太死,用户要可以比较方便地做出改变。
  • Working Library,一个工作的库,如果它连这点都达不到,一定要注明。没有人希望浪费时间在一个无法工作的程序库上面。
  • Basic Readme,清晰地描述库是用来做什么的,测试的情况等等。
  • Interfaces,接口必须清晰地定义,这可以帮助库的使用者。
  • Special Functions,特殊的功能,一定要注明,包括在readme文档中注明,以及在注释中注明。
  • Busy Waits,如果有一些场景需要使用busy wait(我不知道怎么翻译),其过程中可能会出现异常,使用interrupt或者其它妥善的方法来处理。
  • Comments,你做的任何的改变都要注释清楚,明确描述接口和其每个参数,方法是做什么的,又返回什么;如果有某个中间方法被调用到,就要注明。
  • Consistency,一致性,所有东西,包括注释。相关的方法要放在一个简单的代码文件里面,小但是逻辑一致。

其中的高级部分又包括Detailed Readme,Directory Structure,Licensing和Version Control。

这些都是需要注意的内容,并且大部分对于程序的库设计来说是基础要求,但是这些从重要性来说,并没有说到点上。《C++沉思录》里面有这样一句话:“库设计就是语言设计,语言设计就是库设计”,二者从先定义问题域到后解决问题的思路是类似的。我觉得比较重要的需要考虑的事情包括:

考虑库的目标用户。这听起来扯得有点泛,但实际上这是确切的问题,这是开源库还是你只是在小组内部使用的库,或者是公司内部使用的?用户的能力和需求是不一样的,要求当然不同。

要解决的核心问题。这是上文中Pin Map的一部分,不要重复发明轮子,那么每一个新库都有其存在的价值,这个问题既要通用又要具体,“通用”指的是库总有一个普适性,解决的实际问题对于不同的用户来说是不一样的;而“具体”是指库解决的问题对于程序员来说是非常清晰和直接的。例如设计一个库,根据某种规则把不同的数据类型(XML,BSON或者某种基于行的文本等等)都转换成JSON。

统一的编程风格。很多库都有自己精心设计的一套DSL,比如链式调用等等几种方式,当然,这也和使用的语言有关系。定义一种用起来舒服的编程风格对于程序库的推广是很有好处的。这也是一致性的一个体现。

内聚的调用入口。这和面向对象的“最少知识原则”有类似的地方,把那些不该暴露出去的库内部实现信息隐藏起来,在很多情况下,程序库不得不暴露和要求用户了解一些知识的时候,比如:

1
2
3
4
5
6
7
MappingConfig config = new MappingConfig();
config.put(MappingConfigConstants.ENCODE, "UTF-8");
FileBuilder fileBuilder = new StandardFileBuilder(mapping);
InputStream stream = fileBuilder.build().getInputStream();
DataTransformer<XMLNode> transformer = new XMLDataTransformer(...);
...
transformer.transform(stream);

这里引入了太多的概念,MappingConfig、FileBuilder、DataTransformer等等,整个过程大致是构造了一个数据源,还有一个数据转换器,然后这个数据转换器接受这个数据源来转换出最后结果的过程。那么:

这些象征着概念的接口和类最好以某种易于理解的形式组织起来,比如放在同一层比较浅的包里面,便于寻找;

也可以建立一个facade类,提供几种常用的组合,避免这些繁琐的对象构建和概念理解,例如:

1
XXFacade.buildCommonXMLTransformer();

向后兼容。当然,这一点也可以归纳到前文提到的一致性里面去。我曾经拿JDKHashTable举了一个例子,它的containsValue和contains方法其实是一样的,造成这种情况的一个原因就是为了保持向后兼容。

依赖管理。依赖管理很多情况下是一个脏活累活,但是却不得不考虑到。通常来说,任何一个库考虑自己的依赖库时都必须慎重,尤其是面对依赖的库需要升级的时候。如果依赖的库出了问题,自己设计的程序库也可能因此连累。

完善的测试用例。通常来说,程序库都配套有单元测试保证,无论是什么语言写的。

健全的文档组织。通常包括教程(tutorial)、开发者文档(developer guide)和接口API文档(API doc)。前者是帮助上手和建议使用的,中间的这个具备详尽的特性介绍,后者则是传统的API参考使用文档。

转自 http://blog.jobbole.com/65709/

 | 189 views | 0 comments | 0 flags | 

video post

this is a post with video

[jwplayer playlistid="185"]

 | 144 views | 0 comments | 0 flags | 

OAuth2 Authentication

http://developer.wordpress.com/docs/oauth2/

 

OAuth2 Authentication

 

OAuth2 is a protocol that allows applications to interact with blogs on WordPress.com and self-hosted WordPress sites running Jetpack.

The primary goal of OAuth is to allow developers to interact with WordPress.com and Jetpack sites without requiring them to store sensitive credentials. Our implementation also allows users to manage their own connections.

If you are new to the world of OAuth, you can read more at http://oauth.net.

If you are already familiar with OAuth, then all you really need to know about are the two authentication endpoints. The authorization endpoint and the token request endpoint.

These endpoints are

 

https://public-api.wordpress.com/oauth2/authorize

https://public-api.wordpress.com/oauth2/token

 

The same endpoints are used for WordPress.com blogs and Jetpack sites.

Before you begin to develop an application, you will need a client id and a client secret key. The client id and client secret key will be used to authenticate your application and verify that the API calls being are valid. You can sign up for an id and secret at ourapplications manager.

Receiving an Access Token

To act on a user’s behalf and make calls from our API you will need an access token. To get an access token you need to go through the access token flow and prompt the user to authorize your application to act on his or her behalf.

Access tokens are currently per blog per user for most of our endpoints. This means that you will need a separate access token for each blog that a user owns and that you want access to. There are certain endpoints like likes and follows where you can use a users token on any blog to act on their behalf.

To begin, you will need to send the user to the authorization endpoint.

https://public-api.wordpress.com/oauth2/authorize?client_id=your_client_id&redirect_uri=your_url&response_type=code

client_id should be set to your application’s client id. response_type should always be set to “code”. redirect_uri should be set to the URL that the user will be redirected back to after the request is authorized. The redirect_uri should be set in the applications manager.

The redirect to your application will include a code which you will need in the next step. If the user has denied access to your app, the redirect will include ?error=access_denied

Optionally you may also pass along a blog parameter (&blog=) with the URL to a WordPress.com blog or Jetpack site. If you do not pass along a URL, or if the user does not have administrative access to manage the blog you passed along, then the user will be prompted to select the blog they are granting you access to.

Once the user has authorized the request, he or she will be redirected to the redirect_url. The request will look like the following:

http://developer.wordpress.com/?code=cw9hk1xG9k

This is a time-limited code that your application can exchange for a full authorization token. To do this you will need to pass the code to the token endpoint by making a POSTrequest to the token endpoint: https://public-api.wordpress.com/oauth2/token.

1
2
3
4
5
6
7
8
9
10
11
12
13
curl_setopt( $curl, CURLOPT_POST, true );
curl_setopt( $curl, CURLOPT_POSTFIELDS, array(
    'client_id' => your_client_id,
    'redirect_uri' => your_redirect_url,
    'client_secret' => your_client_secret_key,
    'code' => $_GET['code'], // The code from the previous request
    'grant_type' => 'authorization_code'
) );
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1);
$auth = curl_exec( $curl );
$secret = json_decode($auth);
$access_key = $secret->access_token;

You are required to pass client_id, client_secret, and redirect_uri for web applications. These parameters have to match the details for your application, and the redirect_urimust match the redirect_uri used during the Authorize step (above). grant_type has to be set to “authorization_code”. code must match the code you received in the redirect.

If everything works correctly and the user grants authorization, you will get back a JSON-encoded string containing the token and some basic information about the blog:

{ "access_token": "YOUR_API_TOKEN", "blog_id": "blog id", "blog_url": "blog url", "token_type": "bearer" }

You now have an access token which should be stored securely with the blog id and blog url. This access token allows our application to act on the behalf of the user on this specific blog.

Making an API Call

Our API is JSON based. You can view all of the available endpoints at our API documentation. You can also make API calls with our legacy XML-RPC API.

In order to make an authenticated call to your APIS, you need to include your access token with the call. OAuth2 uses a BEARER token that is passed along in an Authorization header.

1
2
3
4
5
6
<?php
$access_key = "YOUR_API_TOKEN";
curl_setopt( $curl, CURLOPT_HTTPHEADER, array( 'Authorization: Bearer ' . $access_key ) );
curl_exec( $curl );
?>

The above example would return blog post ’43′ from our developer blog. You can make similar calls to the other available endpoints.

 | 304 views | 0 comments | 0 flags | 

关于MapReduce的思考

MapReduce为什么基本计算数据单元是<Key,Value>,因为它要通过Key来找计算结点,有了Key它才能Hash,才能让计算结点间负载均衡。
MapReduce通过把各计算结点的计算能力统一管理、协调,实现群集计算,这要求各计算结点间,无状态依赖,就是把你的输入处理完,给出输出,咱俩就没关系了。如果各计算结点是以class API的形式暴露的,就是Elastos了。Elastos在物联网模式,各计算结点耦合力比web强,比单机弱,就是这样的计算模型。
一个群集内,各计算结点都声明好自己的输入(供请求的API),同时再声明自己需要的API,就象是一个PE文件的导入、导出表,采用MapReduce这种思想,就构成了一个计算群集,参数传递自己序列化marshling/unmarshling,这样的OS有价值吗?Elastos就是这样的。

 | 223 views | 0 comments | 0 flags | 

【转】很多学习笔记而且整理得很好

1 index

1.1 Operating System

  • Computer System Reading 关于单机系统方面的一些文章以及阅读心得。
  • APUE Unix环境高级编程(Advanced Programming Unix Environment),W. Richard Stevens的神作。笔记里面没有包含书最后的几个部分,比如终端,打印机等,因为我觉得可能大家都不太需要这个东西了。我还尝试将一些跨章节的概念整合到一起,这样比较容易从总体把握Unix编程环境。
  • Linux 主要是介绍Linux下面一些工具使用以及和内核相关的知识。(将原来APUE 和 UNP 中的一部分内容放在这里面来了,这样可以保持这两篇内容比较稳定)
  • PIC 分析了一下PIC位置无关代码内部的原理以及和动态库之间的关系。通过阅读<深入理解计算机系统>并且结合实际的例子总结出来的。
  • GCC-Assembly 如何编写GCC内嵌汇编,以及一些关于GCC内嵌汇编的文章,主要是参考了<GCC Manual>. 但是自己对这个依然不是很了解。
  • Encoding 介绍了GB2312/GBK/GB18030/Unicode/UTF16/UTF32/UTF8这几种字符编码格式。还是觉得UTF8在设计以及实现上都相对更加合理。
  • CPU CPU相关
    • SIMD SIMD(Single Instruction Muitple Data)单指令多数据。这个笔记其实是Intel Reference Manual中关于SIMD指令的总结。里面包含了一些理解SIMD指令需要的知识,以及对SIMD指令进行了分类。遗憾的是里面没有什么过多的例子,毕竟这个是结合场景来使用的。
  • Concurrency 并发
  • Coroutine 协程,轻量级线程。
  • Lock 锁的原理与不同实现
  • Memory 内存相关
    • NUMA Non-Uniform Memory Access. 非一致性内存访问
    • Memory Barrier 内存屏障
    • TCMalloc Google的开源线程缓存内存分配器,解决多线程下面内存分配效率问题。
  • Continuation 延续,异步编程一种实现。
  • System-Peformance 系统性能相关
    • OProfile OProfile(系统级profiler)的原理和应用。不过说实话没有分析过源代码(或者是自己技术背景不行)终究觉得对这个东西理解不够深入,而且自己也仅仅是使用OProfile功能的子集。
    • SystemTap 通过将观察语句编译成为内核驱动,和linux内核提供的接口匹配,来深度地观察linux操作系统。
    • gperftools Google的性能分析工具,TCMalloc 实现也在里面。用来观察应用程序似乎是个不错的选择。
    • Perf Linux系统自带的性能分析工具,支持硬件以及软件事件计数器,支持profile kernel以及user code.
  • Akka Akka is a toolkit and runtime for building highly concurrent, distributed, and fault tolerant event-driven applications on the JVM.
  • Azkaban Linkedin的工作流系统,和 Oozie 功能相似但是相比好用很多。UI不错,概念也比较清晰。

1.2 Compression Technology

  • Snappy Google的开源压缩解压库。在满足一定压缩比率的条件下着重提升压缩和解压速度。
  • Lzf Redis 使用的开源压缩解压库。轻量(两个文件), 可以很容易地独立纳入项目。
  • Lzma Lempel-Ziv-Markov chain algorithm ,压缩速度相对较慢但是压缩比超高。

1.3 Tool and Desktop

  • Tool
    • License 一些常见的开源协议。不知道自己以后是否可以用得上:)
    • Web Misc Web开发遇到的一些问题。因为自己对于Web开发不太了解,所以内容上的话可能显得有点弱智。
    • SWIG C/C++多语言扩展接口生成器,使用起来非常方便(至于生成的代码没有看过效率如何).个人觉得比较适合quick & dirty的方案。
    • Git 分布式版本控制系统。很多项目都在使用Git进行版本管理包括Linux Kernel, Ruby on Rails, WINE, X.org等。
    • Folly Folly is an open-source C++ library developed and used at Facebook.
    • Flex/Bison 可以用来书写词法和语法分析器,Bison支持的语法是LALR(1)。
    • Valgrind CPU和内核模拟器,通过暴露接口可以构建动态分析工具,比如内存调试,检测内存泄露,多线程分析和性能分析等。
    • Protocol-Buffers Google的数据交换格式,能够高效地序列化和反序列化对象,同时考虑向后兼容问题。
    • Thrift Facebook的数据交换格式,能够高效地序列化和反序列化对象,同时考虑向后兼容问题。
    • GDB GNU Debugger, mainly on C/C++. 不过现在比较习惯打印日志方式来调试。
    • lcov the LTP(Linux Testing Project) GCOV extension.用来完成C/C++测试覆盖。
    • BuildSystem 在实现Baidu in-house的构建工具Comake2之前,做过构建系统的调研总结出来的。在语法表达上面(非常重要)借鉴了SCons,考虑了其他构建系统提供的功能。
      • Maven 主要针对Java开发的基于工程对象模型(POM, Project Object Model)构建系统,主要记录了一些使用方面的问题和解决办法。
      • SBT Simple(or Scala?) Build Tool
  • Desktop
    • Ubuntu 如何更好地使用Ubuntu. 之前有过痛苦的经历 。主要记录自己使用出现的问题。
    • FVWM F* Virtual Windows Manager.Linux下面高度可定制化的窗口管理器。
    • Macintosh 如何更好地使用Mac。以前使用Windows,后来转向Ubuntu,再后来买了个MBA. 熟悉它又需要一段时间了。
    • Emacs 如何更好地使用Emacs。自己依然只是使用一些简单功能,身边有很多牛人用Emacs那是相当的出神入化。
    • Eclipse 虽然我喜欢Emacs,但是似乎java方面开发似乎还是离不开它。
    • Intellij Eclipse太慢了,看看Intellij怎么样。

1.4 Algorithm Related

1.5 Network Programming

  • UNP Unix网络编程(Unix Network Programming),W. Richard Stevens的又一神作。笔记里面只是对于TCP原理有比较详细讨论,这部分笔记也包含了TCP Illustrated v1里面和TCP相关的章节内容。对于编程方面也着重TCP socket使用,毕竟在大部分应用场景下面我们选用TCP模型更多,并且TCP里面有很多非常琐碎的知识。
  • itachi 自己两天时间写完的异步网络编程框架,当然有很多地方需要改进,但是内核基本稳定了。并且在上面做了asocket封装,编写网络程序非常方便。TODO(dirlt):考虑写篇文章介绍一些
  • libev 开源的事件触发器。被认为是更高效的libevent. itachi 这个项目开始想自己编写事件触发器,但是发现工作比较琐碎枯燥,所以底层还是选择使用libev.
  • HPServer 开源的网络编程框架。可以当做一个入门级的网络编程框架阅读。这个是我最开始阅读的网络编程框架(1st).
  • NMSTL 开源的网络编程框架。一个很早期的作品,代码十分简洁。主要是内部实现了SEDA的思想。这个是我第二个阅读的网络编程框架(2nd).
  • Muduo 开源的网络编程框架。作者理想中的网络编程框架实现,里面有很多mina/netty的影子。这个是我三个阅读的网络编程框架(3rd).
  • Kylin Baidu in-house的异步编程框架,是linsd(百度首席架构师林仕鼎)的神作,通过阅读这个框架的代码让我理解了异步编程模型。这个是我第四个阅读的网络编程框架(4th).
  • ZeroMQ 开源的消息传输系统。颠覆我们思考和编写网络通信程序的方式。TODO(dirlt):只是阅读了文档和API,可以考虑阅读一下代码.
  • ACE 开源的网络编程框架。非常重量级,也被人诟病为学术产物而不是适合生产实践。TODO(dirlt):只是有大概的了解,有待更深入的研究.
  • Apache 历史悠久的开源HTTP服务器。 an effort to develop and maintain an open-source HTTP server for modern operating systems including UNIX and Windows NT
  • Netty an asynchronous event-driven network application framework in Java based on Java NIO.
  • Finagle an extensible RPC system for the JVM, used to construct high-concurrency servers.
  • HAProxy 高性能的负载均衡器,可以提供4(TCP),7(HTTP)层两种代理。

1.6 Storage System

  • Storage System Reading 关于存储系统方面的一些文章以及阅读心得。
    • Backblaze Storage Pod 构建廉价存储服务器的厂商。将其设计以及使用公开并且做了比较深入的说明。
  • DBMS DBMS(database management system)现在正在研究。打算首先阅读一下数据库系统基础教程(A First Course in Database Systems by Jeffrey D. Ullman),然后看看另外一本也是Jeffrey D. Ullman写的数据库系统实现(Database System Implementation).主要是了解DBMS方面的理论和大致实现,之后会稍微结合现有数据库实现阅读代码(MySQL/PostgreSQL).
  • LevelDB Google的开源kv存储系统。支持billion级别的数据量,适合于写少读多的情况。当时阅读的时候是从github上面clone下来的,可能还存相当多的bug.
  • MongoDB 面向文档的分布式存储系统,但是却可以针对文档字段索引来加快查询。功能上比通常我们认为的NoSQL强但是弱于RDBMS.
  • Redis 内存存储系统,支持丰富的数据类型以及相应的计算(支持持久化)。外围包装网络访问接口(并且提供了丰富的客户端),可以比较方便地在分布式系统内或者是环境下面使用.
  • MySQL 开源关系型数据库。The world’s most popular open source database.
  • SSD solid state disk.固态硬盘
  • RAID Redundant Array of Inexpensive Disk. 廉价磁盘冗余阵列
  • Memcached an in-memory key-value store for small chunks of arbitrary data (strings, objects) 可以用来搭建分布式缓存服务,没有持久化存储。
  • Gizzard a library for creating distributed datastores 可以认为是数据库中间层,完成partition/replication,也做fault-tolerant migration.
  • RocksDB A persistent key-value store for fast storage environments. 基于 LevelDB 构建,借鉴了 HBase 的思想。

1.7 Distributed System

1.8 Programming Language

  • Programming Language including following languages:
    • C/C++ # C++ (pronounced “cee plus plus”) is a statically typed, free-form, multi-paradigm, compiled, general-purpose programming language. It is regarded as an intermediate-level language, as it comprises a combination of both high-level and low-level language features. Wikipedia
    • Scheme # Scheme is a functional programming language and one of the two main dialects of the programming language Lisp. Wikipedia
    • Java # Java is a programming language originally developed by James Gosling at Sun Microsystems (which has since merged into Oracle Corporation) and released in 1995 as a core component of Sun Microsystems’ Java platform. The language derives much of its syntax from C and C++ but has a simpler object model and fewer low-level facilities. Wikipedia
      • JNI Java Native Interface
      • JVM Java Virtual Machine
    • Clojure # Clojure (pronounced like “closure”) is a recent dialect of the Lisp programming language created by Rich Hickey. It is a functional general-purpose language. Its focus on programming with immutable values and explicit progression-of-time constructs are intended to facilitate the development of more robust programs, particularly multithreaded ones. Wikipedia
    • Python # Python is a general-purpose, high-level programming language whose design philosophy emphasizes code readability. Its syntax is said to be clear and expressive. Python has a large and comprehensive standard library. Wikipedia
    • Go # Go is a compiled, garbage-collected, concurrent programming language developed by Google Inc. Wikipedia
    • Scala # Scala is an object-functional programming and scripting language for general software applications, statically typed, designed to concisely express solutions in an elegant, type-safe and lightweight (low ceremonial) manner. Wikipedia

1.9 Software Design

1.10 About Me

My name is Zhang Yan. I get MS. at Shandong University on Computer Science and BEng. at Shandong University on Electronic Engineering. I currently work as Software Architect in Data Platform Team at Umeng which focus on mobile analytics since 2012.5 where I manily work on building the infrastructure of big data processing. From 2010.6 to 2012.6, I have been a Senior Software Engineer at Baidu Infrastructure Tream where I wrote libraries, tools, services and distributed systems. From 2008.7 to 2010.6, I have been a Software Engineering Intern at Baidu Component Tream where I wrote libraries and tools. You can dig me more on my linkedin(or resume)

Here are my some notes written casually to record my life footprint. I think it will be fun to read them when I became old. Also there are some words on my blog

My code name is dirtysalt or dirlt. It’s translated from my chinese name. The translation works as following:

  • Zhang pronounce like ‘dirty’ in cn
  • Yan pronounce like in ‘salt’ in cn
  • then my code name is the combination of ‘dirty’ and ‘salt’ as ‘dirtysalt’
  • ‘dirtysalt’ can be abbreviated to ‘dirlt’.

Here is my contact info. I think email is the easiest way to get me.

My favouritest words comes from theidea of Valve : “Open your mind, Open your eyes” (放眼未来,自由想象), with a image from its game “Half Life”

Especially thanks to xuchaoqian for encouraging me to write and providing me host initially so I can publish my writings. And also thanks people who help me, support me, and accompany me, you complete me.

 | 258 views | 0 comments | 0 flags |