分类目录归档:语言

tcl

一、与tcl结缘

它时最老牌的自动化脚本语言。初学时痛苦万分,遇到问题时无处寻求帮助。曾经下决心将其抛弃。
但就如同对键盘的复杂情感,用它做了不少工作。后来也真的放弃了。
忽然有一些发现某些事情,只需写几行简单的自动化代码即可完成,于是又想起了tcl~~~真香。
中途也用过 python--paramiko,写起来确实比较轻松,但是本质上套路还是一样。

二、tcl安装

centos直接安装:
    yum install -y tcl tclx tcl-devel
源码包安装:
    ./configure --with-tcl=/usr/lib --with-tclinclude=/usr/include/tcl-private/generic
    make && make install  安装完毕

还有 expect 也需要安装

三、tcl 基础语法

1、变量的定义与赋值

set a 0.0000016 ==>>0.0000016
set b [expr 1.0*$a] ==>> 0.0000016 ===>>1.6e-06
puts [format "%10.8f" $b] ==>> 0.00000160
puts [format "%10.6f" $b] ==>> 0.000002  打印的小数只有六位,最后被四舍五入

2、打印文件名及其大小

format "\"%s\" size=%d" $fileName [file size $fileName]

3、expr 关键字,数值计算

set a 101
set b 201
set sum1 [expr $a+$b]
set sum2 [expr $sum1+100]

set 1 100  ==>>> 这是什么神奇语法,实践下来果然如此
% set 1 200
200
% puts [expr 1 + 2]
3
% puts [expr $1 + 2]
202
% set 1 33
33
% puts [expr $1+2]
35

4、proc 程序

proc sum {arg1 arg2} {
   set r [expr $arg1+$arg2]
   return $r
}

sum 1 2

5、条件判断 if 关键字

if bool then body1 else body2

set r [expr $b%10]
set q [expr $b/10]
if {$r >= 5} then {
    set b [expr ($q+1)*10]
} else {
    set b [expr $q*10]
}

6、循环控制

A、while 循环
语法:while bool body

# 计算1000以内所有偶数之和
set a 0
set sum 0
while {$a <= 1000} {
   set sum [expr $sum+$a]
   set a [expr $a + 2]
}

B、for 循环
语法:for init bool step body

# 计算1000以内所有偶数之和
for {set a 0; set sum 0} {$a <= 1000} {set a [expr $a + 2]} {
   set sum [expr $sum + $a]
}

C、foreach 循环
语法:foreach var valueList body 

# 计算1000以内所有偶数之和
set L [list]
for {set a 0} {$a <= 1000} {incr a 2} {
   lappend L $a
}

set sum 0
foreach b $L {
   set sum [expr $sum + $b]
}

7、列表与数组

A、列表

举例,一个包含七个元素的列表: {1 2 a {3 {4 5} 6} "bcd ef" 7 {g h}}
其中第四个又是一个列表,第五个是一个字符串

简单的登陆别的机器执行命令,可行 (更详细的还得参考expect的有关说明)
expect.sh

#!/usr/local/bin/expect
set timeout 10
spawn ssh root@ip
expect "*password*"
send "123456\r"
expect "#"
send "service crond restart\r"
expect eof

注:
expect的功能是很强大的,实现了无须人与系统交互的功能,
已经成为SA的一个强大助手,要研究的彻底,还是需要大量的时间的。

一、tcl 概述

我们通过 Shell 可以实现简单的控制流功能,如:循环、判断等。
但是对于需要交互的场合则必须通过人工来干预,有时候我们可能会需要实现和交互程序如 telnet 服务器等进行交互的功能。
而Expect就使用来实现这种功能的工具。
  Expect是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。
Expect 的作者 Don Libes 在 1990 年开始编写 Expect 时对 Expect 做有如下定义 :
   Expect 是一个用来实现自动交互功能的软件套件 \(Expect [is a] software suite for automating interactive tools)。
   使用它系统管理员可以创建脚本用来实现对命令或程序提供输入,而这些命令和程序是期望从终端(terminal)得到输入,一般来说这些输入都需要手工输入进行的。
   Expect则可以根据程序的提示模拟标准输入提供给程序需要的输入来实现交互程序执行。甚至可以实现实现简单的 BBS 聊天机器人。
  Expect是不断发展的,随着时间的流逝,其功能越来越强大,已经成为系统管理员的的一个强大助手。Expect需要Tcl编程语言的支持,要在系统上运行Expect必须首先安装Tcl。

二、Expect工作原理

从最简单的层次来说,Expect的工作方式象一个通用化的 Chat 脚本工具。
Chat 脚本最早用于 UUCP 网络内,以用来实现计算机之间需要建立连接时进行特定的登录会话的自动化。
Chat 脚本由一系列 expect-send 对组成 :
   expect 等待输出中输出特定的字符,通常是一个提示符,然后发送特定的响应。

例如下面的 Chat脚本实现等待标准输出出现 Login:字符串,然后发送somebody作为用户名;然后等待Password:提示符,并发出响应 sillyme。
  引用:Login: somebody Password: sillyme
  这个脚本用来实现一个登录过程,并用特定的用户名和密码实现登录。
  Expect最简单的脚本操作模式本质上和 Chat 脚本工作模式是一样的。

例子:

1、实现功能

下面我们分析一个响应 chsh 命令的脚本。
我们首先回顾一下这个交互命令的格式。假设我们要为用户chavez改变登录脚本,要求实现的命令交互过程如下:
  引用:# chsh chavez
  Changing the login shell for chavez
  Enter the new value, or press return for the default
  Login Shell [/bin/bash]: /bin/tcsh
  #
可以看到该命令首先输出若干行提示信息并且提示输入用户新的登录 shell。
我们必须在提示信息后面输入用户的登录shell或者直接回车不修改登录shell。

2、下面是一个能用来实现自动执行该命令的 Expect 脚本

#!/usr/bin/expect
# Change a login shell to tcsh
set user [lindex $argv 0]
spawn chsh $user
expect "]:"
send "/bin/tcsh "
expect eof
exit

这个简单的脚本可以解释很多 Expect 程序的特性。
和其他脚本一样首行指定用来执行该脚本的命令程序,这里是/usr/bin/expect。
程序第一行用来获得脚本的执行参数(其保存在数组$argv中,从0号开始是参数),并将其保存到变量user中。
  第二个参数使用 Expect 的 spawn 命令来启动脚本和命令的会话,这里启动的是 chsh 命令,实际上命令是以衍生子进程的方式来运行的。
  随后的 expect 和 send 命令用来实现交互过程。脚本首先等待输出中出现 ]: 字符串,一旦在输出中出现chsh输出到的特征字符串(一般特征 字符串往往是等待输入的最后的提示符的特征信息)。
  对于其他不匹配的信息则会完全忽略。当脚本得到特征字符串时,expect将发送/bin/tcsh和 一个回车符给chsh命令。
  最后脚本等待命令退出(chsh结束),一旦接收到标识子进程已经结束的eof字符,expect脚本也就退出结束。

3、决定如何响应

管理员往往有这样的需求,希望根据当前的具体情况来以不同的方式对一个命令进行响应。
我们可以通过后面的例子看到 expect 可以实现非常复杂的条件响应,而仅仅通过简单的修改预处理脚本就可以实现。
下面的例子是一个更复杂的expect-send例子:
  expect -re "\[(.*)]:"
  if {$expect_out(1,string)!="/bin/tcsh"} {
      send "/bin/tcsh"
    }
  send " "
  expect eof

在这个例子中,第一个 expect 命令现在使用了 -re 参数,这个参数表示指定的的字符串是一个正则表达式,而不是一个普通的字符串。
对于上面这个例子里是查找一个左方括号字符(其必须进行三次逃逸(escape),因此有三个符号,
因为它对于 expect 和正则表达时来说都是特殊字符)后面跟有零个或多个字符,最后是一个右方括号字符。
这里.*表示表示一个或多个任意字符,将其存放在()中是因为将匹配结果存放在一个变量中以实现随后的对匹配结果的访问。

  当发现一个匹配则检查包含在[]中的字符串,查看是否为/bin/tcsh。
如果不是则发送 /bin/tcsh 给 chsh 命令作为输入,如果是则仅仅发送一个回车符。
这个简单的针对具体情况发出不同相响应的小例子说明了expect的强大功能。

  在一个正则表达时中,可以在()中包含若干个部分并通过 expect_out 数组访问它们。
各个部分在表达式中从左到右进行编码,从 1 开始(0 包含有整个匹配输出)。
() 可能会出现嵌套情况,这种情况下编码从最内层到最外层来进行的。

4、使用超时

下一个 expect 例子中将阐述具有超时功能的提示符函数。
这个脚本提示用户输入,如果在给定的时间内没有输入,则会超时并返回一个默认的响应。
这个脚本接收三个参数:提示符字串,默认响应和超时时间(秒)。
  #!/usr/bin/expect
  # Prompt function with timeout and default.
  set prompt [lindex $argv 0]
  set def [lindex $argv 1]
  set response $def
  set tout [lindex $argv 2]

  脚本的第一部分首先是得到运行参数并将其保存到内部变量中。
  send_tty "$prompt: "
  set timeout $tout
  expect " " {
      set raw $expect_out(buffer)
      # remove final carriage return
      set response [string trimright "$raw" " "]
  }
  if {"$response" == ""} {set response $def}
  send "$response

这是脚本其余的内容。可以看到 send_tty 命令用来实现在终端上显示提示符字串和一个冒号及空格。
set timeout 命令设置后面所有的 expect 命令的等待响应的超时时间为 $tout (-l 参数用来关闭任何超时设置)。

然后 expect 命令就等待输出中出现回车字符。
如果在超时之前得到回车符,那么 set 命令就会将用户输入的内容赋值给变量 raw。
随后的命令将用户输入内容(最后的回车符号去除以后)赋值给变量 response。

然后,如果 response 中内容为空则将 response 值置为默认值(如果用户在超时以后没有输入或者用户仅仅输入了回车符)。
最后 send 命令将 response 变量的值加上回车符发送给标准输出。

一个有趣的事情是该脚本没有使用spawn命令。该expect脚本会与任何调用该脚本的进程交互。
如果该脚本名为 prompt,那么它可以用在任何 C 风格的 shell 中。
  % set a='prompt "Enter an answer" silence 10'
  Enter an answer: test
  % echo Answer was "$a"
  Answer was test
  prompt设定的超时为10秒。如果超时或者用户仅仅输入了回车符号,echo命令将输出
  Answer was "silence"

5、一个更复杂的例子

下面我们将讨论一个更加复杂的expect脚本例子,这个脚本使用了一些更复杂的控制结构和很多复杂的交互过程。
这个例子用来实现发送 write 命令给任意的用户,发送的消息来自于一个文件或者来自于键盘输入。
  #!/usr/bin/expect
  # Write to multiple users from a prepared file
  # or a message input interactively
  if {$argc<2} {
      send_user "usage: $argv0 file user1 user2 ... "
      exit
  }

  # send_user 命令用来显示使用帮助信息到父进程(一般为用户的shell)的标准输出。
  set nofile 0
  # get filename via the Tcl lindex function
  set file [lindex $argv 0]
  if {$file=="i"} {
      set nofile 1  # means not check file
  } else {
      # make sure message file exists
      if {[file isfile $file]!=1} {
          send_user "$argv0: file $file not found. "
          exit
        }
   }
  这部分实现处理脚本启动参数,其必须是一个储存要发送的消息的文件名或表示使用交互输入得到发送消的内容的"i"命令。

  变量 file 被设置为脚本的第一个参数的值,是通过一个 Tcl 函数 lindex 来实现的,
该函数从列表/数组得到一个特定的元素。[] 用来实现将函数 lindex 的返回值作为 set 命令的参数。

  如果脚本的第一个参数是小写的 "i",那么变量nofile被设置为1,否则通过调用 Tcl 的函数 isfile 来验证参数指定的文件存在,如果不存在就报错退出。

  可以看到这里使用了 if 命令来实现逻辑判断功能。
该命令后面直接跟判断条件,并且执行在判断条件后的 {} 内的命令。if 条件为 false 时则运行 else 后的程序块。
  set procs {}
  # start write processes
  for {set i 1} {$i<$argc} {incr i} {
      spawn -noecho write [lindex $argv $i]
      lappend procs $spawn_id
  }

最后一部分使用 spawn 命令来启动 write 进程实现向用户发送消息。
这里使用了 for 命令来实现循环控制功能,循环变量首先设置为 1,然后依次递增。循环体是最后的 {} 的内容。
这里我们是用脚本的第二个和随后的参数来 spawn 一个 write 命令,并将每个参数作为发送消息的用户名。
lappend 命令使用:保存每个 spawn 的进程的进程 ID 号的内部变量 $spawn_id 在变量 procs 中构造了一个进程ID号列表。

  if {$nofile==0} {
       setmesg [open "$file" "r"]
  } else {
      send_user "enter message,
      ending with ^D: "
    }

  最后脚本根据变量 nofile 的值实现打开消息文件或者提示用户输入要发送的消息。
  set timeout -1
  while 1 {
      if {$nofile==0} {
          if {[gets $mesg chars] == -1} {
                break
            }
          set line "$chars "
      } else {
          expect_user {
              -re " " {}
              eof break
            }
          set line $expect_out(buffer)
       }
       foreach spawn_id $procs {
          send $line
       }
       sleep 1
    }
    exit

上面这段代码说明了实际的消息文本是如何通过无限循环 while 被发送的。
while 循环中的 if 判断消息是如何得到的。
在非交互模式下,下一行内容从消息文件中读出,当文件内容结束时 while 循环也就结束了。(break命令实现终止循环) 。

  在交互模式下,expect_user 命令从用户接收消息,当用户输入ctrl+D 时结束输入,循环同时结束。
两种情况下变量 $line 都被用来保存下一行消息内容。当是消息文件时,回车会被附加到消息的尾部。

  foreach 循环遍历 spawn 的所有进程,这些进程的 ID 号都保存在列表变量 $procs 中,实现分别和各个进程通信。
send 命令组成了 foreach 的循环体,发送一行消息到当前的 write 进程。
while 循环的最后是一个 sleep 命令,主要是用于处理非交互模式情况下,以确保消息不会太快的发送给各个write进程。
当while循环退出时,expect脚本结束

tcl 之坑
1、tcl 脚本执行时,貌似没有按顺序执行
tcl 执行时,出现顺序混乱,纠结了很久

exp_send "\r\n"
expect {
    "root\ @\ user" { exp_send "ls\r"}
    timeout         { exit  0        }
}
expect {
    "root\ @\ user" { exp_send "make\r"}
    timeout         { exit  0        }
}
expect {
    "root\ @\ user" {
      puts "build finished!\r"
      exp_send "ls\r"
  }
    timeout         { exit  0        }
}

---->> 这段代码看似没问题,但是编译时,还没有编译结束,就先打印 "build finished".
究其原因,是因为先遇到了关键字 "root\ @\ user",所以进入下一段。
如果想要按照顺序执行,必须严格缩写 expect 字符串:
expect {
    # 这里是完整的命令行,不会再出现误判
    "\[root @ user ]$ " {
        puts "build finished!\r"
    }
  timeout { exit 0 }
}

splint

一、安装

1、在网络环境、配置OK的系统,可以 yum 一键安装

2、某些情况无法一键安装,需要下载源码自己安装

下载splint-3.1.2.tar.gz:https://github.com/splintchecker/splint

3、安装

[wishcell@localhost]~/test/splint-master% autoreconf -vif
[wishcell@localhost]~/test/splint-master% ./configure
[wishcell@localhost]~/test/splint-master% make
....
e.o lclsyntable.o lcltokentable.o sort.o symtable.o lclinit.o shift.o lclscan.o lsymbol.o mapping.o -lfl
/usr/bin/ld: 找不到 -lfl
collect2: 错误:ld 返回 1
make[3]: *** [splint] 错误 1
make[3]: 离开目录“/home/wishcell/test/splint-master/src”
make[2]: *** [all] 错误 2
make[2]: 离开目录“/home/wishcell/test/splint-master/src”
make[1]: *** [all-recursive] 错误 1
make[1]: 离开目录“/home/wishcell/test/splint-master”
make: *** [all] 错误 2
[wishcell@localhost]~/test/splint-master%
解决 -lfl 报错问题:
[root@localhost splint-master]# sudo yum install flex
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: mirrors.aliyun.com
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
正在解决依赖关系
--> 正在检查事务
---> 软件包 flex.x86_64.0.2.5.37-3.el7 将被 升级
---> 软件包 flex.x86_64.0.2.5.37-6.el7 将被 更新
--> 解决依赖关系完成
然后重新: ./configure && make 时间还挺长,最后仍然报错,还是找不到
原来需要安装开发包:
[root@localhost splint-master]# yum -y install flex-devel
再次 make ,很快完成
[root@localhost splint-master]# whereis splint
splint: /usr/local/bin/splint
[root@localhost splint-master]#

二、linux下面,超级严格的代码检查工具

1、基础用法,检验单个文件

A、示例代码

test.c:
int main()
{
    int a = 3;
    int b = 4;
    printf("a +b = %d", a +b);
    return 0;
}

B、用法

/usr/local/bin/splint test.c

2、检测工程,需要对复杂的目录结构下所有C文件检测

A、困难

此时,检测的每个C文件,都会要求包含其依赖的头文件。
即需要包含头文件。项目特别复杂,怎么办?

B、解决办法

可以信托项目中原来的 makefile 框架,在编译规则中,将生成
中间文件 .o 的操作,改为splint检测规则

C、举例

GTEST_DIR=./GTEST-1.7.0
CurrDir=$(shell pwd)
spline=/usr/bin/splint \
      -booltrue TRUE \
      -retvalint     \
      +bounds        \
      -type          \
      +boundsread    \
      +trytorecover  \
      -nullstate     \
      charindex      \
      -immediatetrans\
      -mustfreefresh \
      -nullret       \
      mustfreeonly   \
      -temptrans     \
      -dependenttrans\
      -boundswrite   \
      -bufferoverflowhigh \
      -retvalother   \
      -mayaliasunique\
      -exportlocal   \
      -initalelements\
      +ignoresigns   \
      +ignorequals   \
      +relaxtypes    \
      -predboolothres\
      -predboolint   \
      -globstate     \
      -unqualifiedtrans\
      -compdestroy   \
      -compdef       \
      -usedef        \
      -paramuse      \
      -nullassign    \
      -formattype    \
      -onlytrans     \
      -nullpass      \
      -usereleased   \
      -redef         \
      -compmempass   \
      -branchstate   \
      +posixlib      \
      -shiftnegative \
      -boolops       \
      -evalorder     \
      -unregcog      \
      -likelybool

PrjDir = ../
Module1= ../module_1/source
Module2= ../module_2/source
Module3= ../module_3/source

CaseDir = ./case
stubDir = ./stub/source

linux_env=$(shell getconf LONG_BIT)
ifeq($(linux_env), 32)
   DEFS=-DMACHINE_BIT32=32
else
   DEFS=-DMACHINE_BIT64=64
endif

INCS=-I../module_1/include \
     -I../module_2/include \
     -I../module_3/include \
     -I$(GTEST_DIR)/include\
     -I$(GTEST_DIR)/include/gtest \
     -I$(GTEST_DIR)/include/gtest/internal

vpath %c $(../module_1)
vpath %c $(../module_2)
vpath %c $(../module_3)

PrjSrc=$(notdir $(wildcard $(module1_dir)/*.c))\
       $(notdir $(wildcard $(module2_dir)/*.c))\
       $(notdir $(wildcard $(module3_dir)/*.c))
PrjOBJ=$(PrjSrc:%.c=$(PrjOBJDir%.x))
stubOBJ=$(stubSrc:%.c=$(StubOBJDir%.x))

#这里原来是做中间文件生成的,但是现在让它生成检测日志
$(PrjOBJDir)%.x : %.c
    @$(SPLINT) ${INCS}


lt; || echo '' $(TESTS): ${PrjObj} ${CaseObj} ${StubObj} echo '' .PHONY: clean clean: rm -f $(TESTS) *o *.gcda * 

三、强大的测试能力

1、典型示例

A、典型案例背景说明

对数据非法越界操作,连valgrind都无法检测,甚至一个warning都没有.
在splint的检测报告中,显然看到了对它的告警

B、示例代码

混用结构体中的 char data[0]; 和 char *data 成员。
造成特定类型cpu环境崩溃。在某些环境上,无异常症状

C、实例检测效果

i、先用 valgrind 做对比实验

valgrind --tool=memcheck -leak-check=full --max-stackframe=20000000 ./xx > log 2>&1

ii、splint 检测结果

在修改 splint 告警的过程中,必然会解决掉越界问题

shttpd

[toc]

shttpd 上传文件后,如果想对数据处理一番,然后跳转到新的页面。正确做法是在建立开始传输文件时,回应一个完整的页面头给浏览器,然后一包一包地接收,直到接收完毕,并处理完成,再将跳转的完事web页面发送给浏览器。

web页面编码与后台编码不一致的问题

页面是gbk显示中文,但是传到后台,C中处理变成了乱码。将其改为utf-8,传到后台能成功识别,但是前台页面又变成了乱码。

解决方案

https://www.cnblogs.com/Marydon20170307/p/11362559.html
linux vim 环境下,查看文件编码,发现是cp936,需要将其转化为utf-8,然后将 shttpd example.c中,返回html内容的 example.c 转化为 utf-8格式。这样web浏览器中文显示正常,数据传到后台也能正常处理

iconv 转换cp936到utf-8

1.用vim打开文件,确保下面两个步骤

vim xx.txt
set ff=unix
set fileencoding=utf-8

2.用工具iconv转换

iconv -f gbk -t utf-8 -c xx.txt -o xx.txt.o
奇怪的是,转换之后,xx.txt和转换之前并无区别。但是此时xx.txt放到web页面上竟然显示正常了

指定根目录,后续资源默认都到根目录查找

可以对shttpd主函数进行改造,直接将部分参数写死,而不是从命令行传入,这样反而简单
int main() 或者在多进程中,将其函数名改为其它,如int webServer(). 在函数入口处,即自己构造参数,模拟手工输入:

int argc=3;
char *argv[10] =
{
     "webServer", //这是服务器的名字
     "-root",     //这是干什么的?难道是权限?
     "./web",     //指定当前路径下的web为根目录
0,0,0,0,0,0,0
};


对于shttpd的某些URL,需要指定处理函数,或者转发给脚本处理。在shttpd中,都是指定函数自己解决,如:

shttpd_register_uri(ctx, "/post.html", &receive_post_from_client, (void*)data
表示对于资源 127.0.0.1/post.html 的请示,将由函数
receive_post_from_client来处理。
而这个post.html 在网页中一般是什么样子呢?
实际上就是一个 \<form>, 可以是shttpd自己通过shttpd_printf()返回给前端的页面,也可以是web/xx.html中的内容。

场景一

shtpd_printf(arg, "%s", "<form id=\"upload\" method=\"post\" action=\"/post.html\" enctype=\"multipart/form-data\" >"
    "<input type=\"file\" ... />"
    "<input type=\"text\" ... />"
    "<input type=\"hidden\" ... />"    "<input type=\"submit\" ... />"
"</form>");


其中,method="post"表示传输方式,post加密,get为明文传输。action="/post.html",表示资源为127.0.0.1:port/post.html,也就是说要由 receive_post_from_client 来处理,这在前面服务器启动已经注册。写成action="post.html"效果也是同样的

场景二

在web/目录下,存在某个html文件,xx.html中有一段代码 :

<form id="upload" method="post" action="post.html" enctype="multipart/form-data>
   <input type="file" ... />
    <input type="text" ... />
   <input type="hidden" ... />
   <input type="submit" ... />
</form>


同样,这个文件被打开的时候:127.0.0.1:port/xx.html
只要点击控件中的提交按钮,或者在其它js 函数中,执行> 了$("#upload").submit操作,都能达到提交效果。将数据传输到后台

shttpd中C函数可以引用 js 文件

访问shttpd首页时,默认由show_index函数,输出 html 字符串。默认的样例中,只回复了基础的http协议报文
现在要让它支持更多功能,需要继续加大输出,用C来实现webServer,显得十分臃肿笨拙。但是前端还是能勉强展示出效果。

shttpd_printf(arg, "%s",
  "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
  "<html>"
  "<head>"
  "<meta http-equiv='Content-Type' content='text/html;charset=utf-8' />"
  "<style>div{float:left;border:2px;solid #000}" 
     "ol{margin:5; padding:0}"
    ".div-inline{margin:0 40px}"
  "</style>"
  "<script src=\"./jquery.min.js\">      "</script>"
  "<script src=\"./otherFunction.js\">   "</script>"
  "<script src=\"./someModule.js\">      "</script>"
  "</head>"
  "<body style=\"text-align:center; background-color:#d1d1e3\" >"
  "<br/>"
  "<h1>欢迎使用shttpd服务器</h1>"
);


在这里,已经将这么多功能抽象到 js 文件中,然后通过引用的方式来使用,极大的简化了代码.
同样,还有许多CSS是C写死的,也就当封装成CSS文件来引用。

python

python2.7 中的调试模块

https://blog.csdn.net/weixin_33843409/article/details/92691700

python 中的zip函数
import os,pdb
ass=zip(["good","better","best","supper"],[[],[],None,None])
myDict=dict(ass)
print(ass)
print(myDict)

用 python2.7 的执行结果:

----------------
 $ python2.7 ass.py
[('good', []), ('better', []), ('best', None), ('supper', None)]
------------------
{'better': [], 'good': [], 'best': None, 'supper': None}

用 python3.8 的执行结果:

 $ python ass.py
<zip object at 0xe838be28>
-------------------
{'good': [], 'better': [], 'best': None, 'supper': None}
$

python模块中 all 的作用

test.py文件开头写上: __all__ =[func1,func2]
当其它文件导入 from test import *
只会导出"[func1, func2]"里面的内容,其它调用不行

python中为什么要继承 object 类
https://www.zhihu.com/question/19754936

python2.7中,将输入内容转化为unicode

https://www.cnblogs.com/jinjiangongzuoshi/p/4375071.html

import  os, sys
val=raw_input("input your name:").decode(sys.stdin.encoding)


decode作用是把其它编码的字符串转换成unicode编码:name.decode("GB2312")
encode则是将unicode编码的字符串转换成其它编码:name.encode("GB232")表示将

又一个惊掉下巴的用法

import os,sys
[print("i=%d"%(i))  for i in range(0,1)]


此语句在 python3.7 环境运行,成功输出0~9
但在python2.7环境运行,却提示语法错误。但是在python2.7的项目中,却出现了此语法而没有报错,晕...

python中创建元组tuple

https://www.cnblogs.com/SCCQ/p/1266543.html
创建元组的方法,与创建列表很类似,只是将创建列表时的【】改为()即可:

创建元组:t=('aa','bb','cc')
创建数组:s=['aa','bb','cc']
两者区别在于,元组创建之后,即不能再更改,也不能插入。而数组可以随便操作

python中参数带星号的作用

https://blog.csdn.net/claire03/article/details/78950998?utm_source=blogxgwz5
1、对于函数定义:表示允许接收过量参数
2、对于调用,表示将参数打包成一个参数传入,然后在函数内部解开

python异常处理

import os,pdb
arr=[1,2,3,4,5]
for i in range(3,10):
    try:
        print(arr[i])
    except:
        print(%d failed"%(i))

python同时给多个变量赋值
  直接赋值
  a,b=0,1
  assert a==0
  assert b==1
  从数组(列表)赋值
  (r,g,b)==["red","green","blue"]
  assert r=="red"
  assert g=="green"
  assert b=="blue"
  从元组赋值
  (x,y)=(1,2)
  assert x==1
  assert y==2

python字典遍历
https://www.cnblogs.com/FlyingLiao/p/11192330.html

python中的property关键字

简而言之,就是把函数当成成员变量一样的 get.
具体解释,还是廖雪峰说的明白
https://www.liaoxuefeng.com/wiki/897692888725344/923030524000032

python中星号参数的作用

一句话简述:
      一个星号就是压缩参数与解压参数
  定义的地方,def func(a,b,*args)表示支持参数扩展,把收到的一个参数扩展成N个。
  相应的,在调用的地方,则表示将多个数据打包成一个参数传入
  https://blog.csdn.net/mm2zzyzzp/article/details/83057152

python2.7开发环境迁移

1、故事背景

因为工作需要,原来的windows环境需要切换为linux。
本机所有文件都被限期迁移到linux上。
整个团队只有一台win-server2008。

原来本地部署的python开发环境,经过长期积累,已经安装了许
多第三方库。即将面临废弃的风险。

迫切需要将现在开发环境原封不动的移到新服务器上

2、迁移方法,以及其中遇到的问题

方法:
   最简单的将 C:\python27 目录直接复制过去

遇到问题一:
   一开始运行简单程序还好,没问题。但是执行到某些第三方库时,会提示找不到对应的dll。
解决办法:
   将 python2.7.msi 安装文件,重新执行一次安装操作,选择修复。修复结束后,不再提示错误。

遇到问题二:
   操作excel时报错,花费了半天时间。一开始以为是 win32com模块在迁移后出了问题。
   后来认真查看错误,百度查找。原来只是因为服务器上没有安装office/excel 或者 wps/excel。
解决办法:
   安装wps/excel之后,即可正常执行,能生成excel和数据库

python 大数据扫描内存不足问题解决

1、来源

【解决Python memory error的问题(四种解决方案)_小白白的博客-CSDN博客】https://blog.csdn.net/weixin_39750084/article/details/81501395

2、引出问题

本文原作者:
昨天在用用Pycharm读取一个200+M的CSV的过程中,竟然出现了Memory Error!
简直让我怀疑自己买了个假电脑,毕竟是8G内存i7处理器,一度怀疑自己装了假的内存条。
下面说一下几个解题步骤。。。。一般就是用下面这些方法了,按顺序试试.

本人的原由:因为扫描100G左右的 TXT 文件,逐个进行内容
分析,
内存显然还没有用尽,程序就报 memory error!找了很久,
好不容易找到这篇文档,尝试后成功解决问题。

一、逐行读取

如果你用pd.read_csv来读文件,会一次性把数据都读到内存里来,导致内存爆掉,那么一个想法就是一行一行地读它,代码如下:
data = []
with open(path, 'r',encoding='gbk',errors='ignore') as f:
    for line in f:
        data.append(line.split(','))
        data = pd.DataFrame(data[0:100])

这就是先用with open把csv的每一行读成一个字符串,然后因为csv都是靠逗号分隔符来分割每列的数据的,那么通过逗号分割就可以把这些列都分离开了,然后把每一行的list都放到一个list中,形成二维数组,再转换成DataFrame。
这个方法有一些问题,首先读进来之后索引和列名都需要重新调整,其次很多数字的类型都发生了变化,变成了字符串,最后是最后一列会把换行符包含进去,需要用replace替换掉。 
不知道为什么,用了这个操作之后,还是出现了Memory error的问题。基于这些缺点以及遗留问题,考虑第二种解决方案。 

二、巧用pandas中read_csv的块读取功能

pandas设计时应该是早就考虑到了这些可能存在的问题,所以在read功能中设计了块读取的功能,也就是不会一次性把所有的数据都放到内存中来,而是分块读到内存中,最后再将块合并到一起,形成一个完整的DataFrame。
f = open(path)
data = pd.read_csv(path, sep=',',engine = 'python',iterator=True)
loop = True
chunkSize = 1000
chunks = []
index=0
while loop:
    try:
        print(index)
        chunk = data.get_chunk(chunkSize)
        chunks.append(chunk)
        index+=1
    except StopIteration:
        loop = False
        print("Iteration is stopped.")
        print('开始合并')
        data = pd.concat(chunks, ignore_index= True)

以上代码规定用迭代器分块读取,并规定了每一块的大小,即chunkSize,这是指定每个块包含的行数。
这个方法能够保持数据的类型,也不需要自己费心思去调整列名和index,比较方便。但不幸的是,我的还是出现了这个问题,如果你的用了这种方法还是出现memory error,你可以继续往下看。 

三、扩充虚拟内存

我在运行代码的过程中发现,出现memory error错误的时候,其实我的内存只用到了40+%,
所以其实不太可能会出现这个错误啊,所以我查了下,发现有说是内存被限制了,
考虑关掉一些可能限制内存的软件啦,扩大虚拟内存啦,这些的。
扩大虚拟内存的方法(我的系统是win8,不过应该都大同小异):
1、打开 控制面板;
2、找到 系统 这一项;
3、找到 高级系统设置 这一项;
4、点击 性能 模块的 设置 按钮;
5、选择 高级面板,在 虚拟内存 模块点击更改;
6、记得 不要 选中“自动管理所有驱动器的分页文件大小”,然后选择一个驱动器,也就是一个盘,选中自定义大小,手动输入初始大小和最大值,当然,最好不要太大,更改之后能在查看盘的使用情况,不要丢掉太多空间。
7、都设置好之后,记得点击 “设置”, 然后再确定,否则无效,最后 重启电脑 就可以了。
悲剧的是我在设置完这一步之后还是出现了内存错误,到这一步就没有问题了的朋友就可以不用看下面一种解法了,如果还是有问题,可以接下来看。

四、更新Pandas和Numpy库为64位

如果你的Python用的是32位的,那么你的pandas和Numpy也只能是32位的,那么当你的内存使用超过2G时,就会自动终止内存。发现这个错误也是因为我察觉到报内存溢出的错误的时候,我的内存明明显示只用了40+%,然后错误提示是在pandas的core中,所以查了一下,发现原来还有这么个大坑。 
解决方法就是:先检查一下你的python是多少位的,在shell中输入python,查看位数,如果是32位,那么就重装Python,装一个64位的,但同时你的库也需要重新装了。不过我执行完这一步之后,问题就完美解决了!

五、如果还有内存溢出的错

    以上四种方法,按顺序逐一使用,到哪一步错误消失了就可以停止啦。
当然了,如果你的内存显示使用达到了99%+,那么就是内存真的不够用,
不是别的问题,如果不是数据量特别大,那就是写代码的时候的习惯问题,
虽然Python有垃圾回收机制,但是有时候可能来不及回收,尤其是在循环迭代这些过程中,
往往会循环完毕了才来得及收拾垃圾,所以记得及时把不要的变量del掉,
或者用gc这个垃圾回收库,这样内存自然就一直清清爽爽啦~

字典中先判断变量是否定义,没有的话则定义

1、引出问题

因为自己负责的python代码崩溃,通过添加打印,终于找到死因:
访问了没有定义的字典元素。
将故障演示如下

##2、故障演示

a={"a1":(39,40,41,42),
   "a2":(49,50,51),
  };
print(a); print(a["a1"]); print(a["a2"])

if 50 in a["a1"]:
    print "good-1"

if 50 in a["a2"]:
    print "good-2"

# 这里就是故障发生的地方,我用try规避了一下
try:
    print a["a3"]
except:
    a["a3"]={}

# 换一种更加科学的写法
if "a3" in a.keys():
    print ("a3 in in a dictionary!")
    1; #如果数据量太大,打印会严重影响速度,直接放个1什么事情都不做
else:
    a["a3"]={}

pip 安装自己的库文件
1、用法
pip install pywin32 --trusted-host mirrors.my.com.cn -i http://mirrors.my.com.cn/pypi/simple

pip install pywin32 --trusted-host 192.168.1.9 -i http://192.168.1.9/pypi/simple

2、怎样制作自己的服务器

来源
https://www.cnblogs.com/special-li/p/9149573.html

具体操作
应用场景:  Windows10
安装 : pip install pypiserver
使用 :服务器端
创建包仓库,eg D:/packages
把下载好的python包放到创建好的仓库里
启动pypi-server服务器,并指定仓库目录
## Will listen to all IPs.
pypi-server -p 8080 D:/packages

客户端
配置pip.ini文件
[global]
index-url=http://<server_ip>:8080/simple
trusted-host=<server_ip>
no-warn-script-location=False

从服务器下载packages
pip install <package>

搜索包
pip search --index http://<server_ip>:8080

值得注意的是: 想要search正常工作,url后面不能以 simple 结尾

获取帮助信息
pypi-server --help

满足条件时进入断点
import pdb
if (isValidCondition(3)):
    print("something error happens")
    pdb.set_trace

ModuleNotFoundError: No module named 'setuptools'
执行python安装命令时报错"ModuleNotFoundError: No module named 'setuptools'"如何处理?

答: 安装python-setuptools/python3-setuptools
$ sudo apt-get install python-setuptools python3-setuptools

python unitTest

https:/www.cnblogs.com/coderzh/archive/2009/12/01/pythoncoverage.html
仅仅四步即实现代码覆盖率的统计功能

python2.7 -m pip install coverage
unittest相关
https://docs.python.org/zh-cn/3.7/library/unittest.html

yield函数

import  re
import  pdb
def yield_test(n):
    for i in range(n):
        yield call(i)
        print("step_3:i=%d\n"%i)
    print("do something else..")
    print("end."

def call(i):
    print("step_1: input %d and call() returns %d"%(i,i*2))
    return i*2

for result in yield_test(5):
    print("step_2:result = %d"%(result))


https://www.cnblogs.com/ethancui/p/5656757.html
这个实践收获很不错,特别爽,人家写的也好。

nodejs

一、一个nodejs生成excel的demo

https://www.jianshu.com/p/56d91006110f

https://www.jianshu.com/p/56d91006110f

#nodejs在没有网络的情况下如何离线安装

https://www.jianshu.com/p/dc282bd720ef

一个nodejs做后台的完事业务,nodejs全栈

https://www.cnblogs.com/adouwt/p/8047304.html

LinuxC上帝视角

LinuxC上帝视角
零、文章来源

文章来源

主体复制别人的内容,自己略有增加(后续补充)

一、Linux下的C编程实战(1)

1、开发平台搭建

Linux操作系统在嵌入式系统领域的延伸也可谓是如日中天,许多版本的嵌入式Linux系统被开发出来,如ucLinux、RTLinux、ARM-Linux等等。
   在嵌入式操作系统方面,Linux的地位是不容怀疑的,它开源、它包含TCP/IP协议栈、它易集成GUI。
   鉴于Linux操作系统在服务器和嵌入式系统领域愈来愈广泛的应用,社会上越来越需要基于Linux操作系统进行编程的开发人员。

鉴于读者的需求,在本文中,笔者将对Linux平台下C编程的几个方面进行实例讲解,并力求回答读者们关心的问题,
以与读者朋友们进行交流,共同提高。在本文的连载过程中,有任何问题或建议,您也可以进入笔者的博客参与讨论:
http://hovertree.com/  (截止2020-10-07转发此文时,进入链接,没有找到相关讨论,或者极少有程序员文章,而是到处音乐相关链接)

笔者建议在PC内存足够大的情况下,不要直接安装Linux操作系统,最好把它安装在运行VMWare虚拟机软件的Windows平台上

2、GCC编译器

GCC是Linux平台下最重要的开发工具,它是GNU的C和C++编译器,其基本用法为:
    gcc [options] [filenames]
options为编译选项,GCC总共提供的编译选项超过100个,但只有少数几个会被频繁使用,我们仅对几个常用选项进行介绍。

假设我们编译一输出“Hello World”的程序:
/* Filename:helloworld.c */
int main()
{
    printf("Hello World\n");
    return 0;
}`/* 何问起 hovertree.com */`

最简单的编译方法是不指定任何编译选项: gcc helloworld.c
它会为目标程序生成默认的文件名a.out,我们可用-o编译选项来为将产生的可执行文件指定一个文件名来代替a.out。
例如,将上述名为 helloworld.c 的 C 程序编译为名叫helloworld的可执行文件,需要输入如下命令:
  gcc –o helloworld helloworld.c

-c 选项告诉GCC仅把源代码编译为目标代码而跳过汇编和连接的步骤;
-S 编译选项告诉 GCC 在为 C代码产生了汇编语言文件后停止编译。GCC 产生的汇编语言文件的缺省扩展名是.s,上述程序运行如下命令:

[root@ecs-b0b7 c]# gcc -S helloworld.c
将生成helloworld.c的汇编代码,使用的是AT&T汇编。
`
[root@ecs-b0b7 c]# vim helloworld.s
        .file   "helloworld.c"
        .section        .rodata
.LC0:
        .string "Hello World"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $.LC0, %edi
        call    puts
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-39)"
        .section        .note.GNU-stack,"",@progbits
`

-E 选项指示编译器仅对输入文件进行预处理。当这个选项被使用时,
预处理器的输出被送到标准输出(默认为屏幕)而不是储存在文件里。
将头文件引用全部展开:
`
[root@ecs-b0b7 c]# gcc -E helloworld.c | head -n 20
# 1 "helloworld.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "helloworld.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 375 "/usr/include/features.h" 3 4
# 1 "/usr/include/sys/cdefs.h" 1 3 4
# 392 "/usr/include/sys/cdefs.h" 3 4
# 1 "/usr/include/bits/wordsize.h" 1 3 4
# 393 "/usr/include/sys/cdefs.h" 2 3 4
# 376 "/usr/include/features.h" 2 3 4
# 399 "/usr/include/features.h" 3 4
# 1 "/usr/include/gnu/stubs.h" 1 3 4
# 10 "/usr/include/gnu/stubs.h" 3 4
# 1 "/usr/include/gnu/stubs-64.h" 1 3 4
# 11 "/usr/include/gnu/stubs.h" 2 3 4
`
-O 选项告诉GCC对源代码进行基本优化从而使得程序执行地更快;而-O2选项告诉GCC产生尽可能小和尽可能快的代码。
使用-O2选项编译的速度比使用-O时慢,但产生的代码执行速度会更快。

-g 选项告诉GCC产生能被GNU调试器使用的调试信息以便调试你的程序,可喜的是,在GCC里,我们能联用-g和-O (产生优化代码)。

-pg 选项告诉GCC在你的程序里加入额外的代码,执行时,产生gprof用的剖析信息以显示你的程序的耗时情况。
`
[root@ecs-b0b7 c]# gcc helloworld.c -O3 -g -pg -o helloworldO3
[root@ecs-b0b7 c]# gprof helloworldO3
Flat profile:

Each sample counts as 0.01 seconds.
 no time accumulated

  %   cumulative   self              self     total
 time   seconds   seconds    calls  Ts/call  Ts/call  name

 %         the percentage of the total running time of the
time       program used by this function.

cumulative a running sum of the number of seconds accounted
 seconds   for by this function and those listed above it.

 self      the number of seconds accounted for by this
seconds    function alone.  This is the major sort for this
           listing.

calls      the number of times this function was invoked, if
           this function is profiled, else blank.

 self      the average number of milliseconds spent in this
ms/call    function per call, if this function is profiled,
           else blank.

 total     the average number of milliseconds spent in this
ms/call    function and its descendents per call, if this
           function is profiled, else blank.

name       the name of the function.  This is the minor sort
           for this listing. The index shows the location of
           the function in the gprof listing. If the index is
           in parenthesis it shows where it would appear in
           the gprof listing if it were to be printed.

Copyright (C) 2012-2016 Free Software Foundation, Inc.

Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved.
[root@ecs-b0b7 c]#
`

3、GDB调试器

GCC用于编译程序,而 Linux 的另一个 GNU 工具 gdb 则用于调试程序。
gdb 是一个用来调试 C 和 C++ 程序的强力调试器,我们能通过它进行一
系列调试工作,包括设置断点、观查变量、单步等。

其最常用的命令如下:
file:装入想要调试的可执行文件。
kill:终止正在调试的程序。
list:列表显示源代码。
next:执行一行源代码但不进入函数内部。
step:执行一行源代码而且进入函数内部。
run:执行当前被调试的程序
quit:终止gdb
watch:监视一个变量的值
break:在代码里设置断点,程序执行到这里时挂起
make:不退出gdb而重新产生可执行文件
shell:不离开gdb而执行shell

下面我们来演示怎样用GDB来调试一个求0+1+2+3+…+99的程序:
/* Filename:sum.c */
void main()
{
  int i, sum;
  sum = 0;
  for (i = 0; i < 100; i++)
  {
    sum +  = i;
  }
  printf("the sum of 1+2+...+ is %d", sum);
}
执行如下命令编译sum.c(加-g选项产生debug信息): gcc –g –o sum sum.c
在命令行上键入gdb sum并按回车键就可以开始调试sum了,再运行run命令执行sum,屏幕上将看到如下内容:
list命令:list命令用于列出源代
这个时候我们再run,程序会停止在第5行,如下图:
设置断点的另一种语法是 break <function>,它在进入指定函数(function)时停住。

clear 用于清除所有的已定义的断点
   clear lineNo
   clear functionName
delete
   delete lineNo
   delete breakPointNum

watch命令:
   watch命令用于观查变量或表达式的值,我们观查sum变量只需要运行watch sum

watch <expr>为表达式(变量)expr设置一个观察点,一量表达式值有变化时,程序会停止执行。

要观查当前设置的watch,可以使用info watchpoints命令。

next、step命令:
next、step用于单步执行,在执行的过程中,被watch变量的变化情况将实时呈现(分别显示Old value和New value),如下图:

next、step命令的区别在于step遇到函数调用,会跳转到到该函数定义的开始行去执行,而next则不进入到函数内部,它把函数调用语句当作

4、Make

make 是所有想在 Linux 系统上编程的用户必须掌握的工具,对于任何稍具规模的程序,
我们都会使用到make,几乎可以说不使用make的程序不具备任何实用价值。

在此,我们有必要解释编译和连接的区别。编译器使用源码文件来产生某种形式的目标文件(object files),在编译过程中,外部的符号参考

并没有被解释或替换(即外部全局变量和函数并没有被找到)。因此,在编译阶段所报的错误一般都是语法错误。而连接器则用于连接目标文

件和程序包,生成一个可执行程序。在连接阶段,一个目标文件中对别的文件中的符号的参考被解释,
如果有符号不能找到,会报告连接错误

编译和连接的一般步骤是:第一阶段把源文件一个一个的编译成目标文件,
第二阶段把所有的目标文件加上需要的程序包连接成一个可执行文件。
这样的过程很痛苦,我们需要使用大量的 gcc 命令。

而 make 则使我们从大量源文件的编译和连接工作中解放出来,综合为一步完成。
GNU Make 的主要工作是读进一个文本文件,称为 makefile。

这个文件记录了哪些文件(目的文件,目的文件不一定是最后的可执行程序,它可以是任何一种文件)由哪些文件(依靠文件)产生,用什么命令来产生。
Make依靠此makefile中的信息检查磁盘上的文件,如果目的文件的创建或修改时间比它的一个依靠文件旧的话,make就执行相应的命令,以便更新目的文件。

假设我们写下如下的三个文件,add.h用于声明add函数,add.c提供两个整数相加的函数体,而main.c中调用add函数:

`/* filename:add.h */
extern int add(int i, int j);`

`/* filename:add.c */
int add(int i, int j)
{
  return i + j;
};`

`/* filename:main.c */
#include "add.h"
main()
{
  int a, b;
  a = 2;
  b = 3;
  printf("the sum of a+b is %d", add(a + b));
};`


怎样为上述三个文件编写 makefile 呢?如下:
-------------------------
test : main.o add.o
gcc main.o add.o -o test

main.o : main.c add.h
gcc -c main.c -o main.o

add.o : add.c add.h
gcc -c add.c -o add.o
-----------------------
上述 makefile 利用 add.c 和 add. h文件执行 gcc -c add.c -o add.o 命令产生add.o目标代码,利用main.c和add.h文件执行gcc -c main.c -o

main.o 命令产生 main.o 目标代码,最后利用 main.o 和 add.o 文件(两个模块的目标代码)执行gcc main.o add.o -o test命令产生可执行文件 test。

我们可在 makefile 中加入变量,另外。
环境变量在make过程中也被解释成make的变量。这些变量是大小写敏感的,一般使用大写字母。

Make变量可以做很多事情,例如:
i) 存储一个文件名列表;
ii) 存储可执行文件名;
iii) 存储编译器选项。

要定义一个变量,只需要在一行的开始写下这个变量的名字,后面跟一个=号,再跟变量的值。
引用变量的方法是写一个$符号,后面跟(变量名)。
我们把前面的 makefile 利用变量重写一遍(并假设使用-Wall -O –g编译选项):
`
OBJS = main.o add.o
CC = gcc
CFLAGS = -Wall -O -g
test : $(OBJS)
$(CC) $(OBJS) -o test
main.o : main.c add.h
$(CC) $(CFLAGS) -c main.c -o main.o
add.o : add.c add.h
$(CC) $(CFLAGS) -c add.c -o add.o
`
makefile 中还可定义清除(clean)目标,可用来清除编译过程中产生的中间文件,例如在上述makefile文件中添加下列代码:
`
clean:
    rm -f *.o
`
运行 make clean 时,将执行rm -f *.o命令,删除所有编译过程中产生的中间文件。
不管怎么说,
自己动手编写 makefile 仍然是很复杂和烦琐的,而且很容易出错。
因此,GNU 也为我们提供了 Automake 和 Autoconf 来辅助快速自动产生makefile,读者可以参阅相关资料。

5、小结

本章主要阐述了 Linux 程序的编写、编译、调试方法及 make,
实际上就是引导读者学习怎样在 Linux 下编程,为后续章节做好准备

二、Linux下的C编程实战(二)

文件系统编程

1、Linux文件系统

Linux 支持多种文件系统,如 ext、ext2、minix、iso9660、msdos、fat、vfat、nfs等。
在这些具体文件系统的上层,Linux提供了虚拟文件系统(VFS)来统一它们的行为,虚拟文件系统为不同的文件系统与内核的通信提供了一致的接口。
下图给出了 Linux 中文件系统的关系(缺失)
在 Linux 平台下对文件编程可以使用两类函数:
(1)Linux操作系统文件API;
(2)C语言I/O库函数。
前者依赖于Linux系统调用,后者实际上与操作系统是独立的,因为在任何操作系统下,
使用C语言I/O库函数操作文件的方法都是相同的。
本章将对这两种方法进行实例讲解。

2、Linux文件API

Linux的文件操作API涉及到创建、打开、读写和关闭文件。
创建: int creat(const char *filename, mode_t mode);

参数 mode 指定新建文件的存取权限,它同 umask 一起决定文件的最终权限(mode&umask),
其中 umask 代表了文件在创建时需要去掉的一些存取权限。umask可通过系统调用umask()来改变:int umask(int newmask);
该调用将umask设置为newmask,然后返回旧的umask,它只影响读、写和执行权限。

打开:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
open函数有两个形式,其中pathname是我们要打开的文件名(包含路径名称,缺省是认为在当前路径下面),
flags可以去下面的一个值或者是几个值的组合:
------------------------------------------------------
标志         含义
O_RDONLY  以只读的方式打开文件
O_WRONLY  以只写的方式打开文件
O_RDWR    以读写的方式打开文件
O_APPEND  以追加的方式打开文件
O_CREAT   创建一个文件
O_EXEC    如果使用了O_CREAT而且文件已经存在,就会发生一个错误
O_NOBLOCK 以非阻塞的方式打开一个文件
O_TRUNC   如果文件已经存在,则删除文件的内容

O_RDONLY、O_WRONLY、O_RDWR三个标志只能使用任意的一个。

如果使用了 O_CREATE 标志,则使用的函数是
int open(const char *pathname,int flags,mode_t mode);
这个时候我们还要指定mode标志,用来表示文件的访问权限。mode可以是以下情况的组合:
------------------------------------------------------
标志            含义
S_IRUSR      用户可以读
S_IWUSR      用户可以写
S_IXUSR      用户可以执行
S_IRWXU      用户可以读、写、执行
S_IRGRP      组可以读
S_IWGRP      组可以写
S_IXGRP      组可以执行
S_IRWXG      组可以读写执行
S_IROTH      其他人可以读
S_IWOTH      其他人可以写
S_IXOTH      其他人可以执行
S_IRWXO      其他人可以读、写、执行
S_ISUID      设置用户执行ID
S_ISGID      设置组的执行ID
------------------------------------------------------
除了可以通过上述宏进行`或`逻辑产生标志以外,我们也可以自己用数字来表示,Linux 总共用 5 个数字来表示文件的各种权限:
第一位表示设置用户ID;
第二位表示设置组ID;
第三位表示用户自己的权限位;
第四位表示组的权限;
最后一位表示其他人的权限。

每个数字可以取1(执行权限)、2(写权限)、4(读权限)、0(无)或者是这些值的和。
例如,要创建一个用户可读、可写、可执行,但是组没有权限,其他人可以读、
可以执行的文件,并设置用户ID位。那么,我们应该使用的模式是1(设置用户ID)、0(不设置组ID)、
7(1+2+4,读、写、执行)、0(没有权限)、5(1+4,读、执行)即10705:
open("test", O_CREAT, 10705);
上述语句等价于: open("test", O_CREAT, S_IRWXU | S_IROTH | S_IXOTH | S_ISUID );

如果文件打开成功,open 函数会返回一个文件描述符,以后对该文件的所有操作就可以通过对这个文件描述符进行操作来实现。

读写:
在文件打开以后,我们才可对文件进行读写了,Linux 中提供文件读写的系统调用是read、write函数:
int read (int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);

其中参数 buf 为指向缓冲区的指针,length 为缓冲区的大小(以字节为单位)。
函数 read() 实现从文件描述符fd所指定的文件中读取 length 个字节到buf所指向的缓冲区中,返回值为实际读取的字节数。
函数 write 实现将把 length 个字节从 buf 指向的缓冲区中写到文件描述符fd所指向的文件中,返回值为实际写入的字节数。

以O_CREAT为标志的open实际上实现了文件创建的功能,因此,下面的函数等同creat()函数:
int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);

定位:
对于随机文件,我们可以随机的指定位置读写,使用如下函数进行定位:
int lseek(int fd, offset_t offset, int whence);

lseek() 将文件读写指针相对 whence 移动 offset 个字节。
操作成功时,返回文件指针相对于文件头的位置。

参数whence可使用下述值:
SEEK_SET:相对文件开头
SEEK_CUR:相对文件读写指针的当前位置
SEEK_END:相对文件末尾

offset可取负值,例如下述调用可将文件指针相对当前位置向前移动5个字节:
lseek(fd, -5, SEEK_CUR);

由于lseek函数的返回值为文件指针相对于文件头的位置,因此下列调用的返回值就是文件的长度:
lseek(fd, 0, SEEK_END);

关闭
当我们操作完成以后,我们要关闭文件了,只要调用close就可以了,其中fd是我们要关闭的文件描述符:
int close(int fd);

例程:
编写一个程序,在当前目录下创建用户可读写文件“hello.txt”,在其中写入“Hello, software weekly”,关闭该文件。
再次打开该文件,读取其中的内容并输出在屏幕上。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#define LENGTH 100

void main()
{
  int fd, len;
  char str[LENGTH];
  /* 创建并打开文件 */
  fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
  if (fd)
  {
      /* 写入Hello, software weekly字符串 */
      write(fd, "Hello, Software Weekly", strlen("Hello, software weekly"));
      close(fd);
  }
  fd = open("hello.txt", O_RDWR);
  len = read(fd, str, LENGTH); /* 读取文件内容 */
  str[len] = '\0';
  printf("%s\n", str);
  close(fd);
};

编译并运行,执行

3、C 语言库函数
C库函数的文件操作实际上是独立于具体的操作系统平台的,不管是在DOS、Windows、Linux还是在VxWorks中都是这些函数:

创建和打开:
FILE *fopen(const char *path, const char *mode);

fopen()实现打开指定文件filename,其中的mode为打开模式,C语言中支持的打开模式如下表:
------------------------------------------------------
标志             含义
r, rb         以只读方式打开
w, wb         以只写方式打开。如果文件不存在,则创建该文件,否则文件被截断
a, ab         以追加方式打开。如果文件不存在,则创建该文件
r+, r+b, rb+  以读写方式打开
w+, w+b, wh+  以读写方式打开。如果文件不存在时,创建新文件,否则文件被截断
a+, a+b, ab+  以读和追加方式打开。如果文件不存在,创建新文件

其中b用于区分二进制文件和文本文件,这一点在DOS、Windows系统中是有区分的,但Linux不区分二进制文件和文本文件。

读写
C库函数支持以字符、字符串等为单位,支持按照某中格式进行文件的读写,这一组函数为:
int    fgetc(FILE *stream);
int    fputc(int c, FILE *stream);
char  *fgets(char *s, int n, FILE *stream);
int    fputs(const char *s, FILE *stream);
int    fprintf(FILE *stream, const char *format, ...);
int    fscanf (FILE *stream, const char *format, ...);
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
size_t fwrite (const void *ptr, size_t size, size_t n, FILE *stream);

fread()实现从流stream中读取加n个字段,每个字段为size字节,并将读取的字段放入ptr所指的字符数组中,返回实际已读取的字段数。
在读取的字段数小于num时,可能是在函数调用时出现错误,也可能是读到文件的结尾。
所以要通过调用feof()和ferror()来判断。
write() 实现从缓冲区ptr所指的数组中把n个字段写到流stream中,每个字段长为size个字节,返回实际写入的字段数。
另外,C库函数还提供了读写过程中的定位能力,这些函数包括
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
int fseek(FILE *stream, long offset, int whence);
等。

关闭
利用C库函数关闭文件依然是很简单的操作:
int fclose (FILE *stream);

例程:将第2节中的例程用C库函数来实现。
#include <stdio.h>
#define LENGTH 100
void main()
{
  FILE *fd;
  char str[LENGTH] = {0};
  fd = fopen("hello.txt", "w+"); /* 创建并打开文件 */
  if (fd)
  {
     /* 写入Hello, software weekly字符串 */
     fputs("Hello, Software Weekly", fd);
     fclose(fd);
  }
  fd = fopen("hello.txt", "r");
  fgets(str, LENGTH, fd); /* 读取文件内容 */
  printf("%s\n", str);
  fclose(fd);
}

4、小结

Linux提供的虚拟文件系统为多种文件系统提供了统一的接口,Linux的文件编程有两种途径:
   基于Linux系统调用;
   基于C库函数。
这两种编程所涉及到文件操作有新建、打开、读写和关闭,对随机文件还可以定位。
本章对这两种编程方法都给出了具体的实例。

Linux下的C编程实战(三)

1、进程控制与进程通信编程

1、Linux进程

Linux进程在内存中包含三部分数据:代码段、堆栈段和数据段。
代码段存放了程序的代码。代码段可以为机器中运行同一程序的数个进程共享。
堆栈段存放的是子程序(函数)的返回地址、子程序的参数及程序的局部变量。
而数据段则存放程序的全局变量、常数以及动态数据分配的数据空间(比如用malloc函数申请的内存)。
与代码段不同,如果系统中同时运行多个相同的程序,它们不能使用同一堆栈段和数据段。

Linux进程主要有如下几种状态:
用户状态(进程在用户状态下运行的状态)、内核状态(进程在内核状态下运行的状态)、内存中就绪(进程没有执行,但处于就绪状态,只要内核调度它,就可以执行)、
内存中睡眠(进程正在睡眠并且处于内存中,没有被交换到SWAP设备)、就绪且换出(进程处于就绪状态,但是必须把它换入内存,内核才能再次调度它进行运行)、
睡眠且换出(进程正在睡眠,且被换出内存)、被抢先(进程从内核状态返回用户状态时,内核抢先于它,做了上下文切换,调度了另一个进程,
原先这个进程就处于被抢先状态)、创建状态(进程刚被创建,该进程存在,但既不是就绪状态,也不是睡眠状态,
这个状态是除了进程0以外的所有进程的最初状态)、僵死状态(进程调用exit结束,进程不再存在,但在进程表项中仍有记录,该记录可由父进程收集)。

下面我们来以一个进程从创建到消亡的过程讲解Linux进程状态转换的“生死因果”。
(1)进程被父进程通过系统调用fork创建而处于创建态;
(2)fork调用为子进程配置好内核数据结构和子进程私有数据结构后,子进程进入就绪态(或者在内存中就绪,或者因为内存不够而在SWAP设备中就绪);
(3)若进程在内存中就绪,进程可以被内核调度程序调度到CPU运行;
(4)内核调度该进程进入内核状态,再由内核状态返回用户状态执行。该进程在用户状态运行一定时间后,又会被调度程序所调度而进入内核状态,由此转入就绪态。
有时进程在用户状态运行时,也会因为需要内核服务,使用系统调用而进入内核状态,服务完毕,会由内核状态转回
用户状态。要注意的是,进程在从内核状态向用户状态返回时可能被抢占,这是由于有优先级更高的进程急需使用CPU,
不能等到下一次调度时机,从而造成抢占;
(5)进程执行exit调用,进入僵死状态,最终结束。

2、进程控制

进程控制中主要涉及到进程的创建、睡眠和退出等,在Linux中主要提供了fork、exec、clone的进程创建方法,sleep的进程睡眠和exit的进程
退出调用,另外Linux还提供了父进程等待子进程结束的系统调用wait。

fork
对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一,它执行一次却返回两个值,完全“不可思议”。先看下面的程序
// fork 不需要依赖其它头文件
#include <stdio.h>
int main()
{
    int i = 0;
    if (fork() == 0)
    {
        for (i=1; i<=2; i++)
        {
            printf("this is child process\n");
        }
    }
    else
    {
        for (i=1; i<=2; i++)
        {
            printf("this is child process\n");
        }
    }
    return 0;
}
执行结果为:
This is child process
This is child process
This is parent process
This is parent process

fork在英文中是“分叉”的意思,这个名字取得很形象。一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就“分叉”了。
当前进程为父进程,通过fork()会产生一个子进程。对于父进程,fork函数返回子程序的进程号而对于子程序,fork函数则返回零,这就是
一个函数返回两次的本质。可以说,fork函数是Unix系统最杰出的成就之一,它是七十年代Unix早期的开发者经过理论和实践上的长期艰苦探索后取得的成果。

如果我们把上述程序中的循环放的大一点:
int main()
{
  int i;
  if (fork() == 0)
  {
    for (i = 1; i < 10000; i++)
      printf("This is child process\n");
  }
  else
  {
    for (i = 1; i < 10000; i++)
      printf("This is parent process\n");
  }
};

则可以明显地看到父进程和子进程的并发执行,交替地输出“This is child process”和“This is parent process”,事实上将其改为30时,就能够开始看到有交替打印。

此时此刻,我们还没有完全理解fork()函数,再来看下面的一段程序,看看究竟会产生多少个进程,程序的输出是什么?
[root@localhost ssss]# cat multiFork.c
int main()
{
  int i;
  int j=0;
  for (i = 0; i < 2; i++)
  {
    if (fork() == 0)
    {
      printf("This is child process:%d\n", j++);
    }
    else
    {
      printf("This is parent process:%d\n", j++);
    }
  }
  return 0;
};
[root@localhost ssss]# ./a.out
This is parent process:0
This is parent process:1
This is child process:1
This is child process:0
This is parent process:1
This is child process:1
[root@localhost ssss]#

exec

在Linux中可使用exec函数族,包含多个函数(execl、execlp、execle、execv、execve和execvp),被用于启动一个指定路径和文件名的进程。
exec 函数族的特点体现在:某进程一旦调用了 exec 类函数,正在执行的程序就被干掉了,系统把代码段替换成新的程序(由exec类函数执行)的代码,
并且原有的数据段和堆栈段也被废弃,新的数据段与堆栈段被分配,但是进程号却被保留。也就是说,exec执行的结果为:
系统认为正在执行的还是原先的进程,但是进程对应的程序被替换了。

fork

函数可以创建一个子进程而当前进程不死,如果我们在 fork 的子进程中调用exec函数族就可以实现既让父进程的代码执行又启动一个新的
指定进程,这实在是很妙的。fork 和 exec 的搭配巧妙地解决了程序启动另一程序的执行但自己仍继续运行的问题,请看下面的例子:
[root@localhost ssss]# cat execlp_tst.c 
#include <stdio.h>
#define MAX_CMD_LEN 512
char command[MAX_CMD_LEN];
void main()
{
  int rtn; /* 子进程的返回数值 */
  int errorno = 1024;
  while (1)
  {
    /* 从终端读取要执行的命令 */
    printf("# ");
    fgets(command, MAX_CMD_LEN, stdin);
    command[strlen(command) - 1] = 0;
    if (fork() == 0)
    {
      printf("now is executing : %s\n", command);
      /* 子进程执行此命令 */
      //execlp(command, command);
      execlp("./aa", command);
      /* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/
      perror(command);
      exit(errorno);
    }
    else
    {
      /* 父进程,等待子进程结束,并打印子进程的返回值 */
      wait(&rtn);
      printf(" child process return %d\n", rtn);
    }
  }
}

这个函数基本上实现了一个 shell 的功能,它读取用户输入的进程名和参数,并启动对应的进程。

clone

clone 是 Linux2.0 以后才具备的新功能,它较 fork 更强(可认为 fork 是 clone 要实现的一部分),可以使得创建的子进程共享父进程的资源,
并且要使用此函数必须在编译内核时设置clone_actually_works_ok选项。
clone函数的原型为:
    int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

此函数返回创建进程的PID,函数中的flags标志用于设置创建子进程时的相关选项,具体含义如下表:
=====================================================================================
标志                             含义
CLONE_PARENT             创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
CLONE_FS                 子进程与父进程共享相同的文件系统,包括root、当前目录、umask
CLONE_FILES              子进程与父进程共享相同的文件描述符(file descriptor)表
CLONE_NEWNS              在新的namespace启动子进程,namespace描述了进程的文件hierarchy
CLONE_SIGHAND            子进程与父进程共享相同的信号处理(signal handler)表
CLONE_PTRACE             若父进程被trace,子进程也被trace
CLONE_VFORK              父进程被挂起,直至子进程释放虚拟内存资源
CLONE_VM                 子进程与父进程运行于相同的内存空间
CLONE_PID                子进程在创建时PID与父进程一致
CLONE_THREAD             Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

来看下面的例子:
#include <stdio.h>
#include <linux/sched.h>
#include <fcntl.h>
#include <unistd.h>

int variable, fd;
int do_something() {
   variable = 42;
   close(fd);
   printf("in new function cloned...\n");
   _exit(0);
}

int main(int argc, char *argv[])
{
   void **child_stack;
   char   tempch;

   variable = 9;
   fd = open("aa", O_RDONLY);
   child_stack = (void **) malloc(16384);
   printf("The variable was %d\n", variable);
   clone(do_something, child_stack, CLONE_VM|CLONE_FILES, NULL);
   sleep(3);   /* 延时以便子进程完成关闭文件操作、修改变量 */
   printf("The variable is now %d\n", variable);
   if (read(fd, &tempch, 1) < 1) {
      perror("File Read Error");
      exit(1);
   }
   printf("We could read from the file\n");
   return 0;
}

运行输出,很奇怪:
[root@localhost ssss]# gcc -g cloneTest.c

直接运行,崩溃,因为在 do_something 函数退出时崩溃了,没有打印,好像没有进入 do_something
[root@localhost ssss]# ./a.out
The variable was 9
段错误
[root@localhost ssss]#

采用 gdb 调试,却表现出不同的行为:
从截图上看,显然它进入到了 do_something 函数,而且输出了调试打印机,但遇到 exit(0) 后,直接崩溃


程序到的 CLONE_VM、CLONE_FILES 标志将使得变量和文件描述符表被共享),
父进程随即就感觉到了,这就是clone的特点。

sleep

函数调用sleep可以用来使进程挂起指定的秒数,该函数的原型为:
unsigned int sleep(unsigned int seconds);

该函数调用使得进程挂起一个指定的时间,如果指定挂起的时间到了,该调用返回0;
如果该函数调用被信号所打断,则返回剩余挂起的时间数(指定的时间减去已经挂起的时间)。

exit

系统调用exit的功能是终止本进程,其函数原型为:
void _exit(int status);
_exit会立即终止发出调用的进程,所有属于该进程的文件描述符都关闭。
参数status作为退出的状态值返回父进程,在父进程中通过系统调用

wait。
wait 可获得 void _exit(int status) 所返回的 status 值
wait系统调用包括:
    pid_t wait(int *status);
    pid_t waitpid(pid_t pid, int *status, int options);

wait 的作用为发出调用的进程只要有子进程,就睡眠到它们中的一个终止为止; waitpid 等待由参数 pid 指定的子进程退出。

3、进程间通信

Linux 的进程间通信(IPC,InterProcess Communication)
通信方法有 管道、消息队列、共享内存、信号量、套接口等。
管道分为有名管道和无名管道,无名管道只能用于亲属进程之间的通信,而有名管道则可用于无亲属关系的进程之间。

#include <stdio.h>
#include <linux/sched.h>
#include <fcntl.h>
#include <unistd.h>

#define INPUT 0
#define OUTPUT 1

#define BUFFER_LEN 1024
void main()
{
  int file_descriptors[2];
  /*定义子进程号 */
  pid_t pid;
  char buf[BUFFER_LEN];
  int returned_count;
  /*创建无名管道*/
  pipe(file_descriptors);
  /*创建子进程*/
  if ((pid = fork()) ==  -1)
  {
    printf("Error in fork\n");
    exit(1);
  }

  /*执行子进程*/
  if (pid == 0)
  {
    printf("in the spawned (child) process...\n");
    /*子进程向父进程写数据,关闭管道的读端*/
    close(file_descriptors[INPUT]);
    write(file_descriptors[OUTPUT], "test data", strlen("test data"));
    exit(0);
  }
  else
  {
    /*执行父进程*/
    printf("in the spawning (parent) process...\n");
    /*父进程从管道读取子进程写的数据,关闭管道的写端*/
    close(file_descriptors[OUTPUT]);
    returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
    printf("%d bytes of data received from spawned process: %s\n", returned_count, buf);
  }
}
[root@localhost ssss]# ./a.out
in the spawning (parent) process...
in the spawned (child) process...
9 bytes of data received from spawned process: test data
[root@localhost ssss]#


上述程序中,无名管道以 int pipe(int filedis[2]);
方式定义,参数 filedis 返回两个文件描述符filedes[0]为读而打开,
filedes[1]为写而打开,filedes[1]的输出是filedes[0]的输入;

在Linux系统下,有名管道可由两种方式创建(假设创建一个名为“fifoexample”的有名管道):
(1)mkfifo("fifoexample","rw");
(2)mknod fifoexample p
mkfifo 是一个函数,mknod 是一个系统调用,即我们可以在shell下输出上述命令。
有名管道创建后,我们可以像读写文件一样读写之:
[root@localhost ssss]# cat writePipe.c
#include <stdio.h>
#include <linux/sched.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFFER_LEN 128

/* 进程二:写有名管道*/
void main()
{
  FILE *out_file;
  int   count = 1;
  char  buf[BUFFER_LEN];

  out_file = fopen("pipeexample", "w");
  if (out_file == NULL)
  {
    printf("Error opening pipe.");
    exit(1);
  }
  sprintf(buf, "this is test data for the named pipe example\n");
  fwrite(buf, 1, BUFFER_LEN, out_file);
  fclose(out_file);
}

[root@localhost ssss]# cat readPipe.c
#include <stdio.h>
#include <linux/sched.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFFER_LEN 128

/* 进程一:读有名管道*/
void main()
{
  FILE *in_file;
  int count = 1;
  char buf[BUFFER_LEN];
  in_file = fopen("pipeexample", "r");
  if (in_file == NULL)
  {
    printf("Error in fdopen.\n");
    exit(1);
  }

  while ((count = fread(buf, 1, BUFFER_LEN, in_file)) > 0)
  {
    printf("received from pipe: %s\n", buf);
  }
  fclose(in_file);
}

[root@localhost ssss]# ./writePipe
[root@localhost ssss]# ./readPipe
received from pipe: this is test data for the named pipe example

消息队列用

消息队列用于运行于同一台机器上的进程间通信,与管道相似;
共享内存通常由一个进程创建,其余进程对这块内存区进行读写。
得到共享内存有两种方式:映射/dev/mem设备 和 内存映像文件。
前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的是实际的物理内存;常用的方式是通过shmXXX函数族来实现共享内存:
int shmget(key_t key, int size, int flag); /* 获得一个共享存储标识符 */

该函数使得系统分配size大小的内存用作共享内存;
void *shmat(int shmid, void *addr, int flag); /* 将共享内存连接到自身地址空间中*/

shmid 为 shmget 函数返回的共享存储标识符,addr 和 flag 参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址。
此后,进程可以对此地址进行读写操作访问共享内存。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。
一般说来,为了获得共享资源,进程需要执行下列操作:
(1)测试控制该资源的信号量;
(2)若此信号量的值为正,则允许进行使用该资源,进程将进号量减1;
(3)若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1);
(4)当进程不再使用一个信号量控制的资源时,信号量值加1,如果此时有进程正在睡眠等待此信号量,则唤醒此进程。

下面是一个使用信号量的例子,该程序创建一个特定的 IPC 结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后清除信号量:

#include <stdio.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>

#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
  /* union semun is defined by including <sys/sem.h> */
#else
  /* according to X/OPEN we have to define it ourselves */
  union semun {
        int val;                  /* value for SETVAL */
        struct semid_ds *buf;     /* buffer for IPC_STAT, IPC_SET */
        unsigned short *array;    /* array for GETALL, SETALL */
                                  /* Linux specific part: */
        struct seminfo *__buf;    /* buffer for IPC_INFO */
  };
#endif
void main()
{
  key_t  unique_key; /* 定义一个IPC关键字*/
  int    id;
  struct sembuf lock_it;
  union  semun  options;
  int    i;

  unique_key = ftok(".", 'a'); /* 生成关键字,字符'a'是一个随机种子*/
  /* 创建一个新的信号量集合*/
  id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
  printf("semaphore id=%d\n", id);
  options.val = 1; /*设置变量值*/
  semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/

 /*打印出信号量的值*/
  i = semctl(id, 0, GETVAL, 0);
  printf("value of semaphore at index 0 is %d\n", i);

  /*下面重新设置信号量*/
  lock_it.sem_num = 0;          /*设置哪个信号量*/
  lock_it.sem_op  = - 1;        /*定义操作*/
  lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/
  if (semop(id, &lock_it, 1) == - 1)
  {
    printf("can not lock semaphore.\n");
    exit(1);
  }

  i = semctl(id, 0, GETVAL, 0);
  printf("value of semaphore at index 0 is %d\n", i);

  /*清除信号量*/
  semctl(id, 0, IPC_RMID, 0);
}
[root@localhost ssss]# ./semTest
semaphore id=131072
value of semaphore at index 0 is 1  ==>> 表明此时有一个信号量可以获取
value of semaphore at index 0 is 0  ==>> 表明此时已经没有信号量可用
[root@localhost ssss]#

套接字
套接字通信并不为Linux所专有,在所有提供了TCP/IP协议栈的操作系统中几乎都提供了socket,而所有这样操作系统,对套接字的编程方法几

乎是完全一样的。

4.小节

本章讲述了 Linux 进程的概念,并以多个实例讲解了进程控制及进程间通信方法,理解这一章的内容可以说是理解 Linux 这个操作系统的关键。

Linux下的C编程实战(四)

“线程” 控制与 “线程” 通信编程

1、Linux “线程”

笔者曾经在《基于嵌入式操作系统VxWorks的多任务并发程序设计》(《软件报》2006年第5~12期)中详细叙述了进程和线程的区别,
并曾经说明 Linux 是一种 "多进程单线程" 的操作系统。
Linux本身只有进程的概念,而其所谓的 "线程" 本质上在内核里仍然是进程。
大家知道,进程是资源分配的单位,同一进程中的多个线程共享该进程的资源(如作为共享内存的全局变量)。
Linux 中所谓的"线程"只是在被创建的时候"克隆"(clone)了父进程的资源,因此,clone出来的进程表现为"线程",这一点一定要弄清楚。
因此,Linux "线程"这个概念只有在打引号的情况下才是最准确的,可惜的是几乎没有书籍留心去强调这一点。
  Linux内核只提供了轻量进程的支持,未实现线程模型,但Linux尽最大努力优化了进程的调度开销,这在一定程度上弥补无线程的缺陷。
  Linux用一个核心进程(轻量进程)对应一个线程,将线程调度等同于进程调度,交给核心完成。
目前Linux中最流行的线程机制为 LinuxThreads,所采用的就是线程-进程"一对一"模型,调度交给核心,
而在用户级实现一个包括信号处理在内的线程管理机制。LinuxThreads 由 Xavier Leroy (Xavier.Leroy@inria.fr)负责开发完成,
并已绑定在GLIBC中发行,它实现了一种 BiCapitalized 面向 Linux 的 Posix 1003.1c "pthread" 标准接口。
Linuxthread 可以支持 Intel、Alpha、MIPS 等平台上的多处理器系统。

按照POSIX 1003.1c 标准编写的程序与 Linuxthread 库相链接即可支持Linux平台上的多线程,
在程序中需包含头文件 pthread. h,在编译链接时使用命令:
gcc -D -REENTRANT -lpthread xxx. c

其中 '-REENTRANT' 宏使得相关库函数(如stdio.h、errno.h中函数) 是可重入的、线程安全的(thread-safe),
-lpthread 则意味着链接库目录下的 libpthread.a 或 libpthread.so 文件。
使用Linuxthread库需要2.0以上版本的Linux内核及相应版本的C库(libc 5.2.18、libc 5.4.12、libc 6)。

2、”线程” 控制

线程创建

进程被创建时,系统会为其创建一个主线程,而要在进程中创建新的线程,则可以调用pthread_create:
pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
  (start_routine)(void*), void *arg);

start_routine为新线程的入口函数,arg为传递给start_routine的参数。

每个线程都有自己的线程ID,以便在进程内区分。线程 ID 在pthread_create调用时回返给创建线程的调用者;
一个线程也可以在创建后使用 pthread_self() 调用获取自己的线程ID:
pthread_self (void) ;

线程退出

线程的退出方式有三:
(1)执行完成后隐式退出;
(2)由线程本身显示调用pthread_exit 函数退出;
pthread_exit (void * retval) ;
(3)被其他线程用pthread_cance函数终止:
pthread_cance (pthread_t thread) ;

在某线程中调用此函数,可以终止由参数thread 指定的线程。
如果一个线程要等待另一个线程的终止,可以使用 pthread_join 函数,该函数的作用是调用pthread_join的线程将被挂起直到线程ID为参数 thread 的线程终止:
pthread_join (pthread_t thread, void** threadreturn);

3、线程通信

线程互斥

互斥意味着 "排它",即两个线程不能同时进入被互斥保护的代码。
   Linux下可以通过 pthread_mutex_t 定义互斥体机制完成多线程的互斥操作,该机制的作用是对某个需要互斥的部分,
在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体的线程阻塞,
直到拥有该互斥体的线程完成互斥部分的操作为止。

下面的代码实现了对共享全局变量 x 用互斥体 mutex 进行保护的目的:

int x; // 进程中的全局变量
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); //按缺省的属性初始化互斥体变量mutex
pthread_mutex_lock(&mutex); // 给互斥体变量加锁
… //对变量x 的操作
phtread_mutex_unlock(&mutex); // 给互斥体变量解除锁

线程同步

同步就是线程等待某个事件的发生。只有当等待的事件发生线程才继续执行,否则线程挂起并放弃处理器。
当多个线程协作时,相互作用的任务必须在一定的条件下同步。

Linux 下的 C 语言编程有多种线程同步机制,最典型的是条件变量'condition variable'。
pthread_cond_init 用来创建一个条件变量,其函数原型为:
pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);

pthread_cond_wait 和 pthread_cond_timedwait 用来等待条件变量被设置,值得注意的是这两个等待调用需要一个已经上锁的互斥体 mutex,
这是为了防止在真正进入等待状态之前别的线程有可能设置该条件变量而产生竞争。pthread_cond_wait 的函数原型为:
pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_broadcast 用于设置条件变量,即使得事件发生,这样等待该事件的线程将不再阻塞:
pthread_cond_broadcast (pthread_cond_t *cond) ;

pthread_cond_signal 则用于解除某一个等待线程的阻塞状态:
pthread_cond_signal (pthread_cond_t *cond) ;

pthread_cond_destroy 则用于释放一个条件变量的资源。

在头文件 semaphore.h 中定义的信号量则完成了互斥体和条件变量的封装,按照多线程程序设计中访问控制机制,
控制对资源的同步访问,提供程序设计人员更方便的调用接口。

sem_init(sem_t *sem, int pshared, unsigned int val);
这个函数初始化一个信号量 sem 的值为val,参数 pshared 是共享属性控制,表明是否在进程间共享。

sem_wait(sem_t *sem);
调用该函数时,若 sem 为无状态,调用线程阻塞,等待信号量sem值增加<post> 成为有信号状态;
若sem为有状态,调用线程顺序执行,但信号量的值减一。

sem_post(sem_t *sem);
调用该函数,信号量sem的值增加,可以从无信号状态变为有信号状态。

4、实例

下面我们还是以著名的生产者/消费者问题为例来阐述 Linux 线程的控制和通信。一组生产者线程与一组消费者线程通过缓冲区发生联系。
生产者线程将生产的产品送入缓冲区,消费者线程则从中取出产品。缓冲区有N 个,是一个环形的缓冲池。
[root@localhost ssss]# cat producer_consumer.c
#include <stdio.h>
#include <linux/sched.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#define BUFFER_SIZE 8 // 缓冲区数量

struct prodcons
{
  // 缓冲区相关数据结构
  int              buffer[BUFFER_SIZE]; /* 实际数据存放的数组*/
  pthread_mutex_t  lock;                /* 互斥体lock 用于对缓冲区的互斥操作 */
  int              readpos;             /* 读指针*/
  int              writepos;            /* 写指针*/
  pthread_cond_t  notempty;             /* 缓冲区非空的条件变量 */
  pthread_cond_t  notfull;              /* 缓冲区未满的条件变量 */
};

/* 初始化缓冲区结构 */
void init(struct prodcons *b)
{
  pthread_mutex_init(&b->lock, NULL);
  pthread_cond_init(&b->notempty, NULL);
  pthread_cond_init(&b->notfull, NULL);
  b->readpos = 0;
  b->writepos = 0;
}

/* 将产品放入缓冲区,这里是存入一个整数*/
void put(struct prodcons *b, int data)
{
  pthread_mutex_lock(&b->lock);
  /* 如果缓冲区已满*/
  if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)
  {
    /* 等待缓冲区释放出空间*/
    pthread_cond_wait(&b->notfull, &b->lock);
  }
  /* 写数据,并移动指针 */
  b->buffer[b->writepos] = data;
  b->writepos++;
  if (b->writepos >= BUFFER_SIZE)
  {
     b->writepos = 0;
  }
  /* 设置缓冲区非空的条件变量*/
  pthread_cond_signal(&b->notempty);
  pthread_mutex_unlock(&b->lock);
}

/* 从缓冲区中取出整数*/
int get(struct prodcons *b)
{
  int data;
  pthread_mutex_lock(&b->lock);
  /* 如果缓冲区是空的*/
  if (b->writepos == b->readpos)
  {
    /* 等待缓冲区被放入数据 */
    pthread_cond_wait(&b->notempty, &b->lock);
  }

  /* 读数据,移动读指针 */
  data = b->buffer[b->readpos];
  b->readpos++;
  /* 已经取完最后一个缓冲数据 */
  if (b->readpos >= BUFFER_SIZE)
  {
    /* 将缓冲区置空*/
    b->readpos = 0;
  }
  /* 设置缓冲区未满的条件变量*/
  pthread_cond_signal(&b->notfull);
  pthread_mutex_unlock(&b->lock);
  return data;
}
/* 测试:生产者线程将 1 到 MaxNum 的整数送入缓冲区,消费者线
程从缓冲区中获取整数,两者都打印信息*/

#define MaxNum 32
#define GAME_OVER ( - 1)
struct prodcons buffer;
void *producer(void *data)
{
  int n;
  for (n = 0; n < MaxNum; n++)
  {
    printf("%d --->\n", n);
    put(&buffer, n);
  }
  put(&buffer, GAME_OVER);
  return NULL;
}

void *consumer(void *data)
{
  int d;
  while (1)
  {
    d = get(&buffer);
    if (d == GAME_OVER)
      break;
    printf("--->%d \n", d);
  }
  return NULL;
}

int main(void)
{
  void      *retval;
  pthread_t  th_a;
  pthread_t  th_b;

  init(&buffer);
  /* 创建生产者和消费者线程*/
  pthread_create(&th_a, NULL, producer, 0);
  pthread_create(&th_b, NULL, consumer, 0);
  /* 等待两个线程结束*/
  pthread_join(th_a, &retval);
  pthread_join(th_b, &retval);
  return 0;
}
==========================
[root@localhost ssss]# gcc producer_consumer.c -lpthread -o producer_consumer
[root@localhost ssss]#
[root@localhost ssss]# ./producer_consumer
0 --->
1 --->
2 --->
3 --->
4 --->
5 --->
6 --->
7 --->
--->0
--->1
--->2
--->3
--->4
--->5
--->6
8 --->
9 --->
10 --->
11 --->
12 --->
13 --->
14 --->
--->7
--->8
--->9
--->10
--->11
--->12
--->13
15 --->
16 --->
17 --->
18 --->
19 --->
20 --->
21 --->
--->14
--->15
--->16
--->17
--->18
--->19
--->20
--->21
22 --->
23 --->
24 --->
25 --->
26 --->
27 --->
28 --->
29 --->
--->22
--->23
--->24
--->25
--->26
--->27
--->28
30 --->
31 --->
--->29
--->30
--->31
[root@localhost ssss]#

5.WIN32、VxWorks、Linux线程类比

目前为止,笔者已经创作了《基于嵌入式操作系统VxWorks的多任务并发程序设计》(《软件报》2006年5~12期连载)、
《深入浅出Win32多线程程序设计》(天极网技术专题)系列,我们来找出这两个系列文章与本文的共通点。
看待技术问题要瞄准其本质,不管是Linux、VxWorks还是WIN32,其涉及到多线程的部分都是那些内容,无非就是线程控制和线程通信,
它们的许多函数只是名称不同,其实质含义是等价的,下面我们来列个三大操作系统共同点详细表单:

事项           WIN32          VxWorks          Linux
线程创建   CreateThread      taskSpawn      pthread_create
线程终止  执行完成后退出;线程自身调用ExitThread 函数即终止自己;被其他线程调用函数TerminateThread函数
执行完成后退出;由线程本身调用exit退出;被其他线程调用函数taskDelete终止
执行完成后退出;由线程本身调用pthread_exit 退出;被其他线程调用函数pthread_cance终止
获取线程ID  GetCurrentThreadId  taskIdSelf   pthread_self
创建互斥    CreateMutex         semMCreate   pthread_mutex_init
获取互斥  WaitForSingleObject、 semTake      pthread_mutex_lock
         WaitForMultipleObjects
释放互斥    ReleaseMutex        semGive      phtread_mutex_unlock
创建信号量  CreateSemaphore    semBCreate、  sem_init
                              semCCreate
等待信号量 WaitForSingleObject semTake       sem_wait
释放信号量 ReleaseSemaphore    semGive       sem_post

6、小结

本章讲述了 Linux 下多线程的控制及线程间通信编程方法,给出了一个生产者/消费者的实例,并将Linux的多线程
  与WIN32、VxWorks多线程进行了类比,总结了一般规律。鉴于多线程编程已成为开发并发应用程序的主流方法,学好本章的意义也便不言自明。

Linux下的C编程实战(五)

驱动程序设计

1、引言

设备驱动程序是操作系统内核和机器硬件之间的接口,它为应用程序屏蔽硬件的细节,一般来说,Linux的设备驱动程序需要完成如下功能:
(1)初始化设备;
(2)提供各类设备服务;
(3)负责内核和设备之间的数据交换;
(4)检测和处理设备工作过程中出现的错误。

妙不可言的是,Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得Windows的设备操作犹如文件一般。
在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。
本系列文章的第2章文件系统编程中已经看到了这些函数的真面目,它们就是 open ()、 close ()、 read ()、 write() 等。
Linux主要将设备分为二类:
   字符设备和块设备(当然网络设备及USB等其它设备的驱动编写方法又稍有不同)。

这两类设备的不同点在于:
   在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,
   而块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求
能满足用户的要求,就返回请求的数据,如果不能,
就调用请求函数来进行实际的I/O操作。

块设备主要针对磁盘等慢速设备。以字符设备的驱动较为简单,因此本章主要阐述字符设备的驱动编写。

2、驱动模块函数

init 函数用来完成对所控设备的初始化工作,并调用 register_chrdev() 函数注册字符设备。

假设有一字符设备"exampledev",则其 init 函数为:
void exampledev_init(void)
{
  if (register_chrdev(MAJOR_NUM, "exampledev", &exampledev_fops))
    TRACE_TXT("Device exampledev driver registered error");
  else
    TRACE_TXT("Device exampledev driver registered successfully");
  //设备初始化
}

其中, register_chrdev() 函数中的参数 MAJOR_NUM 为主设备号, "exampledev" 为设备名,exampledev_fops为包含基本函数入口点的结构体,
类型为 file_operations。当执行 exampledev_init 时,它将调用内核函数register_chrdev,
把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执行系统调用时提供入口地址。

file_operations 结构体定义为:
struct file_operations
{
  int (*lseek)();
  int (*read)();
  int (*write)();
  int (*readdir)();
  int (*select)();
  int (*ioctl)();
  int (*mmap)();
  int (*open)();
  void(*release)();
  int (*fsync)();
  int (*fasync)();
  int (*check_media_change)();
  void(*revalidate)();
};

大多数的驱动程序只是利用了其中的一部分,对于驱动程序中无需提供的功能,只需要把相应位置的值设为NULL。
对于字符设备来说,要提供的主要入口有:
   open ()、 release ()、 read ()、 write ()、 ioctl ()。

open()函数 对设备特殊文件进行open()系统调用时,将调用驱动程序的open () 函数:
   int open(struct inode * inode ,struct file * file);

其中参数 inode 为设备特殊文件的 inode (索引结点) 结构的指针,参数file是指向这一设备的文件结构的指针。
open() 的主要任务是确定硬件处在就绪状态、验证次设备号的合法性(次设备号可以用MINOR(inode-> i - rdev) 取得)、控制使用设备的进程数、根据执行情况返回状态码
(0表示成功,负数表示存在错误) 等;

release()函数 当最后一个打开设备的用户进程执行close ()系统调用时,内核将调用驱动程序的release () 函数:
void release (struct inode * inode ,struct file * file) ;
release() 函数 的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。

read()函数 当对设备特殊文件进行read() 系统调用时,将调用驱动程序read() 函数:
void read(struct inode * inode ,struct file * file ,char * buf ,int count) ;
参数buf是指向用户空间缓冲区的指针,由用户进程给出,count 为用户进程要求读取的字节数,也由用户给出。

read() 函数的功能就是从硬设备或内核内存中读取或复制count个字节到buf 指定的缓冲区中。
在复制数据时要注意,驱动程序运行在内核中,而 buf 指定的缓冲区在用户内存区中,是不能直接在内核中访问使用的,
因此,必须使用特殊的复制函数来完成复制工作,这些函数在<asm/segment.h>中定义:
void put_user_byte (char data_byte ,char * u_addr) ;
void put_user_word (short data_word ,short * u_addr) ;
void put_user_long(long data_long ,long * u_addr) ;
void memcpy_tofs (void * u_addr ,void * k_addr ,unsigned long cnt) ;
参数 u_addr 为用户空间地址,k_addr 为内核空间地址,cnt为字节数。

write( ) 函数 当设备特殊文件进行 write () 系统调用时,将调用驱动程序的write () 函数:
void write (struct inode * inode ,struct file * file ,char * buf ,int count) ;
write ()的功能是将参数buf 指定的缓冲区中的count 个字节内容复制到硬件或内核内存中, 和 read() 一样,复制工作也需要由特殊函数来完成:
unsigned char_get_user_byte (char * u_addr) ;
unsigned char_get_user_word (short * u_addr) ;
unsigned char_get_user_long(long * u_addr) ;
unsigned memcpy_fromfs(void * k_addr ,void * u_addr ,unsigned long cnt) ;
ioctl() 函数 该函数是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息,函数原型为:
int ioctl (struct inode * inode ,struct file * file ,unsigned int cmd ,unsigned long arg);
参数 cmd 为设备驱动程序要执行的命令的代码,由用户自定义,参数arg 为相应的命令提供参数,类型可以是整型、指针等。
同样,在驱动程序中,这些函数的定义也必须符合命名规则,按照本文约定,设备 "exampledev" 的驱动程序的这些函数应分别命名为
exampledev_open()、 exampledev_release()、 exampledev_read()、 exampledev_write()、 exampledev_ioctl(),
因此设备 "exampledev" 的基本入口点结构变量 exampledev_fops 赋值如下:

struct file_operations exampledev_fops {
 NULL ,
 exampledev_read ,
 exampledev_write ,
 NULL ,
 NULL ,
 exampledev_ioctl ,
 NULL ,
 exampledev_open ,
 exampledev_release ,
 NULL ,
 NULL ,
 NULL ,
 NULL
} ;

3.内存分配

由于Linux驱动程序在内核中运行,因此在设备驱动程序需要申请/释放内存时,不能使用用户级的malloc/free函数,而需由内核级的函数
kmalloc/kfree () 来实现,kmalloc()函数的原型为:
void kmalloc (size_t size ,int priority);
参数 size 为申请分配内存的字节数;
参数 priority 说明若 kmalloc() 不能马上分配内存时用户进程要采用的动作:
  GFP_KERNEL 表示等待,
    即等 kmalloc() 函数将一些内存安排到交换区来满足你的内存需要,
  GFP_ATOMIC 表示不等待,如不能立即分配到内存则返回 0 值;
函数的返回值指向已分配内存的起始地址,出错时,返回 0。

kmalloc ()分配的内存需用kfree()函数来释放,kfree ()被定义为:

# define kfree (n) kfree_s( (n) ,0)

其中kfree_s () 函数原型为:
void kfree_s (void * ptr ,int size);

参数 ptr 为 kmalloc() 返回的已分配内存的指针,size是要释放内存的字节数,若为0 时,由内核自动确定内存的大小。

4、中断

许多设备涉及到中断操作,因此,在这样的设备的驱动程序中需要对硬件产生的中断请求提供中断服务程序。
与注册基本入口点一样,驱动程序也要请求内核将特定的中断请求和中断服务程序联系在一起。
在Linux中,用 request_irq() 函数来实现请求:
int request_irq (unsigned int irq ,void( * handler) int ,unsigned long type ,char * name);

参数 irq 为要中断请求号,参数 handler 为指向中断服务程序的指针,参数type 用来确定是正常中断还是快
速中断(正常中断指中断服务子程序返回后,内核可以执行调度程序来确定将运行哪一个进程;而快速中断是指中断服务子程序返回后,
立即执行被中断程序,正常中断type 取值为0 ,快速中断type 取值为SA_INTERRUPT),参数name是设备驱动程序的名称。

5、实例

笔者最近设计了一块采用三星S3C2410 ARM处理器的电路板(ARM处理器广泛应用于手机、PDA等嵌入式系统),
板上包含四个用户可编程的发光二极管(LED),这些LED连接在ARM处理器的可编程I/O口(GPIO)上。
下图给出了 ARM 中央处理器与 LED 的连接原理(缺失)

我们在 ARM 处理器上移植 Linux 操作系统,现在来编写这些 LED 的驱动:

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <asm/hardware.h>
#define DEVICE_NAME   "leds" /*定义led 设备的名字*/
#define LED_MAJOR      231 /*定义led 设备的主设备号*/

static unsigned long led_table[] =
{
  /* I/O 方式led 设备对应的硬件资源 */
  GPIO_B10, GPIO_B8, GPIO_B5, GPIO_B6,
};

/*使用 ioctl 控制 led */
static int leds_ioctl (struct inode *inode,
                       struct file  *file,
                       unsigned int  cmd,
                       unsigned long arg)
{
  switch (cmd)
  {
    case 0:
    case 1:
      if (arg > 4)
      {
        return  -EINVAL;
      }
      write_gpio_bit(led_table[arg], !cmd);
    default:
      return  -EINVAL;
  }
}

static struct file_operations leds_fops =
{
  owner: THIS_MODULE,
  ioctl: leds_ioctl,
};

static devfs_handle_t devfs_handle;
static int __init leds_init(void)
{
  int ret;
  int i;
  /*在内核中注册设备*/
  ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &leds_fops);
  if (ret < 0)
  {
    printk(DEVICE_NAME " can't register major number"n");
    return ret;
  }
  devfs_handle = devfs_register(NULL, DEVICE_NAME,
                                DEVFS_FL_DEFAULT, LED_MAJOR, 0, 
                                S_IFCHR | S_IRUSR | S_IWUSR, &leds_fops, 
                                NULL);
  /* 使用宏进行端口初始化,set_gpio_ctrl 和 write_gpio_bit 均为宏定义*/
  for (i = 0; i < 8; i++)
  {
    set_gpio_ctrl(led_table[i] | GPIO_PULLUP_EN | GPIO_MODE_OUT);
    write_gpio_bit(led_table[i], 1);
  }
  printk(DEVICE_NAME " initialized"n");
  return 0;
}

static void __exit leds_exit(void)
{
  devfs_unregister(devfs_handle);
  unregister_chrdev(LED_MAJOR, DEVICE_NAME);
}
module_init(leds_init);
module_exit(leds_exit);

使用命令方式编译led 驱动模块:
# arm-linux-gcc -D__KERNEL__ -I/arm/kernel/include  \
 -DKBUILD_BASENAME=leds -DMODULE -c -o leds.o leds.c

以上命令将生成leds.o 文件,把该文件复制到板子的/lib目录下,使用以下命令就可以安装leds驱动模块:
#insmod /lib/ leds.o

删除该模块的命令是:
# rmmod leds

6、小结

本章讲述了Linux设备驱动程序的入口函数及驱动程序中的内存申请、中断等,
并给出了一个通过ARM处理器的GPIO口控制LED的驱动实例。

gdb

一、看到别人文章写的好,忍不住抄袭过来,怕下次网址消失

https://www.jianshu.com/p/ed90aad9cb0a

二、内容

GDB初学者指南
Later_c44e
2019.04.22 15:02:59
字数 644阅读 461
从VS转gdb的人会觉得GDB比较难用,其实gdb使用熟练了比VS可能还方便。可以说不会gdb的人,没法真正懂Linux编程。
有正在学习C语言的朋友,可以进Q群121811911下载软件资料和视频,我们一起进步。
编译具有调试符号的程序
gcc编译时,带上-g选项才能产生具有调试符号的程序。才能供gdb进行有源码调试
$ gcc -g justtest.c
反汇编
disassemb命令是反汇编
(gdb) disassemble main
Dump of assembler code for function main: 0x000000000040057d <+0>: push %rbp 0x000000000040057e <+1>: mov %rsp,%rbp 0x0000000000400581 <+4>: sub $0x20,%rsp 0x0000000000400585 <+8>: mov %edi,-0x14(%rbp) 0x0000000000400588 <+11>: mov %rsi,-0x20(%rbp) 0x000000000040058c <+15>: movl $0x12345678,-0x4(%rbp) ... 0x00000000004005c6 <+73>: retq
End of assembler dump.
/r选项是反汇编时带上机器码
(gdb) disassemble /r main
Dump of assembler code for function main: 0x000000000040057d <+0>: 55 push %rbp 0x000000000040057e <+1>: 48 89 e5 mov %rsp,%rbp 0x0000000000400581 <+4>: 48 83 ec 20 sub $0x20,%rsp 0x0000000000400585 <+8>: 89 7d ec mov %edi,-0x14(%rbp) 0x0000000000400588 <+11>: 48 89 75 e0 mov %rsi,-0x20(%rbp)
... 0x00000000004005bb <+62>: e8 90 fe ff ff callq 0x400450 <write@plt> 0x00000000004005c0 <+67>: b8 00 00 00 00 mov $0x0,%eax 0x00000000004005c5 <+72>: c9 leaveq 0x00000000004005c6 <+73>: c3 retq
End of assembler dump.
/m选项是反汇编时带上源码(如果有的话)
(gdb) disassemble /m main
Dump of assembler code for function main:
4 { 0x000000000040057d <+0>: push %rbp 0x000000000040057e <+1>: mov %rsp,%rbp 0x0000000000400581 <+4>: sub $0x20,%rsp 0x0000000000400585 <+8>: mov %edi,-0x14(%rbp) 0x0000000000400588 <+11>: mov %rsi,-0x20(%rbp)
5 int nValue = 0x12345678; 0x000000000040058c <+15>: movl $0x12345678,-0x4(%rbp)
6 printf("%p:%08X\n", &nValue, nValue); 0x0000000000400593 <+22>: mov -0x4(%rbp),%edx 0x0000000000400596 <+25>: lea -0x4(%rbp),%rax 0x000000000040059a <+29>: mov %rax,%rsi 0x000000000040059d <+32>: mov $0x400654,%edi 0x00000000004005a2 <+37>: mov $0x0,%eax 0x00000000004005a7 <+42>: callq 0x400460 <printf@plt>
7 write(0, "hello,world\n", 13);
...
也可以两个选项一起用:
disassemble /rm main
设置反汇编的风格
反汇编的语法默认是GNU风格的,可以使用命令设置为Intel风格
(gdb) set disassembly-flavor intel
(gdb) disassemble main
Dump of assembler code for function main: 0x000000000040057d <+0>: push rbp 0x000000000040057e <+1>: mov rbp,rsp 0x0000000000400581 <+4>: sub rsp,0x20 0x0000000000400585 <+8>: mov DWORD PTR [rbp-0x14],edi 0x0000000000400588 <+11>: mov QWORD PTR [rbp-0x20],rsi 0x000000000040058c <+15>: mov DWORD PTR [rbp-0x4],0x12345678 ... 0x00000000004005c5 <+72>: leave 0x00000000004005c6 <+73>: ret
End of assembler dump.
单步调试
next:单步步过
step:单步步入
以上是源码级别的单步。如果是汇编级别的单步,则使用:
nexti/stepi(两者效果一样)
自动显示反汇编代码
如果gdb匹配了源文件,则即使使用stepi,那么自动回显的内容依然是源码级别的,需要手动敲disassemble来查看当前汇编内容,很不方便。这时,可以通过display命令达到自动回显的目的:
(gdb) help display
Print value of expression EXP each time the program stops.
/FMT may be used before EXP as in the "print" command.
/FMT "i" or "s" or including a size-letter is allowed,
as in the "x" command, and then EXP is used to get the address to examine
and examining is done as in the "x" command.
With no argument, display all currently requested auto-display expressions.
Use "undisplay" to cancel display requests previously made.
可知,display命令是在每次调试器断下来后,自动显示某个表达式。
我们通过命令:
display /5i $pc
然后每次停下,就会显示对应的反汇编了:
(gdb) stepi
6 printf("%p:%08X\n", &nValue, nValue);
6: x/5i $pc
=> 0x400593 <main+22>: mov -0x4(%rbp),%edx 0x400596 <main+25>: lea -0x4(%rbp),%rax 0x40059a <main+29>: mov %rax,%rsi 0x40059d <main+32>: mov $0x400654,%edi 0x4005a2 <main+37>: mov $0x0,%eax
普通断点
通过breakpoint命令下断点。breakpoint之后可接源文件行、函数名称、地址。如:
(gdb) break main
Note: breakpoint 1 also set at pc 0x40058c.
Breakpoint 3 at 0x40058c: file justtest.c, line 5.
(gdb) break 8
Breakpoint 4 at 0x4005c0: file justtest.c, line 8.
(gdb) break 0x4005c0
Note: breakpoint 4 also set at pc 0x4005c0.
Breakpoint 5 at 0x4005c0: file justtest.c, line 8.
可以使用info命令查看断点信息:
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040058c in main at justtest.c:5
2 breakpoint keep y 0x0000000000400593 in main at justtest.c:6
3 breakpoint keep y 0x00000000004005c0 in main at justtest.c:8
通过delete命令,可以删除指定编号的断点。
(gdb) delete breakpoints 1
也可以通过disable/enable来暂定或启用断点。
(gdb) disable breakpoints 1
内存断点
watch命令是下内存写断点:
watch ($rbp-4)
类似的,还有内存读断点rwatch,与内存访问断点(读与写)awatch命令。
查看表达式及内存寄存器等
命令查看内存
x命令(examine)命令可以用于查看内存。
(gdb) help x
Examine memory: x/FMT ADDRESS.
ADDRESS is an expression for the memory address to examine.
FMT is a repeat count followed by a format letter and a size letter.
Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char), s(string) and z(hex, zero padded on the left).
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).
The specified number of objects of the specified size are printed
according to the format.
如,以字节为单位,以16进制显示,查看4单位&nValue地址处的数据:
(gdb) x /4xb &nValue
0x7fffffffdf3c: 0x78 0x56 0x34 0x12
以10进制查看:
(gdb) x /4db &nValue
0x7fffffffdf3c: 120 86 52 18
按照float解释数据:
(gdb) x /1fw &nValue
0x7fffffffdf3c: 5.69045661e-28
按照double解释数据:
(gdb) x /1fg &nValue
0x7fffffffdf3c: 1.5089747817000635e-315
查看寄存器
(gdb) info registers
修改内存值
使用set命令
set *(type)=newValue
如:
(gdb) set (int)0x7fffffffdf38=0x40490fda
有正在学习C语言的朋友,可以进Q群121811911下载软件资料和视频,我们一起进步。

三、在windows下进行反汇编研究

https://www.cnblogs.com/amanlikethis/p/3570832.html

四、gdb查看反汇编代码,返回值存放在哪里

如果是指针,一般有些话在$rax或者$eax里面。
但是我在自己跟踪的时候为什么没有发现?难道漏掉了?
问别人,一验证,果然如此!

但是如果是其它常数呢?存放在哪个寄存器?

多线程调试之痛,以及解决之道

1、文章来源

https://www.cnblogs.com/frankbadpot/archive/2010/06/23/1762916.html

2、多线程调试之痛

调试器(如VS2008和老版GDB)往往只支持all-stop模式,调试多线程程序时,如果某个线程断在一个断点上,你的调试器会让整个程序freeze,直到你continue这个线程,程序中的其他线程才会继续运行。这个限制使得被调试的程序不能够像真实环境中那样运行--当某个线程断在一个断点上,让其他线程并行运行。
GDBv7.0引入的non-stop模式使得这个问题迎刃而解。在这个模式下,
    当某个或多个线程断在一个断点上,其他线程仍会并行运行
    你可以选择某个被断的线程,并让它继续运行
让我们想象一下,有了这个功能后
    当其他线程断在断点上时,程序里的定时器线程可以正常的运行了,从而避免不必要得超时 
    当其他线程断在断点上时,程序里的watchdog线程可以正常的运行了,从而避免嵌入式硬件以为系统崩溃而重启
    可以控制多个线程运行的顺序,从而重现deadlock场景了。由于GDB可以用python脚本驱动调试,理论上可以对程序在不同的线程运行顺序下进行自动化测试。

因此,non-stop模式理所当然成为多线程调试“必杀技”。这2009年下半年之后发布的Linux版本里都带有GDBv7.0之后的版本。很好奇,不知道VS2010里是不是也支持类似的调试模式了。

3、演示项目

A、源码

// gdb non-stop mode demo
// build instruction: g++ -g -o nonstop nonstop.cpp -lboost_thread

#include <iostream>
#include <boost/thread/thread.hpp>

struct op
{
        op(int id): m_id(id) {}
        void operator()()
        {
                std::cout << m_id << " begin" << std::endl;
                std::cout << m_id << " end" << std::endl;
        }
        int m_id;
};
int main(int argc, char ** argv)
{
        boost::thread t1(op(1)), t2(op(2)), t3(op(3));
        t1.join(); t2.join(); t3.join();
        return 0;
}

B. 把一下3行添加到~/.gdbinit来打开non-stop模式

set target-async 1
set pagination off
set non-stop on

C. 启动gdb,设断点,运行.可以看到主线程1是running,3个子线程都断在断点上,而不是只有一个子线程断在断点上.

~/devroot/nonstop$ gdb ./nonstop
GNU gdb (GDB) 7.0-ubuntu
Reading symbols from /home/frankwu/devroot/nonstop/nonstop...done.
(gdb) break 14
Breakpoint 1 at 0x402058: file nonstop.cpp, line 14.
(gdb) break 24
Breakpoint 3 at 0x401805: file nonstop.cpp, line 24.
(gdb) run
Starting program: /home/frankwu/devroot/nonstop/nonstop
[Thread debugging using libthread_db enabled]
[New Thread 0x7ffff6c89910 (LWP 2762)]
[New Thread 0x7ffff6488910 (LWP 2763)]
1 begin
Breakpoint 1, op::operator() (this=0x605118) at nonstop.cpp:14
14                  std::cout << m_id << " end" << std::endl;
2 begin
Breakpoint 1, op::operator() (this=0x605388) at nonstop.cpp:14
14                  std::cout << m_id << " end" << std::endl;
[New Thread 0x7ffff5c87910 (LWP 2764)]
3 begin
Breakpoint 1, op::operator() (this=0x605618) at nonstop.cpp:14
14                  std::cout << m_id << " end" << std::endl;
(gdb) info threads
  4 Thread 0x7ffff5c87910 (LWP 2764)  op::operator() (this=0x605618) at nonstop.cpp:14
  3 Thread 0x7ffff6488910 (LWP 2763)  op::operator() (this=0x605388) at nonstop.cpp:14
  2 Thread 0x7ffff6c89910 (LWP 2762)  op::operator() (this=0x605118) at nonstop.cpp:14
* 1 Thread 0x7ffff7fe3710 (LWP 2759)  (running)

D、 让线程3继续运行,注意我顾意把主线程1也continue,这是我发现的workaround,否则gdb不能切回thread 1.

(gdb) thread apply 3 1 continue

Thread 3 (Thread 0x7ffff6488910 (LWP 2763)):
Continuing.

Thread 1 (Thread 0x7ffff7fe3710 (LWP 2759)):
Continuing.
Cannot execute this command while the selected thread is running.
2 end
[Thread 0x7ffff6488910 (LWP 2763) exited]

warning: Unknown thread 3.

Thread 1 (Thread 0x7ffff7fe3710 (LWP 2759)):
Continuing.
Cannot execute this command while the selected thread is running.
(gdb) info threads
  4 Thread 0x7ffff5c87910 (LWP 2764)  op::operator() (this=0x605618) at nonstop.cpp:14
  2 Thread 0x7ffff6c89910 (LWP 2762)  op::operator() (this=0x605118) at nonstop.cpp:14
* 1 Thread 0x7ffff7fe3710 (LWP 2759)  (running)

E、 让另外两个线程继续运行而结束,主线程断在第24行,最后结束.

(gdb) thread apply 4 2 1 continue

Thread 4 (Thread 0x7ffff5c87910 (LWP 2764)):
Continuing.

Thread 2 (Thread 0x7ffff6c89910 (LWP 2762)):
Continuing.

Thread 1 (Thread 0x7ffff7fe3710 (LW命令P 2759)):
Continuing.
Cannot execute this command while the selected thread is running.
3 end
1 end
[Thread 0x7ffff5c87910 (LWP 2764) exited]
[Thread 0x7ffff6c89910 (LWP 2762) exited]

Breakpoint 3, main (argc=1, argv=0x7fffffffe348) at nonstop.cpp:24
24          return 0;

(gdb) continue
Thread 1 (Thread 0x7ffff7fe3710 (LWP 2759)):
Continuing.

Program exited normally.

设置 GDB 工程代码,在其它机器上调试时尤其重要

1、dir 命令,添加一个搜索路径

(gdb) dir source_dir/

2、set substitute-dir命令

set substitute-path from_path  to_path

3、list命令

l 为 list 的缩写
查看30~90行之间的代码: l 30,90

4、总结

在gdb里面查看源码,
list 一下提醒找不到相关文件,列出来的是绝对路径的完整文件名。
help files   看一下帮助,可以加载符号,源文件等,自己看一下。
dir 设置源码路径无效,show directories  看到设置成功,但是还是找不到文件。
应该是绝对路径的问题。
因为igcc 根据你编译的时候指定的是绝对路径还是  ../../XXX.cpp之类的相对路径,在生成debug_info的时候,也把这个路径保存为debug_info 里面的文件名字,就是最后 gdb list 里面找到的文件名字。
这个可以list  查看是不是绝对路径,然后可以用命令
 readelf -p .debug_str  exe_or_so_file 
看到里面保存是是完整的绝对路径。
gdb 的dir 命令添加的源码搜索路径只对相对路径的情况有效。
一个解决办法就是在gcc的时候,使用相对路径那么gdb里面你就可以使用 dir来设置了。像些CMake之类的,它喜欢使用绝对路径,有人可以写个脚本让它使用相对路径,参考  http://stackoverflow.com/questions/9607155/make-gcc-put-relative-filenames-in-debug-information
如果gcc里面-g  生成的debug_info 使用的绝对路径了,最简单的办法就是你把源码也放到相应的位置上去了。
但如果你不想使用这个绝对路径,那也还是有办法的。 
GDB还提供另外一个选择,可以让你修改源码搜索路径,把源码绝对路径里面的一个path映射到另一个path上去,这样即使你debug info了里面的是绝对路径,源码也可以放到另外的目录。
这就是命令
set substitute-path from_path  to_path
比如 list显示的源码是   /home/aaa/1.cpp
那么设置了  set substitute-path /home/aaa/   /home/bbb/

之后,即使你的源文件1.cpp 放在 /home/bbb下面也是可以找到的了。因为gdb帮你做了字符串替换

gcov_lcov

一、报错lcov和gcov的使用错误

编译使用的gcc版本和gcov的版本对不上的话,使用lcov和gcov的时候会报错

lcov的错误:
[xx@localhost XXX]$ lcov --capture --directory cov --output-file xxx.info --test-name xxx
Capturing coverage data from cov
Found gcov version: 4.8.5
Scanning cov for .gcda files ...
Found 35 data files in cov
Processing XXX.gcda
geninfo: ERROR: XXX.gcno: reached unexpected end of file

gcov的错误:
[xxx@localhost xxx]$ gcov -o cov ./xxx.c
cov/xxx.gcno:版本‘405 ’,首选‘408R’

gcov: out of memory allocating 16030351984 bytes after a total of 135168 bytes

确保gcc和gcov版本一样即可

按照上面说的方法实践,果然搞定
查看了一下:
  gcc -v : 4.4.7
  g++ -v  : 4.4.7
   gcov -v :4.4.7
    lcov -v :1.10

然后将lcov 版本升级到 1.12,问题搞定

通用代码的覆盖率问题

1、问题描述

公共代码在项目A中被覆盖 30,在项目 B 中覆盖另外 70,怎样`才能统计出 100% 的覆盖效果?

2、实验代码

[zhoujinhua@ecs-b0b7 tmp]$ cat common.c
#include <stdio.h>

int myAdd(int a, int b)
{
    return a+b;
}
int mySub(int a, int b)
{
    return a - b;
}

[zhoujinhua@ecs-b0b7 tmp]$ cat good.c
    #include <stdio.h>
    #include <execinfo.h>
    #include <stdlib.h>

    extern int myAdd(int a, int b);
    void print_call_stack()
    {
        int    i          = 0;
        int    size       =32;
        void  *array[32];
        int    stack_num  = backtrace(array, size);
        char **stacktrace = (char**)backtrace_symbols(array, stack_num);
        for (i=0; i<stack_num; i++)
        {
            printf("func_%d addr:%s\n", i, stacktrace[i]);
        }
        free(stacktrace);
        return;
    }
    void func_1(){print_call_stack();}
    void func_2(){func_1();}
    void func_3(){func_2();}
    void func_4(){func_3();}
    void func_5(){func_4();}
    void func_6(){func_5();}
    int main(int argc, char **argv)
    {
        myAdd(3,4);
        printf("argc = %d\n", argc);
        if (argc < 2)
        {
            printf("please use at least one parameter!\n");
            return 1;
        }
        func_6();
        return 0;
    }
[zhoujinhua@ecs-b0b7 tmp]$ cat better.c
#include <stdio.h>
extern int mySub(int a,int b);
int main()
{
    return mySub(9, 2);
}
[zhoujinhua@ecs-b0b7 tmp]$

3、实验操作步骤

1、编译第一个项目

gcc -g3 -fprofile-arcs -ftest-coverage good.c common.c -o dir1/good

2、运行第一个项目,产生覆盖率数据

./dir1/good

3、将中间文件放入第一个目录

mv *.gcda ./dir1/
  mv *.gcna ./dir1/
  mv *.gcov ./dir1/

4、编译第二个项目,同样的操作步骤

gcc -g3 -fprofile-arcs -ftest-coverage better.c common.c -o dir2/better

5、当前目录下的所有覆盖文件进行统计

lcov -d ./ -c -o ./prj_dir/my_prj.info -t 'prj_title' -b ./
  说明:-d ./ 表示当前目录下的所有

6、最终生成漂亮的覆盖率网页

genhtml -o prj_dir/ prj_dir/my_prj.info

7、网页查看,不用浏览器,直接在 linux 命令行就能查看,TAB切换、跳转

yum -y install w3m w3m-image
   w3m ./prj_dir/index.html

在某些大型项目中,无法随意修改编译、链接参数,怎样实现stacktrace
当编译、链接参数无法正确✔️配置时,尽管如下函数可以运行,但是结果缺不是预想那样
void print_call_stack()
{
    int    i          = 0;
    int    size       =32;
    void  *array[32];
    int    stack_num  = backtrace(array, size);
    char **stacktrace = (char**)backtrace_symbols(array, stack_num);
    for (i=0; i<stack_num; i++)
    {
        printf("func_%d addr:%s\n", i, stacktrace[i]);
    }
    free(stacktrace);
    return;
}

stacktrace[i] 并没有被转换成函数名,仍然是栈中十六进制数的样子。

此时只能自己实现转换。
通过观察发现,array[1],就是上一层函数中的某个地址,一般是函数名离开偏移地址。
如果上层函数是func,责array[1]的值是 func + offset.
可以通过简单的条件判断:
if ((array[1]>func) && ((array[1]-func)<1024)) //限定偏移范围,可以调整
{
    return true;
}

但是实际编译是,编译器会报错,提示void*类型不能和函数类型进行减法操作。
需要做一次转换,将两个值都转化为word32:

typedef word32 unsinged int
typedef void (*entryFuncType)()
typedef struct
{
    eFuncIdx      idx;
    entryFuncType func;
}funcMapType;
funcMapType map[]=
{
    {eIdx_1,  func_1},
    {eIdx_2,  func_2},
};
BOOL isInFunction(void *stackPos, entryFuncType func)
{
    bool ret = false;
    int  offset=0;
    char buf_1[32] = {0};
    char buf_2[32] = {0};
    word32 stackPosVal=0;
    word32 funcVal=0;
    sprintf(buf_1, "0x%x",stackPos);
    sprintf(buf_2, "0x%x",func);
    sscanf(buf_1, "%x", &stackPosVal);
    sscanf(buf_2, "%x", &funcVal);
    offset=stackPosVal - funcVal;
    if ((0<offset) && (offset<1024))
    {
        ret = true;
    }
    return ret;
}

void print_call_stack()
{
    int    i          = 0;
    int    size       =32;
    void  *array[32];
    int    stack_num  = backtrace(array, size);
    char **stacktrace = (char**)backtrace_symbols(array, stack_num);

    //这个简单的判断,可以替换成一个循环
    if(isInFunction(array[1], myFunction))
    {
        //do something....
    }
    free(stacktrace);
    return;
}