0%

lua5.2暂时不使用,使用lua5.1+luarocks

  1. 看readline软件包是否安装(dpkg -l | grep -i 软件名)
  2. 是否安装ncurses安装包(ncurses安装包 )
  3. 是否安装libncurses5-dev 或libncursesw5-dev软件包(apt-get install libncurses5-dev)

ncurses-5.9.tar.gz

$ ./configure
$ make
$ sudo make install

readline-6.3.tar.gz

$ ./configure
$ make
$ sudo make install
$ sudo ldconfig

lua-5.2.3.tar.gz

$ sudo apt-get install libreadline6-dev
$ sudo apt-get install libreadline6-dbg
$ make linux
$ OK

lua-cjson

$ vim Makefile
LUA_VERSION = 5.2    
$ make
$ sudo make install

lua rocks

luarocks会自动安装lua版本,现在版本为5.1,如果版本不一致,会导致找不到安装包,建议使用手动方式安装需要的包;

如果使用luarocks管理包,不要单独安装lua,或者必须保证版本一致为5.1

$ sudo apt-get install luarocks
$ sudo luarocks install luasocket
$ luarocks list
$ sudo luarocks install lua-cjson
$ luarocks search **

github上传代码时,每次都需要输入用户名和密码,原因是使用了https方式进行push:

$ git clone https://github.com/breezetemple/breezetemple.github.com.git

使用以下命令更换为使用ssh方式:

$ git remote -v
origin  https://github.com/breezetemple/breezetemple.github.com.git (fetch)
origin  https://github.com/breezetemple/breezetemple.github.com.git (push)
$ git remote rm origin
$ git remote add origin git@github.com:breezetemple/breezetemple.github.com.git
$ git push origin master 
Everything up-to-date

或者使用如下命令初始化:

git clone git@github.com:breezetemple/breezetemple.github.com.git

另外,需要将ssh pub key上传到github:

$ ls ~/.ssh/id_rsa.pub                
/home/yanwzh/.ssh/id_rsa.pub
$ xclip -sel clip < ~/.ssh/id_rsa.pub


A Basic Starting Point (Step1)

CMakeLists.txt文件:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

cmake_minimum_required:是判断需要cmake的最小版本号。所有程序都需要这个。

project:用于指定工程的名字。

add_executable命令用于指定程序从哪些源文件生成。此例中 程序 Tutorial则是tutorial.cxx 源文件生成。

添加版本号和配置头文件:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)

# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
        "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
        "${PROJECT_BINARY_DIR}/TutorialConfig.h"
        )

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

# add the executable
add_executable(Tutorial tutorial.cxx)

因为配置文件会被生成到二进制目录中,所以我们需要把二进制目录加入到头文件搜索路径中
(include),然后我们在源码目录中建立如下 ‘TutorialConfig.h.in’:

    // the configured options and settings for Tutorial
    #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
    #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

执行cmake时,配置文件头文件中@Tutorial_VERSION_MAJOR@ 和 @Tutorial_VERSION_MINOR@的值将会被
CMakeLists.txt文件中指定的值所替换。然后我们可以在源文件tutorial.cxx引用这个所定义的宏:

fprintf(stdout,"%s Version %d.%d\n",
    argv[0],
    Tutorial_VERSION_MAJOR,
    Tutorial_VERSION_MINOR);
fprintf(stdout,"Usage: %s number\n",argv[0]);

Adding a Library (Step 2)

添加一个库到工程中:

add_library(MathFunctions mysqrt.cxx)

将mysqrt.cxx源文件打包为库文件MathFunctions,
使用add_subdirectory命令指示这个目录需要进行编译。
用include_directories命令添加MathFunctions目录,
是为了能让编译程序找到 mysqrt.h 头文件。
然后用target_link_libraries命令把生成的库链接到执行文件中:

#增加头文件搜索目录
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
#增加子目录
add_subdirectory (MathFunctions) 
# 生成执行文件
add_executable (Tutorial tutorial.cxx)
#链接库
target_link_libraries (Tutorial MathFunctions)

Installing and Testing (Step 3)

在工程中增加一个安装规则和测试支持。我们需要把库安装到bin目录下,把头文件安装到include下:

install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)

对于这个向导程序,需要在顶层CMakeList.txt文件中增加下面行,安装执行文件和头文件。

# 安装程序 Tutorial到安装目录的bin子目录中
install (TARGETS Tutorial DESTINATION bin)
#安装头文件到安装目录中的include目录中
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h" DESTINATION include)

CMake用变量CMAKE_INSTALL_PREFIX决定安装的根目录。

常用cmake命令

检查CMake版本

cmake版本至少为2.8

cmake_minimum_required(VERSION 2.8)

定义工程名称

工程名为helloworld

project(helloworld)

查找源文件

查找当前目录下所有的源文件并保存到SRC_LIST变量里

aux_source_directory(. SRC_LIST)

查找src目录下所有以cmake开头的文件并保存到CMAKE_FILES变量里

file(GLOB CMAKE_FILES "src/cmake*")

file命令同时支持目录递归查找

file(GLOB_RECURSE CMAKE_FILES "src/cmake*")

按照官方文档的说法,不建议使用file的GLOB指令来收集工程的源文件,原文解释如下:

We do not recommend using GLOB to collect a list of source files from your source tree.
If no CMakeLists.txt file changes when a source is added or removed
then the generated build system cannot know when to ask CMake to regenerate.
大意就是,GLOB收集的源文件增加或删除,而CMakeLists.txt没有发生修改时,CMake不能识别这些文件。
其实,当CMakeLists.txt使用aux_source_directory和file glob查找工程源文件时,如果添加或删除源文件,都需要重新运行CMake。

set命令

经常配合set命令使用的CMake变量,使用set(variable value)进行设置。

  • CMAKE_VERBOSE_MAKEFILE on 输出详细的编译和链接信息
  • CMAKE_CXX_COMPILER “g++” c++编译器
  • CMAKE_CXX_FLAGS “-Wall” c++编译器参数
    • CMAKE_CXX_FLAGS_DEBUG debug版本对应的编译器参数
    • CMAKE_CXX_FLAGS_RELEASE release版本对应的编译器参数
  • EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin 可执行文件的输出目录
  • LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib 链接库的输出目录

set命令还可以设置自定义变量,比如

set(MY_GREETINGS "hello world")

包含目录和链接目录

将./include和./abc加入包含目录列表

include_directories(
        ./include
        ./abc
        )

将./lib加入编译器链接阶段的搜索目录列表

link_directories(
        ./lib
        )

添加生成目标

使用SRC_LIST源文件列表里的文件生成一个可执行文件hello

add_executable(hello ${SRC_LIST})

使用SRC_LIST源文件列表里的文件生成一个静态链接库libhello.a

add_library(hello STATIC ${SRC_LIST})

使用SRC_LIST源文件列表里的文件生成一个动态链接库libhello.so

add_library(hello SHARED ${SRC_LIST})

将若干库文件链接到生成的目标hello(libhello.a或libhello.so)

target_link_libraries(hello A B.a C.so)

需要注意的是,target_link_libraries里库文件的顺序符合gcc链接顺序的规则,
即被依赖的库放在依赖它的库的后面,比如上面的命令里,
libA.so可能依赖于libB.a和libC.so,
如果顺序有错,链接时会报错。
还有一点,B.a会告诉CMake优先使用静态链接库libB.a,
C.so会告诉CMake优先使用动态链接库libC.so,也可直接使用库文件的相对路径或绝对路径。

自定义链接选项

有时候需要自定义链接选项,比如需要单独对B.a使用–whole-archive选项,可以

target_link_libraryies(hello A -Wl,--whole-archive B.a -Wl,--no-whole-archive C.so)

自定义Makefile目标

运行下面的whatever目标make whatever,会先创建一个目录./hello,然后将当前目录的a.txt拷贝到新建的./hello目录里。

add_custom_command(
        OUTPUT ./hello/a.txt
        COMMAND mkdir -p ./hello 
        cp a.txt ./hello
        DEPENDS a.txt
        )
add_custom_target(whatever DEPENDS ./hello/a.txt)

自定义目标还可以使用add_dependencies命令加入到其他目标的依赖列表里,当执行make demo时,whatever目标会被自动调用。

add_executable(demo ${SRC_LIST})
add_dependencies(demo whatever)

其他常用命令

包含其他目录的CMakeLists.txt

include(/path/to/another/CMakeLists.txt)

if命令

if(${MY_BUILD_TYPE} MATCHES "debug")
    ...
else()
    ...
endif()

list命令

list(APPEND SRC_LIST 
        a.cpp
        b.cpp
    )

list(REMOVE_ITEM SRC_LIST
        a.cpp
    )

示例

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(demo)

# add include dir
INCLUDE_DIRECTORIES(.)
INCLUDE_DIRECTORIES(lua/src)

# add source dir
AUX_SOURCE_DIRECTORY(lua/src/           SRCS)
AUX_SOURCE_DIRECTORY(lua/lua-cjson-xml/ SRCS)

ADD_LIBRARY(demo ${SRCS})

SET(CMAKE_BUILD_TYPE "Release")

ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -Wno-deprecated-declarations )

SET(CMAKE_EXE_LINKER_FLAGS "--static")
ADD_EXECUTABLE(test main.cpp)
TARGET_LINK_LIBRARIES(A demo jansson dl pthread curl z)

INSTALL(FILES demo.hpp http.hpp DESTINATION include)

Ref

引用文档:

  1. cmake syntax
  2. cmake tutorial
  3. CMake常用变量列表
  4. CMake文档列表
  5. cmake modules
  6. CMake 使用总结
  7. CMake 用法导览


##Commands

头文件路径,相当于编译器参数 -Idir1 -Idir2

INCLUDE_DIRECTORIES( "dir1" "dir2" ... )

库文件路径。注意:
由于历史原因,相对路径会原样传递给链接器。
尽量使用FIND_LIBRARY而避免使用这个。

LINK_DIRECTORIES("dir1" "dir2")

收集目录中的文件名并赋值给变量

AUX_SOURCE_DIRECTORY( “sourcedir” variable)

可执行程序目标

ADD_EXECUTABLE

库目标

ADD_LIBRARY

自定义目标

ADD_CUSTOM_TARGET

目标target1依赖于t2 t3

ADD_DEPENDENCIES( target1 t2 t3 )

本意是供设置 -D… /D… 等编译预处理需要的宏定义参数,对比 REMOVE_DEFINITIONS()

ADD_DEFINITIONS( "-Wall -ansi")

设置单个目标需要链接的库

TARGET_LINK_LIBRARIES( target-name lib1 lib2 ...)

设置所有目标需要链接的库

LINK_LIBRARIES( lib1 lib2 ...)

设置目标的属性 OUTPUT_NAME, VERSION, ….

SET_TARGET_PROPERTIES( ... )

打印输出信息

MESSAGE(...)

DESTINATION 相对于 ${CMAKE_INSTALL_PREFIX}

INSTALL( FILES “f1” “f2”DESTINATION . )

变量赋值

SET( VAR value [CACHE TYPE DOCSTRING [FORCE]])

列表操作

LIST( APPEND|INSERT|LENGTH|GET| REMOVE_ITEM|REMOVE_AT|SORT ...)

字符串操作

STRING( TOUPPER|TOLOWER|LENGTH| SUBSTRING|REPLACE|REGEX ...)

转换空格分隔的字符串到列表

SEPARATE_ARGUMENTS( VAR )

文件操作

FILE( WRITE|READ|APPEND|GLOB| GLOB_RECURSE|REMOVE|MAKE_DIRECTORY ...)

注意 CMAKE_INCLUDE_PATH

FIND_FILE

注意 CMAKE_INCLUDE_PATH

FIND_PATH

注意 CMAKE_LIBRARY_PATH

FIND_LIBRARY
FIND_PROGRAM

注意 CMAKE_MODULE_PATH

FIND_PACKAGE

执行外部程序

EXEC_PROGRAM( bin [work_dir] ARGS <..> [OUTPUT_VARIABLE var] [RETURN_VALUE var] )

设置选项

OPTION( OPTION_VAR “description” [initial value] )

##Ref

引用文档:

  1. cmake syntax
  2. CMake常用变量列表
  3. cmake command

CMake意为cross-platform make,可用于管理c/c++工程。
CMake解析配置文件CMakeLists.txt生成Makefile,相比直接用Makefile管理工程,CMake更灵活和简单。

结构

Cmake的输入是源码目录下的 ‘CMakeLists.txt’ 文件,或者后缀为’.cmake’。
这个文件可以用 ‘include’ 或者 ‘add_subdirectory’ 命令增加入其它的输入文件。组织结构有以下几部分组成:

  • Directories (CMakeLists.txt)
  • Scripts (<script>.cmake)
  • Modules (<module>.cmake)

Directories

顶层目录下的 ‘CMakeLists.txt’ 文件可以使用命令 ‘add_subdirectory’ 来添加子目录,
此子目录下必须存在 ‘CMakeLists.txt’ 文件

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

Scripts

脚本模式可以使用以下命令运行给定的CMake源文件中的命令,但是不会构建系统。

cmake [<options>] (<path-to-source> | <path-to-existing-build>)
cmake [(-D<var>=<value>)...] -P <cmake-script-file>
cmake --build <dir> [<options>] [-- <build-tool-options>...]
cmake -E <command> [<options>]
cmake --find-package <options>...

Modeles

使用命令 ‘include’ 来导入 ‘.cmake’ 源文件

include(<file|module> [OPTIONAL] [RESULT_VARIABLE <VAR>] [NO_POLICY_SCOPE])

语法

编码

使用7-bit ASCII进行编码,新行可使用 ‘\n’ 或 ‘\r\n’,将被转换为 ‘\n’ 做为输入文件读取

源文件

CMakeList.txt文件是由注释、命令和空白字符组成。

命令调用

命令是由:命令名、(、空格分隔的参数、)组成。
‘CMakeLists.txt’ 文件中命令调用大小写不敏感的。 例如:

command (args….)

上面的command可以是一个命令名;或者是一个宏;也可以是一个函数名。

命令参数

args是以空格分隔的参数例表(如果参数中包含空格,则要加双引号)

除了用于分隔参数的空白字符(空格、换行号、tabs)都是被忽略不计的。任何包含在双引号中的字符都做为一个参数。

三种参数类型:括号、引号和普通

Bracket Argument:

message([=[
This is the first line in a bracket argument with bracket length 1.
No \-escape sequences or ${variable} references are evaluated.
This is always one argument even though it contains a ; character.
The text does not end on a closing bracket of length 0 like ]].
It does end in a closing bracket of length 1.
]=])

Quoted Argument:

message("This is a quoted argument containing multiple lines.
This is always one argument even though it contains a ; character.
Both \\-escape sequences and ${variable} references are evaluated.
The text does not end on an escaped double-quote like \".
It does end in an unescaped double quote.
")

可以在行尾添加 ‘' 进行行连接,例如:

message("\
This is the first line of a quoted argument. \
In fact it is the only line but since it is long \
the source code uses line continuation.\
")

Unquoted Argument:

foreach(arg
        NoSpace
        Escaped\ Space
        This;Divides;Into;Five;Arguments
        Escaped\;Semicolon
    )
    message("${arg}")
endforeach()

前两种都是提供一个参数,而且不会进行转义,最后一个需要注意转义!

转义

‘'后跟的部分字符进行转义:

escape_sequence  ::=  escape_identity | escape_encoded | escape_semicolon
escape_identity  ::=  '\(' | '\)' | '\#' | '\"' | '\ ' |
                    '\\' | '\$' | '\@' | '\^'
escape_encoded   ::=  '\t' | '\r' | '\n'
escape_semicolon ::=  '\;'

‘(‘,’)’,’#’,’”‘,’ ‘,’',’$’,‘@’,’^’ 进行简单转义;’\t’ : tab ‘\r’ : return ‘\n’ : newline;’;‘ 用于参数中不表示隔离参数

变量引用

变量引用的形式为 ‘${variable_name}’,替换为变量的值,或空字符串(变量未赋值)。

变量引用可以嵌套,从内到外展开,例如 ‘${outer_${inner_variable}_variable}’

环境变量引用形式为 ‘$ENV{VAR}’

注释

注释由一个不是 (括号中的参数、引用参数或’'转义)一部分的’#’字符开始,有行注释和括号注释:

括号注释:

#[[This is a bracket comment.
It runs until the close bracket.]]
message("First Argument\n" #[[Bracket Comment]] "Second Argument")

行注释:

# This is a line comment.
message("First Argument\n" # This is a line comment :)
        "Second Argument") # This is a line comment.

控制结构

Conditional Blocks

# some_command will be called if the variable's value is not:
# empty, 0, N, NO, OFF, FALSE, NOTFOUND, or -NOTFOUND. 
if(var)
    some_command(...)
elseif()
    some_command(...)
else()
    some_command(...)
endif() 

Loops

set(VAR a b c) 

# loop over a, b,c with the variable f 
foreach(f ${VAR}) 
    message(${f}) 
endforeach(f)

while(condition)
    command(args ...)
    command(args ...)
endwhile(condition)

Command Definitions

macro()/endmacro() 和 function()/endfunction() 进行宏和函数的定义:

# define a macro hello 
macro(hello MESSAGE)
    message(${MESSAGE})
endmacro(hello) 

# call the macro with the string "hello world" 
hello("hello world") 

# define a function hello 
function(hello MESSAGE)
    message(${MESSAGE}) 
endfunction(hello)

Variables

变量命名大小写敏感,set()/unset()对变量进行赋值操作:

set (VAR " hello world ")   #赋值
unset (VAR)                 #取消赋值

CMake支持简单的变量:字符串或字符串列表。用${VAR} 语法得到变量的引用。

可以用一个set命令把一个字符串列表设置为一个变量,然后把这个变量传递给需要传递多参数的函数。例如:

set(Foo a b c)
command(${Foo})

上面两行等效

command(a b c)

如果你想传把一个字符串列表做为一个单独的参数传递给函数,用双引号包含它。例如:

Command(“${Foo}”)
#等效于
command(“a b c”)

环境变量:

用$ENV{VAR}得到环境变量的引用

设置环境变量:

Set(ENV{VAR} /home)

Lists

字符串(string)和字符串列表(lists)

CMake的基本数据类型是字符串(string)。
CMake也支持由字符串组成的字符串列表。字符串列表可以由;或空格分隔的组成。例如:下面设置变量var是等效的。

set(var a;b;c)
set(var a b c)

字符串列表可以用 foreach命令叠代(iterated)或list命令操作。

正则表达式

一些CMake命令(如if和 string),能使用正则表达式或使用正则表达式作为参数。
一个简单的形式,一个正则表达式用于在一个序列的字符串中精确查找一个字符。
然而在大多时候,一个精确查找是不知道的或者只是匹配最前或者最后字符。
所以这里用正则表达式进行不同的转换。
Cmake标准是可被描述的。这个描述是基于开源的正则表达式类(Texas Instruments)。

  • ^ 匹配一行或一字符串开头
  • $匹配一行或一字符串结尾
  • .匹配单一字符或一个新行
  • [ ]匹配括号中的任一字符
  • [^ ] 匹配不在括号内的任一字符
  • [-] 匹配指定范围内的字符
  • * 匹配0次或多次
  • + 匹配一次或多次
  • ? 匹配0次或一次
  • ()保存匹配的表达式并用随后的替换它

Ref

  1. cmake syntax
  2. cmake tutorial
  3. CMake常用变量列表
  4. CMake文档列表

http 使用curl发起https请求

设定为不验证证书和host。

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);

设定一个正确的证书。

curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,true); ;
curl_setopt($ch,CURLOPT_CAINFO,dirname(__FILE__).'/cacert.pem');
  1. libcurl-api
  2. libcurl-tutorial
  3. libcurl教程
  4. LibCurl编程
  5. libcurl调试程序
  6. 使用libcurl提交POST请求
  7. 使用libcurl C接口之Easy接口
  8. curl跟随重定向页面
  9. Using cURL in PHP to access HTTPS (SSL/TLS) protected sites
  10. libcurl 接口调用方式
  11. CURL并发–multi perform模式
  12. curl详细解说

Set Break

断点命令(break)缩写为b,格式如下:

  • break function : b main
  • break line : b 256
  • break file:line : b app_main.c:256
  • break file:function : b app_main.c:main
  • break +offset : b +3
  • break -offset : b -3
  • break *addr : b *0x0811efd4

Set Catch

catch point作用是让程序在发生某种事件的时候停止运行。设置events如下:

  • catch assert – Catch failed Ada assertions
  • catch catch – Catch an exception
  • catch exception – Catch Ada exceptions
  • catch exec – Catch calls to exec
  • catch fork – Catch calls to fork
  • catch syscall – Catch system calls by their names and/or numbers
  • catch throw – Catch an exception
  • catch vfork – Catch calls to vfork

Show Backtrace

backtrace命令可以断点处执行显示栈帧,简写为bt,别名有where & info stack,格式如下:

  • bt : show all
  • bt N : show start N
  • bt -N : show end N
  • bt full : show all & locals

查看桢信息:

  • frame n: 查看第n桢的信息, frame可以用f缩写
  • frame addr: 查看pc地址为addr的桢的相关信息
  • up n: 查看当前桢上面第n桢的信息
  • down n: 查看当前桢下面第n桢的信息

查看更加详细的信息:

  • info frame
  • info frame n
  • info frame addr

示例如下:

(cskygdb) info frame 1
Stack frame at 0x32aa9518:
pc = 0x4ce00 in app_ott_vod_detail_create_exec (ott/app_ott_vod_detail.c:606); saved pc 0x49fee
called by frame at 0x32aa9538, caller of frame at 0x32aa9500
source language c.
Arglist at 0x32aa9500, args: nAlbumNo=0, pName=0xa916d0 "危险关系", pPic=0x96e628 "/tmp/6C880089F646D38.jpg", 
        pVid=0xa916a8 "706afed05f"
        Locals at 0x32aa9500, Previous frame's sp is 0x32aa9518
        Saved registers:
        r8 at 0x32aa9514, r15 at 0x32aa9510, pc at 0x32aa9510

上图中显示的信息有:

  • 当前桢的地址: 0x32aa9518
  • 当前桢PC: pc = 0x4ce00
  • 当前桢函数: app_ott_vod_detail_create_exec (ott/app_ott_vod_detail.c:606)
  • caller桢的PC: saved pc 0x49fee
  • caller桢的地址: called by frame at 0x32aa9538
  • callee桢的地址: caller of frame at 0x32aa9500
  • 源代码所用的程序的语言(c/c++): source language c
  • 当前桢的参数的地址及值: Arglist at at 0x32aa9500, args: nAlbumNo=0, pName=0xa916d0 “危险关系”, pPic=0x96e628 “/tmp/6C880089F646D38.jpg” …
  • 当前相中局部变量的地址:Locals at 0x32aa9500, Previous frame’s sp is 0x32aa9518
  • . 当前桢中存储的寄存器: Saved registers: r8 at 0x32aa9514, r15 at 0x32aa9510, pc at 0x32aa9510

info args:查看当前桢中的参数

(cskygdb) info args
window_name = 0x5650a8 "wnd_ott_vod_detail"

info locals:查看当前桢中的局部变量

(cskygdb) info locals 
widget = 0x0
window = 0x0

info catch:查看当前桢中的异常处理器(exception handlers)

(cskygdb) info catch 
print_frame_label_vars disabled.

示例2:

(cskygdb) info frame 3
Stack frame at 0x32aa9550:
pc = 0x4a40c in app_ott_vod_keypress (ott/app_ott_vod.c:489); saved pc 0x11cd86
called by frame at 0x32aa9568, caller of frame at 0x32aa9538
source language c.
Arglist at 0x32aa9538, args: widgetname=0x6c44d0 "wnd_ott_vod", usrdata=0x32aa963c
Locals at 0x32aa9538, Previous frame's sp is 0x32aa9550
Saved registers:
r8 at 0x32aa954c, r15 at 0x32aa9548, pc at 0x32aa9548
(cskygdb) frame 3
#3  0x0004a40c in app_ott_vod_keypress (widgetname=0x6c44d0 "wnd_ott_vod", usrdata=0x32aa963c) at ott/app_ott_vod.c:489
489             app_ott_page_ok_keypress(find_virtualkey(event->key.scancode));
(cskygdb) info args 
widgetname = 0x6c44d0 "wnd_ott_vod"
usrdata = 0x32aa963c
(cskygdb) info locals 
event = 0x32aa963c
__FUNCTION__ = "app_ott_vod_keypress"
(cskygdb) info catch 
print_frame_label_vars disabled.

Show Variable & Registers

print命令可以显示变量,简写为p

info registers可以显示寄存器,简写为info reg。

在寄存器名之前添加$,可以显示寄存器内容:

p &pc

支持的格式如下:

  • x : 十六进制
  • d : 十进制
  • u : 无符号十进制
  • o : 八进制
  • t : 二进制
  • a : 地址
  • c : ASCII
  • f : 浮点
  • s : 字符串

x命令可以显示内存内容,支持以上格式之外还支持:

i : 显示为汇编语言

使用x命令格式为 x/NFU ADDR,参数意义如下:

  • ADDR : 希望显示的地址
  • N : 重复次数
  • F : 支持格式(x,d,u,o,t,a,c,f,s,i)
  • U : 单位,下所示
    • b : 字节
    • h : 半字(2字节)
    • w : 字(4字节,默认值)
    • g : 双字(8字节)

反汇编存在命令:disassemble

show value命令显示历史中的最后10个值

attach到进程

条件断点

使用格式如下:

break 断点 if 条件

例如:

b main if mode==1

使用condition可以给已存在的断点添加或删除触发条件:

condition Bn
condition Bn 条件

反复执行

在指定的断点、监控点或捕获点忽略指定次数,格式如下:

ignore Bn N

类似命令如下:

  • continue N
  • step N
  • stepi N
  • next N
  • nexti N
  • finish
  • until
  • until ADDR

断点命令

指定程序在某个breakpoint处停下来后执行一串命令,格式:

commands [bnum]
… command-list …
end

指定程序在第bnum个breakpoint处停下来后,执行由command-list指定的命令串,
如果没有指定bnum,则对最后一个breakpoint生效,例子:

break foo if x>0
commands
silent
printf “x is %d\n”,x
continue
end

上面的例子含义:当x>0时,在foo函数处停下来,然后打印出x的值,然后继续运行程序

  • MPI:英文全称是Message Passing Interface,这个就很明了了,信息传递接口,是独立于语言的通信协议(标准),是一个库。
    MPI的实现有MPICH,MPI-1,MPI-2等等
  • openMPI:英文全称是open Message Passing Interface。openMPI是MPI的一种实现,一种库项目
  • OpenMP:英文全称是Open Multiprocessing,一种应用程序界面(API,即Application Program Interface),
    是一种并行的实现和方法,也可以认为是共享存储结构上的一种编程模型,
    可用于共享内存并行系统的多线程程序设计的一套指导性注释(Compiler Directive)。

在当前的并行机子中,openMP和openMPI都是需要的(从上面的各自概念可以看出),
openMP用于本地的并行计算(共享内存内存架构),
支持目前所有平台上的c,fortran等的共享内存式并行计算,
它相当于是给出了一个让并行编程更加容易实现的模型,
而openMPI则是用于机器之间的通信(分布式内存架构)。

OpenMP

简单。不用大改源程序,直接加#pragma就行了。
只适用于共享式内存。比如一台电脑,4核cpu共享16G内存,可以用Openmp启动4核同时计算。

OpenMP:线程级(并行粒度);共享存储;隐式(数据分配方式);可扩展性差;

MPI

稍复杂。需要重新设计程序,学习成本稍高。

MPI:进程级;分布式存储;显式;可扩展性好。

OpenMP采用共享存储,意味着它只适应于SMP,DSM机器,不适合于集群。MPI虽适合于各种机器,但它的编程模型复杂

在并行计算领域内,主要的并行编程模型有三类模型:
数据并行、消息传递、共享变量。
其中基于消息传递的 MPI 编程模型和基于共享变量的 OpenMP 编程模型是最为流行的并行编程模型。
openmp比较简单,修改现有的大段代码也容易。
基本上openmp只要在已有程序基础上根据需要加并行语句即可。
而mpi有时甚至需要从基本设计思路上重写整个程序,调试也困难得多,涉及到局域网通信这一不确定的因素。
不过,openmp虽然简单却只能用于单机多CPU/多核并行,mpi才是用于多主机超级计算机集群的强悍工具,当然复杂。

MPI=message passing interface

在分布式内存(distributed-memory)之间实现信息通讯的一种 规范/标准/协议(standard)。
它是一个库,不是一门语言。可以被fortran,c,c++等调用。
MPI 允许静态任务调度,显示并行提供了良好的性能和移植性,用 MPI 编写的程序可直接在多核集群上运行。
在集群系统中,集群的各节点之间可以采用 MPI 编程模型进行程序设计,
每个节点都有自己的内存,可以对本地的指令和数据直接进行访问,
各节点之间通过互联网络进行消息传递,这样设计具有很好的可移植性,
完备的异步通信功能,较强的可扩展性等优点。
MPI 模型存在一些不足,包括:程序的分解、开发和调试相对困难,
而且通常要求对代码做大量的改动;
通信会造成很大的开销,为了最小化延迟,通常需要大的代码粒度;
细粒度的并行会引发大量的通信;动态负载平衡困难;并行化改进需要大量地修改原有的串行代码,调试难度比较大

MPICH和OpenMPI

它们都是采用MPI标准,在并行计算中,实现节点间通信的开源软件。各自有各自的函数,指令和库。

Reference:

They are two implementations of the MPI standard.
In the late 90s and early 2000s, there were many different MPI implementations,
and the implementors started to realize they were all re-inventing the wheel;
there was something of a consolidation.
The LAM/MPI team joined with the LA/MPI, FT-MPI, and eventually PACX-MPI teams to develop OpenMPI.
LAM MPI stopped being developed in 2007.
The code base for OpenMPI was completely new,
but it brought in ideas and techniques from all the different teams.

Currently, the two major open-source MPI implementation code-bases are OpenMPI and MPICH2.

而MPICH2是MPICH的一个版本。

有的计算机厂商,也会针对旗下机型特点,自主开发基于MPICH的MPI软件,从而使机器的并行计算效率得以提高。

OpenMP

在节点内(多核 SMP)执行的基于共享内存的编程模型。

OpenMP是针对单主机上多核/多CPU并行计算而设计的工具,
换句话说,OpenMP更适合单台计算机共享内存结构上的并行计算。
由于使用线程间共享内存的方式协调并行计算,
它在多核/多CPU结构上的效率很高、内存开销小、编程语句简洁直观,
因此编程容易、编译器实现也容易(现在最新版的C、C++、Fortran编译器基本上都内置OpenMP支持)。
不过OpenMP最大的缺点是只能在单台主机上工作,不能用于多台主机间的并行计算

MPI 负责节点之间的通信(集群系统中的单个计算机通常称为节点)
OpenMP负责节点中多核CPU的计算
CUDA负责节点中gpu上的计算


专注于用户空间的系统级编程–即内核之上的所有内容。

对I/O操作的分类主要基于时间和占有两个角度进行划分:
从时间角度可分为同步操作和异步操作;
从资源占有角度可分为阻塞操作和非阻塞操作。
从而UNXI可以构造出4种不同的I/O操作模型:

  • 同步阻塞I/O
  • 同步非阻塞I/O
  • 异步阻塞I/O
  • 异步非阻塞I/O,AIO

AIO是将I/O操作与应用程序的计算操作重叠执行的一种模型,即实现I/O操作与计算操作的有效分离。
AIO背后的基本思想是允许进程发起很多I/O操作,而不用阻塞或等待任何操作完成。
异步非阻塞I/O模型是一种处理与I/O重叠进行的模型。
读请求会立即返回,说明read请求已经成功发起了。
在后台完成读操作时,应用程序然后会执行其他处理操作。
当read的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。

在一个进程中为了执行多个I/O请求而对计算操作和I/O处理进行重叠处理的能力利用了处理速度与 I/O 速度之间的差异。
当一个或多个I/O请求挂起时,CPU可以执行其他任务;
或者更为常见的是,在发起其他I/O的同时对已经完成的I/O进行操作。

AIO在当前Linux下主要有两种AIO实现技术:一种是glibc库函数实现,另一种是Linux内核实现,并由libaio库进行封装

glibc库中的AIO实现,需要在编译时-lrt

#include <aio.h>

//请求异步读操作
int aio_read(struct aiocb *aiocbp);
//检查异步请求的状态
int aio_error(const struct aiocb *aiocbp);
//获得完成的异步请求的返回状态
ssize_t aio_return(struct aiocb *aiocbp);
//请求异步写操作
int aio_write(struct aiocb *aiocbp);
//挂起调用进程,直到一个或多个异步请求已经完成(或失败)
int aio_suspend(const struct aiocb * const aiocb_list[],
                int nitems, const struct timespec *timeout);
//取消异步 I/O 请求
int aio_cancel(int fd, struct aiocb *aiocbp);
//发起一系列 I/O 操作
int lio_listio(int mode, struct aiocb *const aiocb_list[],
                int nitems, struct sigevent *sevp);

在异步非阻塞I/O中,我们可以同时发起多个传输操作。
这需要每个传输操作都有惟一的上下文,
这样我们才能在它们完成时区分到底是哪个传输操作完成了。
在 AIO 中,这是一个 aiocb(AIO I/O Control Block)结构。
这个结构包含了有关传输的所有信息,包括为数据准备的用户缓冲区。
在产生 I/O (称为完成)通知时,aiocb 结构就被用来惟一标识所完成的 I/O 操作。
定义如下:

#include <aiocb.h>

struct aiocb {
    /* The order of these fields is implementation-dependent */

    int             aio_fildes;     /* File descriptor */
    off_t           aio_offset;     /* File offset */
    volatile void  *aio_buf;        /* Location of buffer */
    size_t          aio_nbytes;     /* Length of transfer */
    int             aio_reqprio;    /* Request priority */
    struct sigevent aio_sigevent;   /* Notification method */
    int             aio_lio_opcode; /* Operation to be performed;
                                    lio_listio() only */

    /* Various implementation-internal fields not shown */
};

sigevent 结构告诉 AIO 在 I/O 操作完成时应该执行什么操作。
我们将在 AIO 的展示中对这个结构进行探索。
现在我们将展示各个 AIO 的 API 函数是如何工作的,以及我们应该如何使用它们。

AIO通知:通过信号实现异步通知和通过函数回调来实现

  1. 网络编程–IO模型示例
  2. 使用异步 I/O 大大提高应用程序的性能
  3. Linux kernel AIO这个奇葩
  4. Linux下的服务器开发模型 Epoll和AIO的讨论
  5. Epoll vs. IOCP
  6. 异步,epoll和aio

转载:

异步IO就是把IO提交给系统,让系统替你做,做完了再用某种方式通知你(通过信号,或者其他异步方式
通知,这时候,操作系统已经帮你完成IO操作,具体来说就是你那个作为传入参数的的buffer的指针所指向
的空间已经读到了数据或者你的buffer的数据已经写出去了);

非阻塞IO就是你要通过某种方式不定时地向系统询问你是否可以开始做某个IO( 轮询啊 ),当可以开始
后,是要自己来完成IO(也就是说还要自己调用一次read来填充buffer或者write来不buffer的数据写出去);

epoll和aio(这里的aio是指linux 2.6内核后提供的aio api)区别:

aio是异步非阻塞的。其实是aio是用线程池实现了异步IO。

epoll在这方面的定义上有点复杂,首先epoll的fd集里面每一个fd都是非阻塞的,但是epoll(还
select poll )在调用时阻塞等待fd可用,然后epoll只是一个异步通知机制,只是在fd可用时通知你,并
没有做任何IO操作,所以不是传统的异步。

工程中遇到问题,修改配置文件之后发现编译不过,部分符号未定义,检查Makefile如下:

LDFLAGS += -lui -lenca -lxml -lgxbox -lstdc++ -ljansson -lcurl
...
LDFLAGS += -L$(GXSRC_PATH)/lib/$(THIRD_LIB)/ -lkolatv -lpcre

其中kolatv库需要调用jansson库gxbox库也需要调用jansson库

在修改配置文件之前,开启了gxbox的支持,在编译中需要链接gxbox库,从而jansson库可以链接成功。

在修改配置文件之后,关闭了gxbox的支持,在编译中不会编译gxbox代码,gxbox库链接失败,
从而jansson库链接失败,在载入kolatv库时不存在jansson库的符号表。

GCC:

-l library

Search the library named library when linking.
(The second alternative with the library as a separate argument is only for POSIX compliance and is not recommended.)

It makes a difference where in the command you write this option;
the linker searches and processes libraries and object files in the order they are specified.
Thus, foo.o -lz bar.o' searches libraryz’ after file foo.o but before bar.o.
If bar.o refers to functions in `z’, those functions may not be loaded.

因此,在链接过程中,链接的库,一定要在使用该库的目标被连接之后,基本的库要放在最后。

修改如下:

LDFLAGS += -lui -lenca -lxml -lkolatv -lgxbox -lstdc++ -ljansson -lcurl

专注于用户空间的系统级编程–即内核之上的所有内容。

计算机系统内存分为系统空间和用户空间。通常,用户是不能够直接访问系统空间、调用系统函数的,
但用户可以通过系统调用进入内核空间,获取内核服务。

用户进程对系统函数的成功调用,将进行相应的用户空间到系统空间的转换。
进入内核后,不同的系统调用会找到各自对应的内核函数入口地址,由内核函数提供服务

以调用系统调用fork()来分析如下:

fork()系统调用

从图中我们可以看出,调用fork()实际上是调用了中断0x80,通过事先初始化好的IDT,
程序转移到了_system_call,最终通过一个函数指针数组sys_call_table[]转化成了调用sys_fork()。

系统调用起到了连接用户进程和操作系统的桥梁作用,由于桥两端处于不同的态,因此需要使用中断来过渡。

动态链接库在使用时,分为“隐式调用”和“显式调用”两种:

1.如果是隐式调用,则与静态库的使用方法差不多,注意需要包含导出函数的头文件,即mylib.h:

#include ...
#include "mylib.h"

int main()
{
    Print();
}

编译方法:

gcc -o main main.c -L./ mylib.so

注意要加上动态链接库的搜索路径,否则编译器只会到系统路径中去寻找。

2.显式调用的方式,不必包含mylib.h,但是需要增加几个系统调用:

#include ...
#include  <dlfcn.h>// 显式加载需要用到的头文件

int main()
{
    void *pdlHandle = dlopen("./mylib.so", RTLD_LAZY); // RTLD_LAZY 延迟加载
    char *pszErr = dlerror();
    if( !pdlHandle || pszErr )
    {
        printf("Load mylib failed!n")
            return 1;
    }

    void (*Print)() = dlsym(pdlHandle, "Print"); // 定位动态链接库中的函数
    if( !Print )
    {
        pszErr = dlerror();
        printf("Find symbol failed!%sn", pszErr);
        dlclose(pdlHandle);
        return 1;
    }

    Print(); // 调用动态链接库中的函数

    dlclose(pdlHandle); // 系统动态链接库引用数减1

    return 0;
}

可以看到,显式调用的代码看上去要复杂很多,但是却比隐式调用要灵活,
不必在编译时就确定要加载哪个动态链接库,可以在运行时再确定,甚至重新加载。

看一下显式调用的编译方式:

gcc -ldl -o main main.c

注意要添加-ldl选项,以使用显式调用相关的函数调用

手动加载动态链接库dlopen dlsym dlcolose

打开动态链接库

#include <dlfcn.h>
void *dlopen(const char *filename, int flag);

该函数返回操作句柄,如:

void *pHandle = dlopen(strSoFilePath, RTLD_LAZY);

取动态对象地址

#include <dlfcn.h>
void *dlsym(void *pHandle, char *symbol);

dlsym根据动态链接库操作句柄(pHandle)与符号(symbol),返回符号对应的地址。
使用这个函数不但可以获取函数地址,也可以获取变量地址。比如,假设在so中
定义了一个void mytest()函数,那在使用so时先声明一个函数指针:
void (*pMytest)(),然后使用dlsym函数将函数指针pMytest指向mytest函数,
pMytest = (void (*)())dlsym(pHandle, "mytest");

关闭动态链接库

#include <dlfcn.h>
int dlclose(void *handle);

该函数将该.so的引用计数减一,当引用计数为0时,将它从系统中卸载。

动态库错误函数

#include <dlfcn.h>
const char *dlerror(void);

当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时 表示没有错误信息。

在取到函数执行地址后,就可以在动态库的使用程序里根据动态库提供的函数接口调用动态库里的函数。
在编写调用动态库的程序的Makefile文件时,需要加入编译选-ldl。

void *dlsym(void *handle, char *symbol) 的参数可以看出,
该函数只传两个参数:一个指向so的handle和一个函数的symbol,
所以so里面的函数应该不允许重载,否则根据一个symbol不能确定指向那个函数。


网络中的进程通常由一个三元组来标识,包括协议、本地地址和端口

主机字节次序与网络字节次序

对于一个4字节的整数:0x11223344,占用四个字节存储单元,在主机中有两种主机字节次序


Bytes3 Bytes2 Bytes1 Bytes0
Little Endian : 11 22 33 44
Big Endian : 44 33 22 11

x86采用小端方式,PowerPC和Sparc采用大端方式。
为了保证互连性,要求所有的数据按照统一字节顺序传输,于是出现了网络字节次序,规定与x86的主机字节次序相反。

在UNIX中提供如下API用于转换:

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

进程经网络通信时,有两种方法可以选择:

  • 面向连接方式,就是通信双方在通信时,要事先建立一条通信线路,
    其过程有建立连接、使用连接和释放连接三个过程。
    TCP协议就是一种面向连接服务的协议,电话系统是一个面向连接的模式。

  • 无连接方式,就是通信双方不需要事先建立一条通信线路,
    而是把每个带有目的地址的包(报文分组)送到线路上,由系统选定路线进行传输。
    IP、UDP协议就是一种无连接协议,邮政系统是一个无连接的模式。

    #include <sys/types.h> /* See NOTES */
    #include <sys/socket.h>

    int socket(int domain, int type, int protocol);

Socket类型:

  • 流套接字,SOCK_STREAM,用于提供面向连接、可靠的数据传输服务
  • 数据包套接字,SOCK_DGRAM,用于提供无连接服务,不能保证数据的可靠性
  • 原始套接字,SOCK_RAW

Raw socket与标准套接字(SOCK_STREAM、SOCK_DGRAM)的区别:

raw socket可以读写内核没有处理的IP数据包,而流套接字只能读取TCP数据,数据包套接字只能读取UDP数据。
使用raw socket可以避开TCP/IP处理机制,被传送的数据包可以被直接传送给需要它的应用程序。

raw socket植根于操作系统网络核心(Network Core),而标准socket则“悬浮”于TCP和UDP协议的外围。

套接字寻址API:

include <netdb.h>

//获取主机名和IP地址
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
//获取服务与端口地址
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);

套接字选项API:

#include <sys/types.h>
#include <sys/socket.h>

//获取套接字选项的设置情况
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
//设置套接字选项
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

套接字API:

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t addrlen);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
int shutdown(int sockfd, int how);

bind函数使用通用地址结构struct sockaddr来设置套接字地址,
而在网络通信时必须使用sockaddr_in来构造一个套接字地址,
所以,调用bind时,需要将sockaddr_in结构类型强制转换为struct sockaddr结构类型。

listen函数仅被TCP服务器调用。当函数socket创建一个套接字时,它被假设为一个主动套接字,
listen将此套接字转换为被动套接字,指示内核应接受向此套接字的连接请求,
同时维护两个队列(未完成连接队列、已完成连接队列)排队连接请求。

服务器套接字进入侦听状态后,必须通过函数accept接收客户进程提交的连接请求,从而完成一个套接字的完整连接。

套接字的数据收发即可以使用文件库函数 read/write ,也可以使用套接字的专用收发函数 recv/send 。

TCP是面向连接的通信协议,采用客户机-服务器模式。套接字的全部工作流程如下所述:

  1. 服务器启动进程,调用Socket创建一个基于TCP协议的流套接字描述符。
  2. 其次,服务进程调用bind命名套接字,将套接字描述符绑定到本地地址和本地端口上,至此Socket的半相关描述—{协议,本地地址,本地端口}—完成。
  3. 再次,服务器端调用listen,开始侦听客户端的Socket连接请求。
  4. 接下来,客户端创建套接字描述符,并且调用connect向服务端提交连接请求。服务器端接收到客户端连接请求后,调用accept接受,并创建一个新的套接字描述符与客户端建立连接,然后原套接字描述符继续侦听客户端的连接请求。
  5. 客户端与服务器端的新套接字进行数据传送,调用write或send向对方发送数据,调用read或recv接收数据。
  6. 在数据交流完毕后,双方调用close或者shutdown半闭套接字。

基于TCP(面向连接)的socket编程TCP是面向连接的,可靠的传输协议

服务器端程序:

  1. 创建套接字(socket)
  2. 将套接字绑定到一个本机地址和端口上(bind)
  3. 将套接字设为监听模式,准备接收客户请求(listen)
  4. 等待客户请求到来,当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)
  5. 用返回的套接字和客户端进行通信(send/recv)
  6. 返回,等待另一个用户的请求
  7. 关闭套接字

客户端程序:

  1. 创建套接字(socket)
  2. 向服务器端发送连接请求(connect)
  3. 和服务器进行通信(recv/send)
  4. 关闭套接字

基于UDP(面向无连接)的socket编程UDP是无连接的,不可靠的传输协议

服务器端(接收端)程序:

  1. 创建套接字(socket)
  2. 将套接字绑定到一个本地地址和端口上(bind)
  3. 等待接收数据(recvfrom)
  4. 关闭套接字

客户端(发送端)程序:

  1. 创建套接字(socket)
  2. 发送数据(sendto)
  3. 关闭套接字

  1. socket与TCP/UDP编程
  2. 基于Socket的UDP和TCP编程介绍

FAT文件系统部分资料,用于开发eCos FAT接口,<FatFs – FAT> <NTGS-3G – NTFS>


问题

可怕的问题:FATFS f_open() 返回可怕的 FR_NO_FILESYS

SDHC KINGSTON的4G卡 ,在电脑上已经格式化,里面有ABC.TXT文本文件。

移植好的FATFS文件系统在 万利的EK-STM32F 开发板上 对写SD卡。

disk_sta=disk_initialize(0);//disk_status        
f_mount(0, &fatfs[0]);  

上面两句都运行正常! 到

/* Open source file on the drive 1 */
res = f_open(&fsrc, "0:/ABC.TXT", FA_CREATE_NEW | FA_READ);

就返回可怕的FR_NO_FILESYSTEM

从网上找来同样的问题的帖子:

SD卡sector0读出来只有最后两个字节是 55 AA,中间也有几个数据,其他的都是00,
check_fs函数返回 FR_NO_FILESYSTEM 无可用的文件系统。
网上查了一下FAT系统的资料,发现物理扇区0(MBR)并不是逻辑扇区0,
根据第一个sector读出的数据,我的物理扇区的地址应该是0x61而不是0。
后来仔细查了一下FATFS的源码,发现只支持 FDISK 和 SFD 格式的FAT系统,
于是我猜想如果用fdisk格式化,MBR的物理扇区和逻辑扇区地址就都是0了。
然后在linux下用FDISK又格式化了一把,发现情况还是一样。

我现在在裸机下移植fatfs,出现和你差不多的问题,麻烦楼主能看看

fmt = check_fs(fs, bsect = 0);                // Check sector 0 if it is a VBR 
//fmt = check_fs(fs, 243);
if (fmt == 1) {                                                // Not an FAT-VBR, the disk may be partitioned 
    // Check the partition listed in top of the partition table 
    tbl = &fs->win;        // Partition table 
    if (tbl) {                                                                        // Is the partition existing? 
        bsect = LD_DWORD(&tbl);                                        // Partition offset in LBA 
        fmt = check_fs(fs, bsect);                                        // Check the partition 
        //fmt = check_fs(fs, 0);
    }
}

第一句话fmt = check_fs(fs, bsect = 0);是去读sd卡的MBR,即第0扇区,数据正常,
且根据里面的数据经过bsect = LD_DWORD(&tbl);这句话计算正确得到启动扇区的位置(243),
然后fmt = check_fs(fs, bsect);这句话就是去读这个启动扇区内的数据,这里面的数据
非常重要,就像我们电脑的C盘,但是返回来全部是0,导致fatfs失败。
然后同一张卡在另一块板子的代码上测试正常,里面的数据也正常。
那么可以说明一点的是,既然可以读0扇区数据正常那么说明sd的底层读函数是对的,那么为什么读不出来第243扇区的数据?
同事怀疑是sd卡初始化中设置成了只能读一次,那么我一开始就读第243扇区,
也是返回全0.这就郁闷了。在另一块板子的代码上测试,一开始读243扇区的数据是正常的。
最后,我将sd卡格式化,用winhex查看其扇区内容,发现0扇区和243扇区的数据没变。

FR_NO_FILESYSTEM 问题:

提示没有在你的目标存储器上建立文件系统,试试res = f_mkfs(0,0,2048);这里的2048是2048 bytes,
指的是你的目标存储器的扇区大小 也有可能是你没有执行注册磁盘空间的操作,试试res = f_mount(0,&fs);

我在读写TF卡的时候也出现过这样的问题,我的解决方式是将卡用卡槽插到PC机上,然后格式化一下,我格式化选择的是FAT格式。

试试用F_MKFS函数格式化一下
未格式化为Fat格式,先在电脑里格式化或者用Fatfs自带的格式化函数格式化SD.记得要格式化为Fatfs支持的格式

记得要把文件系统变量放到main函数的外面,比如工作区的变量,文件指针变量等。不这样做会出现一些错误,改了就好。我移植的时候没注意,就一直停顿这里很久,原因不明。

说明你没有格式化,首先需要执行f_mkfs()函数,此步骤为建立FAT相关的一些表。只需要执行一次就可以了,以后再运行程序不需执行。

在FATFS中经常用到chk_mount这个函数,主要是对物理驱动器检测它的可用性,即磁盘现在是否可用,避免了磁盘中途掉电的情况,在其中有这么一段程序

fmt = check_fs(fs, bsect = 0);                // Check sector 0 if it is a VBR /可用的分区记录
if (fmt == 1) {                                                // Not an FAT-VBR, the disk may be partitioned /
    //结束符正确,确不是FAT系统,则读取下一个分区的数据,看是否是有效的结束符和FAT系统
    // Check the partition listed in top of the partition table /
    tbl = &fs->win[MBR_Table + LD2PT(vol) * SZ_PTE];// Partition table /下一个分区属性的16字节存储指针
    if (tbl[4]) {                                                                        // Is the partition existing? /系统ID对于FAT32为0x01,未用是为0
        bsect = LD_DWORD(&tbl[8]);                                        // Partition offset in LBA /分区偏移地址
        fmt = check_fs(fs, bsect);        //root record                                // Check the partition /这一句不懂了
    }
}        

这一次它是要检测什么的??

后来想了一想,对于我们的硬盘,大多是只有一个分区的,但是有的可能会有多个分区,而对于每一个分区而言,他都有一个MBR区,又称为伪MBR区,存储了这一个分区的引导程序,以及分区的信息,这样的话,如果第一个分区不是FAT系统,我们就查找是否又下一个分区,如果有的话,就开始对下一分区的判断……

但是为什么它只检测了两个分区呢,可能后面还有分区呢???

if (fmt == 3) return FR_DISK_ERR;
if (fmt) return FR_NO_FILESYSTEM; // No FAT volume is found /
//两次检测之后还不是FAT系统,就返回错误

我的理解是:
在一些SD1.0中,物理0扇区就是逻辑0扇区,0扇区就是DBR,没有MBR,
这种情况也就不存在分区,第一个fmt = check_fs(fs, bsect = 0);会返回0,
因为check_fs里的第三个或第四个if判断会检测到DBR里面的FAT系统标识字符串,
其中一个会返回0,fmt=0,fmt = check_fs(fs, bsect);就不会执行了

if (disk_read(fs->drive, fs->win, sect, 1) != RES_OK) // Load boot record  
    return 3; 
if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55) // Check record signature (always placed at offset 510 even if the sector size is >512)  
    return 2; 

if ((LD_DWORD(&fs->win[BS_FilSysType]) & 0xFFFFFF) == 0x544146) // Check "FAT" string  
    return 0; 
if ((LD_DWORD(&fs->win[BS_FilSysType32]) & 0xFFFFFF) == 0x544146) 
    return 0; 

    return 1; 

而在一些有MBR的sd卡中物理0扇区是MBR,mbr里有DBR的偏移信息,
执行fmt = check_fs(fs, bsect = 0);的时候,第一个和第二个if会通过,
但是mbr里面没有fat标识字符串所以第三个和第四个if不会过,返回1,fmt=1就会执行fmt = check_fs(fs, bsect);
其中bsect是从mbr里读出来的逻辑0扇区偏移量,既DBR所在扇区,这次执行就同上面读DBR一样,会再第三个或第三个if返回0.

chaN的驱动是针对sd,ata,usb,nand的,不单单是sd。我的sd2.0是没有MBR的,0扇区就是DBR。而PC硬盘ata的MBR就在0扇区。
我们重装PC的时候硬盘是可以选择NTFS和FAT等格式。
在有MBR的前提下,就有DPT,紧接MBR引导代码是DPT和“55AA”,DBR是在偏移X扇区里,X就是代码的tbl[8]的双字(4字节),也就是DPT的OFFSET 8~11。
假设有4分区,那么只要第一次检查0扇区是不是DBR,如果不是就检查第二次(只需检查1次即可,无需检查多次,目的是验证是否是FAT),检查偏移X扇区。
执行check_fs(fs, bsect);后返回0,则说明第4分区DBR就在X扇区,验证成功,返回!0则错误。

用电脑上的winhex去看下你的sd卡的0扇区是怎么回事
看看是不是mbr,当然 也得了解下这个mbr的结构
fat文件系统原理总得知道个大概吧

可以使用WinHex来查看MBR,方法为:打开WinHex,然后在菜单栏选择“工具”、“打开磁盘”,在“物理驱动器”一栏选择自己的硬盘打开就可以了。

对U盘的格式化就是按照文件系统的规范完成BUFF的组建,然后写入到指定的扇区中,例如FAT32的DBR、FAT表等。

ref

  1. stm32移植ecos #31,ecos sd driver,SD卡驱动(1)
  2. stm32移植ecos #32,ecos sd driver,SD卡驱动(2)
  3. stm32移植ecos #33,ecos sd driver,SD卡驱动(3)
  4. stm32移植ecos #34,ecos sd driver,SD卡驱动(4)
  5. reading size and used space on compact flash (CF) card?
  6. SD卡数据结构与FAT文件系统详细分析(上)
  7. SD卡数据结构与FAT文件系统详细分析(下)
  8. FatFs - Generic FAT File System Module
  9. FAT32文件系统详解
  10. FATFS 移植 (应用部分)
  11. FATFS f_open() 返回可怕的 FR_NO_FILESYS
  12. FAT系统原理.pdf
  13. FAT白皮书中文版.pdf


专注于用户空间的系统级编程–即内核之上的所有内容。

分散/聚集IO

分散/聚集IO是一种在单次系统调用中对多个缓冲区输入输出的方法,
可以把多个缓冲区的数据写到单个数据流,
也可以把单个数据流读到多个缓冲区。

与线性IO(标准读写系统调用)相比有以下优势:

  • 编码模式更自然,尤其针对分段的数据
  • 效率更高,单个向量IO操作可以取代多个线性IO操作
  • 性能更好
  • 支持原子操作

Linux实现了POSIX 1003.1-2001中定义的一组实现分散/聚集IO机制的系统调用:

#include <sys/uio.h>

struct iovec {
    void *iov_base;
    size_t iov_len;
};

ssize_t readv(int fd, const struct iovec *iov, int count);
ssize_t writev(int fd, const struct iovec *iov, int count);

readv()从fd中读取count个段到参数iov所指定的缓冲区中;
writev()从参数iov指定的缓冲区读取count个段,并写入到fd中。
每个iovec结构体描述一个独立的、物理不连续的缓冲区,称之为段:

writev()示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/uio.h>

int main(void)
{
    struct iovec iov[3];
    ssize_t nr;
    int fd, i;

    char *buf = {
        "AAAAAAAAAAAAAAAAAA\n",
        "BBBBBBBBBBBBBBBBBB\n",
        "CCCCCCCCCCCCCCCCCC\n"
    };

    fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC);
    if(-1 == fd) {
        perror("open");
        return 1;
    }

    /* fill out three iovec structures */
    for(i = 0; i < 3; i++) {
        iov[i].iov_base = buf[i];
        iov[i].iov_len = strlen(buf(i)) + 1;
    }

    nr = writev(fd, iov, 3);
    if(-1 == nr) {
        perror("writev");
        return 1;
    }
    printf("wrote %d bytes\n", nr);

    if(close(fd)) {
        perror("close");
        return 1;
    }

    return 0;
}

Event Poll

由于poll()和select()的局限,Linux2.6内核引入了event poll机制。
虽然epoll的实现比poll()和select()要复杂的多,epoll解决了前两个都存在的基本性能问题,并增加了一些新的特性。

对于poll()和select(),每次调用时都需要被监听的文件描述符列表。
内核必须遍历所有被监听的文件描述符。
当这个文件描述符列表变得很大时,每次调用都要遍历列表就变成规模上的瓶颈。

epoll把监听注册从实际监听中分离出来,从而解决这个问题。
一个系统调用会初始化epoll上下文,
另一个从上下文中加入或删除监听的文件描述符,
第三个执行真正的时间等待(event wait)。



创建新的epoll实例

#include <sys/epoll.h>

int epoll_create(int size);
int epoll_create1(int flags);

epoll_create()创建一个epoll的实例,当创建成功后,会占用一个fd,所以记得在使用完之后调用close(),否则fd可能会被耗尽。
自从Linux2.6.8版本以后,size值被忽略,内核可以动态的分配大小,只需要size参数大于0即可。

epoll_create()是老版本的epoll_create1()实现,接受参数flags支持修改epoll行为,当前,只有EPOLL_CLOEXEC是合法flag。
当flag = EPOLL_CLOEXEC,创建的epfd会设置FD_CLOEXEC。

epoll的标准调用方式如下:

int epfd;

epfd = epoll_create1(0);
if(epfd < 0)
    perror("epoll_create1");


epoll控制函数

#include <sys/epoll.h>

typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
};

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create1()的返回值,
第二个参数表示动作,用三个宏来表示:

  • EPOLL_CTL_ADD:注册新的fd到epfd中
  • EPOLL_CTL_MOD:修改已经注册的fd的监听事件
  • EPOLL_CTL_DEL:从epfd中删除一个fd

第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,events可以是以下几个宏的集合:

  • EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
  • EPOLLOUT:表示对应的文件描述符可以写
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
  • EPOLLERR:表示对应的文件描述符发生错误
  • EPOLLHUP:表示对应的文件描述符被挂起
  • EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,默认是条件触发(Level Triggered)
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

event_poll中的data变量是由用户私有使用。
当接收到请求的事件后,data会被返回给用户。
通常的用法是把event.data.fd设置为fd,这样可以很容易查看那个文件描述符触发了事件。



等待epoll事件

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events,
        int maxevents, int timeout);

当调用epoll_wait()时,等待epoll实例epfd中的文件fd上的事件,时限为timeout毫秒。
成功时,events指向描述每个事件的epoll_event结构体的内存,且最多可以有maxevents个事件,返回值是事件数。
当调用返回时,epoll_event结构体中的events变量描述了发生的事件。
data变量保留用户在调用epoll_ctl()前的所有内容。



边缘触发(ET)和条件触发(LT)

如果epoll_ctl()的参数event中的events设置为EPOLLET,fd上监听方式为边缘触发(ET),否则为条件触发(LT)。

以下面的生产者和消费者在通过UNIX管道通信时举例:

1.生产者向管道写入1KB数据
2.消费者在管道上调用epoll_wait(),等待管道上有数据并可读

通过LT,在步骤2中epoll_wait()调用会立即返回,表示管道可读。
通过ET,需要步骤1发生后,步骤2的epoll_wait()才会返回。
也就是说对于ET,在调用epoll_wait()时,即使管道已经可读,也只有在数据写入之后,调用才会返回。

  • LT:效率会低于ET触发,尤其在大并发,大流量的情况下。
    但是LT对代码编写要求比较低,不容易出现问题。
    LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。
  • ET:效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。
    但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。


epoll使用例子:

epoll_wait范围之后应该是一个循环,遍利所有的事件。
几乎所有的epoll程序都使用下面的框架:

for( ; ; )
{
    nfds = epoll_wait(epfd,events,20,500);
    for(i=0;i<nfds;++i)
    {
        if(events[i].data.fd==listenfd) //有新的连接
        {
            connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接
            ev.data.fd=connfd;
            ev.events=EPOLLIN|EPOLLET;
            epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中
        }
        else if( events[i].events&EPOLLIN ) //接收到数据,读socket
        {
            n = read(sockfd, line, MAXLINE);       //读
            ev.data.ptr = md;     //md为自定义类型,添加数据
            ev.events=EPOLLOUT|EPOLLET;
            epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
        }
        else if(events[i].events&EPOLLOUT) //有数据待发送,写socket
        {
            struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据
            sockfd = md->fd;
            send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据
            ev.data.fd=sockfd;
            ev.events=EPOLLIN|EPOLLET;
            epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据
        }
        else
        {
            //其他的处理
        }
    }
}

存储映射

内存映射支持应用程序将文件映射到内存中,即内存地址和文件数据一一对应。
开发人员可以通过内存来访问文件,就像操作内存中的数据块一样,甚至可以写入内存数据区,
然后通过透明的映射机制将文件写入磁盘。

mmap()和munmap()

#include <sys/mmap.h>  

void *mmap(void *addr,size_t len,int prot, int flags,int fd,off_t offset); 
int mummap(void *addr, size_t len);
  • addr:建议内核将文件映射到的地址,这只是一个hint,为了可移植性,一般设置为0,调用返回mapping开始的实际地址。
  • fd:要映像文件的描述符。
  • prot描述了内存映像的保护权限,可以使用OR连接以下选项:
    • PROT_READ:区域可读。
    • PROT_WRITE:区域可写
    • PROT_EXEC:区域可执行
    • PROT_NONE:区域不可访问,很少有用。
    • prot不能和打开文件的模式冲突。比如打开了一个只读文件,prot不可以指定为PROT_WRITE。
  • flags:描述了内存映像的方式,可以使用OR连接以下选项:
    • MAP_FIXED: addr是必须的,并且不是一个hint,如果内核无法在该地址映像,则失败。具有不可移植性,不推荐使用。
    • MAP_PRIVATE:映像不共享,文件被映像为copy-on-write,任何在内存中的改变,都不会反映在文件或者其他进程的mapping中。
    • MAP_SHARED:映像和其他进程共享映射的同一个文件,写入buffer等效于写入文件,读取映像写操作同时反映在其他进程中。
    • MAP_SHARED和MAP_PRIVATE其中之一必须被指定。

addr和off的值需要被指定为系统虚拟页的整数倍,可以通过使用_SC_PAGESIZE或_SC_PAGE_SIZE作为sysconf参数获得页的大小。
如果文件的大小12字节,但系统页的大小是512字节,那么系统调用会映射512字节,其他的部分被填充为0,
我们可以修改另外的500个字节,但是并不反应在文件中,所有我们不能使用mmap来append文件,我们需要首先增大文件到指定的值。

mummap删除了从addr地址开始,是page对齐的,连续的len字节的映像,一旦被删除,
内存中的区域就不在有效,访问会产生SIGSEGV信号。
munmap通常传入由mmap返回的addr以及设置的len长度。

内存映射实例

#include <stdio.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <unistd.h>  
#include <sys/mman.h>  

int main(int argc,char *argv[])
{  
    struct stat sb;  
    off_t i;  
    char *p;  
    int fd;  

    if(argc < 2){  
        fprintf(stderr,"usage: %s <file>\n",argv[0]);  
        return 1;  
    }  

    fd = open(argv[1],O_RDONLY);  
    if(fd == -1){  
        perror("open");  
        return 1;  
    }  

    if(fstat(fd,&sb) == -1){  
        perror("fstat");  
        return 1;  
    }  

    if(! S_ISREG(sb.st_mode)){  
        fprintf(stderr,"%s is not a regular file\n",argv[1]);  
        return 1;  
    }  

    p = mmap(0,sb.st_size, PROT_READ,MAP_SHARED,fd,0);  
    if(p == MAP_FAILED){  
        perror("mmap");  
        return 1;  
    }  

    if(close(fd) == -1){  
        perror("close");  
        return 1;  
    }  

    for(i = 0;i < sb.st_size; i++){  
        putchar(p[len]);  
    }  

    if(munmap(p,sb.st_size) == -1){  
        perror("mummap");  
        return 1;  
    }  
    return 0;  
}

mmap()优缺点

优点:

  1. 从内存映像文件中读写,避免了read、write多余的拷贝。
  2. 从内存映像文件中读写,避免了多余的系统调用和用户-内核模式的切换
  3. 可以多个进程共享内存映像文件。
  4. seeking内存映像只需要指针操作,避免系统调用lseek。

缺点:

  1. 内存映像需要时整数倍页大小,如果文件较小,会浪费内存。
  2. 内存映像需要在放在进程地址空间,大的内存映像可能导致地址空间碎片,找不到足够大的空余连续区域供其它用。
  3. 内核需要维护更多的和内存映像相关的数据结构。

调整映射大小

#define _GNU_SOURCE  

#include <unistd.h>  
#include <sys/mman.h>  

void *mremap(void *addr, size_t old_size, size_t new_size, unsigned long flags);  

mremap扩展或者缩小内存映像,从区域[addr,add+old_size]编程一个新的大小new_size。

flags:

  • 0:不可以移动来改变内存映像大小。
  • MREMAP_MAYMOVE:如果需要可以移动地址改变内存映像大小。

返回值:成功返回重新设置大小之后的内存映像的大小,失败返回MAP_FAILED,并设置errno:

  • EAGAIN:内存区域被锁定,无法改变大小。
  • EFAULT:给定区域的一些也有不合法的页或者重新映像存在问题。
  • EINAL:参数不合法。
  • ENOMEM:不移动则无法扩展,如果MREMAP_MAYMOVE没有被设置。

glibc经常使用mremap来实现高效的realloc():

void * realloc(void *addr, size_t len)
{  
    size_t old_size = look_up_mapping_size(addr);  
    void *p;  
    p = mremp(addr,old_size, len,MREMAP_MAYMOVE);  
    if(p == MAP_FAILED)  
        return NULL;  
    return p;  
} 

改变映射区域权限

mprotect可以允许程序改变已经存在内存区域的permissions:

#include <sys/mman.h>  

int mprotect(const void *addr, size_t len, int prot);  
//mprotect (memory, alloc_size, PROT_READ | PROT_WRITE);

一些系统只能改变由mmap得到的内存映像的protection,Linux可以操作任何一个内存区域。
成功返回0,失败返回-1,并设置errno为:

  • EACCESS:不可以被设置成prot的permissions,可能是文件打开时只读的,设置成可写
  • EINVAL:参数不合法。
  • ENOMEM:内核内存不足或者所给的内存区域不是进程的合法地址空间。

通过映射同步文件

POSIX提供了和文件操作中fsync类似的将文件和内存映像同步的操作:

#include <sys/mman.h>  

int msync(void *addr, size_t len, int flags);  

将内存映像flush到磁盘。没有msync,没有能够保证将mapping的脏数据写回磁盘,除非被unmapped。
当修改内存映像,进程直接修改在内核页cache中的文件页,
内核可能不会很快将内核的页cache同步到磁盘。

flags使用OR链接下面选项:

  • MS_ASYNC:异步的执行同步操作,msync立即返回,更新操作被调度。

  • MS_INVALIDATE:指定所有其他的内存映像cache副本无效。任何以后的操作都同步到磁盘中。

  • MS_SYNC:同步的执行同步操作,等将内容写入磁盘在返回。

    if(msync(addr,len,MS_ASYNC) == -1)

      perror("msync");  

成功返回0,失败返回-1,并设置errno:

  • EINVAL:MS_SYNC和MS_ASYNC同时被设置或者addr没有页对齐。
  • ENOMEM:所给的内存区域(或者部分)没有被映射。

给出映射提示

Linux提供了madvice来让进程建议内核或者给内核提供线索来使用mapping,这样可以优化mapping的使用。

#include <sys/mman.h>  

int madvice(void *addr, size_t len, int advice);  

madvise() 函数建议内核,在从 addr 指定的地址开始,
长度等于 len 参数值的范围内,该区域的用户虚拟内存应遵循特定的使用模式。
len如果为0,内核将建议施用于从addr开始的整个映像。advice:可以是以下之一:

  • MADV_NORMAL:没有特别的建议。建议使用中等程度的预读。
  • MADV_RANDOM:以随机访问的方式访问指定的区域。建议较少的预读。
  • MADV_SEQUENTIAL:顺序访问指定区域。建议大量预读
  • MADV_WILLNEED:将来要访问指定区域。初始化预读,将指定的页读到内存。
  • MADV_DONTNEED:将来不再访问指定区域。内核释放与指定页关联的资源。后续的读会导致从文件中再度调入。

调用mmap()时内核只是建立了逻辑地址到物理地址的映射表,并没有映射任何数据到内存。
在你要访问数据时内核会检查数据所在分页是否在内存,如果不在,则发出一次缺页中断。
将madvise()和mmap()搭配起来使用,在使用数据前告诉内核这一段数据我要用,将其一次读入内存。

  1. epoll详解
  2. LT自动挡,ET手动挡(epoll)
  3. EPOLL LT和ET区别
  4. epoll使用详解(精髓)
  5. How to use epoll? A complete example in C
  6. Linux的内存映射