posts - 200, comments - 8, trackbacks - 0, articles - 0

     摘要: 一.IO与文件目录管理 1.pread=lseek+read   pread读取以后不改变读写位置 2.mmap映射:   /proc/${pid}/mem 无法映射 3.IO的有效用户与实际用户   默认情况:实际用户与有效用户一致。   &nbs...  阅读全文

posted @ 2013-01-02 11:01 鑫龙 阅读(418) | 评论 (0)编辑 收藏

     摘要: 一.IO与文件映射 1.IO的共享与效率  read与write其中数据缓冲的大小  读取数据的缓冲大小:getpagesize。  2.定位与定位读取(随机读取)  read与write在操作的时候,自动移动读取位置.  lseek改变读取位置.  pread/pwr...  阅读全文

posted @ 2012-12-31 15:30 鑫龙 阅读(711) | 评论 (0)编辑 收藏

rzsz 是一款 Linux 下面的文件传输工具。实现原理是通过 Xmodem / Ymodem / Zmodem 协议传输文件。lrzsz 可以在支持这三种协议的Shell界面的工具下工作,比如XShell。

    lrzsz是一个古老的软件,最新版本0.12.20是在1998年更新的。

大部分linux发行版的安装工具都可以直接安装 lrzsz,比如apt-get / pacman,当然我们也可以下载安装包手动安装。这次由于我所在的服务器没有管理员权限,我就使用源码包安装。

$ mkdir /home/alswl/app
$ wget http://ohse.de/uwe/releases/lrzsz-0.12.20.tar.gz
$ tar xzvf lrzsz-0.12.20.tar.gz
$ cd lrzsz-0.12.20
$ ./configure --prefix="/home/alswl/app"
$ make
$ make install
$ export PATH="$PATH:/home/alswl/app/bin"

安装上面步骤就可以安装完成了,重点在于 --prefix="/home/alswl/app",这可以指定安装路径,安装到自己可以读写的地方去。

export作用是添加安装目录到 $PATH 下面,这样可以直接使用 lsz / lrz 操作。



最近学习linux 尝试使用lrzsz 在这里做记录以便今后查阅。

使用前准备

1.      准备 lrzsz-arm-bin编译完成的发送及接收工具 本文例子为 lrz 和lsz

2.      SecureCRT.exe 工具(通过串口连接到终端)。

 

开始添加工具

使用其他工具将lrz 和lsz 拷贝到终端 /usr/bin 目录下( 在系统环境变量路径下即可)

修改 lrz 和lsz 的属性  chmod 755 lrz chmod 755 lsz 。

 

开始验证

1.文件接收

将PC端文件发送到终端某目录本例将PC端文件fport.exe发送到终端/xino目录

在终端执行命令

Cd /xino

Lrz

SecureCRT.exe弹出如下选择文件窗口选择


选择 文件并点击 “添加”添加到发送列表,点击“确定” 进行发送

 

传输文件

 

传输完成  当前目录 出现 Fport.exe 文件。

 

3.      文件发送

在SecureCRT.exe 上选择 “选项“à“会话选项”选择“终端”àX/Y/Zmodem

设置好 目录中的 上传及下载 目录, 本例为F:\my documents

 

完成后 在命令行输入  lsz key.sh (把 key.sh 文件发送到PC端也可以是终端程序执行的 *.log)

 

输入命令后回车 上传文件

上传完成后 去目录F:\my documents

 查看 出现上传的文件

 

 备注

常用参数

-b 以二进制方式,默认为文本方式。(Binary (tell it likeit is) file transfer override.)

-e 对所有控制字符转义。(Force sender to escape allcontrol characters; normally XON, XOFF, DLE, CR-@-CR, and Ctrl-X are escaped.)

如果要保证上传的文件内容在服务器端保存之后与原始文件一致,最好同时设置这两个标志,如下所示方式使用:

rz -be

此命令执行时,会弹出文件选择对话框,选择好需要上传的文件之后,点确定,就可以开始上传的过程了。上传的速度取决于当时网络的状况。

如果执行完毕显示“0错误”,文件上传就成功了,其他显示则表示文件上传出现问题了。

posted @ 2012-12-30 23:19 鑫龙 阅读(485) | 评论 (0)编辑 收藏

一、make与makefile
  1.回顾:
    目标的语法
    目标名:依赖目标
      @命令
      @命令
      
    make的命令行使用
      make -f make脚本文件  目标名
  2.目标的划分
    目标依赖

  3.默认规则:
    a.不指定目标,执行第一个目标
    b.不指定make文件,默认文件是makefile Makefile
      makefile优先
  4.目标的调用规则:(make把目标当成当前文件夹下同名的文件)
    make执行目标:
      搜索与目标相同的文件
      如果文件存在,则判定文件是否被修改过。
      文件未被修改,则停止执行,输出提示
      文件修改过,则进行执行。(文件不存在属于被修改过范畴)

      比较:当前目标与依赖目标
  5.建议:
    只要有文件输出,就把任务作为一个目标,并且把输出的文件作为目标名。
    范例:
    input.o:input.c
       gcc -c -fpic input.c
    libdemo.so:input.o
       gcc -shared -olibdemo.so input.o
    demo:libdemo.so demo.c
       gcc demo.c -ldemo -L. -odemo
  
  6.潜规则(不建议)
    适用于:.c目标与.o目标。
    查找.o目标,目标不存在,就把.o替换成.c
    如果.c存在,实施潜规则:直接调用gcc把.c执为.o
  7.变量
    变量名=值 值
    
    $(变量名)  ${变量}  
  8.伪目标:
    不把目标作为文件处理的目标称为伪目标
    声明伪目标
    .PHONY=目标  

二、环境变量
  1.使用main的参数
  int main(int args,char *argv[],char **arge)
  {
  }
  命令行参数argv与环境行arge都是字符串数组.
  约定:最后一个字符串是NULL/0
  2.在C的标准库提供:外部变量
   extern char **environ;


#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(/*int args,char *argv[],char*arge[]*/)
{
    /*
    while(*arge)
    {
        printf("%s\n",*arge);
        arge++;
    }
    
*/
    /*
    int i=0;
    while(arge[i])
    {
        printf("%s\n",arge[i]);
        i++;
    }
    
*/
    while(*environ)
    {
        printf("%s\n",*environ);
        environ++;
    }
}

3.修改获取某个环境变量
    getenv/setenv/unsetenv

#include <stdlib.h>
#include <stdio.h>
main()
{
    char *val;
    val=getenv("PATH");
    printf("%s\n",val);
}
三、IO的基础
  1.认识内核对象
    不允许访问内核设备和内存,但可以通过内核系统函数去访问.
    对每个内核对象进行编号ID.
    如果访问内核对象,只能通过ID.
    编程模型:
      申请得到一个ID
      在内核系统函数中使用ID得到对应内核对象数据
  2.怎么访问文件
    使用函数,传递一个文件,系统打开文件,加载文件数据,
    返回一个ID.
    使用函数,传递ID,得到数据.
    使用函数传递ID,告诉系统释放文件.
        
    ID:文件描述符号.file description (fd)
    
    每个程序执行的时候都有一个目录,存放打开的文件描述符号
  3.每个程序默认打开三个文件设备:
    0:标准输入
    1:标准输出
    2:错误输出
  4.操作文件描述符号
    ssize_t write(int fd,
     void *buf,//要写入内核对象的数据
     size_t size);//写入数据大小
    返回:
      >0 实际写入的数据
      -1 写入错误 
    ssize_t read(int fd,
      void *buf,//返回数据的空间
      size_t size);//空间大小
    返回:
      >0:实际读取的数据
      =0:碰到文件结束符号EOF (ctrl+d)
      -1:读取错误
    
    建议:
      0:输入
      1:输出
      2:错误输出
课堂练习:
  1.使用write向0 1 2 写数据
  2.使用read从0 1 读取数据,并判定输入的情况,然后根据相应的结果输出提示

#include <stdlib.h>
#include <stdio.h>
main()
{
    //printf("%d\n",getpid());
    
//while(1);
    /*
    int r=write(0,"Hello\n",6);
    write(1,"world\n",6);
    write(2,"louis\n",6);
    int a=20;
    
    write(1,&a,4);
    
*/
    char buf[32];
    //memset(buf,0,32);
    bzero(buf,32);
    
    int r=read(0,buf,30);
    printf("实际输入:%d\n",r);
    if(r>0)
    {
        buf[r]=0;
        printf("::%s\n",buf);
    }
    if(r==0)
    {
        printf("ctrl+d\n");
    }
    if(r==-1)
    {
        printf("输入错误!\n");
    }
}

三.基于文件的描述符号
  1.得到文件描述符号/释放文件描述符号
   a.文件类型
    目录文件d
    普通文件f
    字符设备文件c
    块设备文件b
    软连接文件l
    管道文件p
    socket文件s   
   b.文件的属性
     1.属性的表达方式:绝对模式(0666类似的八进制数),字符模式(rwx)
       0    0   0    0
          拥有者   组   其他用户
       0666
     2.文件的权限属性:
       读
       写
       执行
       粘附位权限
       用户设置位权限
       组设置位权限
      0   0         0      0       0
        特殊权限   Owner  group  其他用户

       s:
       S
       t
       T
      2.1.  s设置位
          2:组设置位
          4:用户设置位
         s对执行有效
         无效的设置位使用S表示
         
         设置位向其他用户开放拥有者权限的权限.用户设置位
         设置位向其他用户开放组用户权限的权限.组用户设置位
         设置位只对执行程序有意义(执行权限有意义)
         
      2.2.  t设置位
          1:表示沾附位设置
          t对写文件有意义
         没有执行权限的沾附位使用T表示.
         沾附的目的:防止有些权限的用户删除文件.
      
      程序在执行的时候到底拥有的是执行者用户的权限
      还是文件拥有者的权限.(看setUID)
       程序执行中有两个用户:
          实际用户:标示进程到底是谁
          有效用户:标示进程访问资源的权限
       上述一般情况是一样的,有时候被setUID改变      
   总结:
     沾附位的作用: 防止其他有写权限用户删除文件
     设置位的作用: 向其他执行者开发组或者用户的权限.
练习:
  1.使用cat创建一个文件
  2.设置沾附位,并观察属性
  3.设置用户设置位, 并观察属性
  4.设置组设置位, 并观察属性
  5.考虑w权限与沾附位的关系
  6.考虑x权限与设置位的关系.

  2.通过文件描述符号读写各种数据.      
    open函数与creat函数

 int open(
                    const char *filename,//文件名
                    int flags,//open的方式[创建/打开]
                    mode_t mode//权限(只有创建的时候有效)
                    )

 返回:
      >=0:内核文件描述符号.
      =-1:打开/创建失败
  
    open的方式:
      必选方式:O_RDONLY O_WRONLY O_RDWR,必须选择一个
      创建/打开:O_CREAT
      可选方式:
          对打开可选方式:O_APPEND  O_TRUNC(清空数据)
          对创建可选方式:O_EXCL
     组合:
        创建:
          O_RDWR|O_CREAT
          O_RDWR|O_CREAT | O_EXCL 
        
        打开:          
          O_RDWR
          O_RDWR|O_APPEND
          O_RDWR|O_TRUNC
     权限:
       建议使用8进制数
    关闭 
    void  close(int fd);           

案例1:
  创建文件
案例2:
  创建文件并写入数据
    20  short float
    tom  20   99.99
    bush  70   65.00
    达内  40   100.00
  注意:
    文件的创建的权限受系统的权限屏蔽的影响
    umask    //显示屏蔽权限.
    umask 0666  //设置权限屏蔽.   
    
    ulimit -a 显示所有的其他限制. 


/*创建文件*/
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
main()
{
    int fd;
    
    char name[20];
    short age;
    float score;
    char sex;
    
    fd=open("test.dat",
        O_RDWR|O_CREAT|O_EXCL,
);
    if(fd==-1) printf("open error:%m\n"),exit(-1);
    
    
    //写第一条
    memcpy(name,"tom",strlen("tom")+1);
    age=20;
    score=99.99;
    sex='F';
    
    write(fd,name,sizeof(name));
    write(fd,&age,sizeof age);
    write(fd,&score,sizeof(float));
    write(fd,&sex,sizeof(sex));
    
    //写第二条
    memcpy(name,"Bush",strlen("Bush")+1);
    age=70;
    score=65.00;
    sex='M';
    write(fd,name,sizeof(name));
    write(fd,&age,sizeof age);
    write(fd,&score,sizeof(float));
    write(fd,&sex,sizeof(sex));
    
    //写第三条
    
    memcpy(name,"达内",strlen("达内")+1);
    age=10;
    score=99.00;
    sex='F';
    write(fd,name,sizeof(name));
    write(fd,&age,sizeof age);
    write(fd,&score,sizeof(float));
    write(fd,&sex,sizeof(sex));
        
    close(fd);
}
案例3:
  打开文件读取数据
  重点:
    怎么打开读取
    文件尾的判定
  
  基本类型的数据读写.
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

main()
{
    char name[20];
    short age;
    float score;
    char sex;
    int fd;
    int r;
    fd=open("test.dat",O_RDONLY);
    if(fd==-1) printf("open error:%m\n"),exit(-1);
    
    while(1)
    {
        r=read(fd,name,sizeof(name));
        if(r==0) break;
        r=read(fd,&age,sizeof(short));
        r=read(fd,&score,sizeof(float));
        r=read(fd,&sex,sizeof(sex));
        printf("%s,\t%4hd,\t%.2f,\t%1c\n",
                name,age,score,sex);
    }
    
    close(fd);
}
案例4:
  结构体读取
  描述:从键盘读取若干条数据,保存到文件
     数据追加
View Code 

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
struct stu
{
    int no;
    char name[20];
    float score;
};
/*
.判定文件是否存在,存在打开,不存在创建
.输入记录
.保存记录
.提示继续输入
.继续/不继续
.关闭文件
*/
int openfile(const char *filename)
{
    int fd;
    fd=open(filename,O_RDWR|O_CREAT|O_EXCL,0666);
    if(fd==-1)//表示文件存在,则打开
    {
        fd=open(filename,O_RDWR|O_APPEND);
        return fd;
    }
    return fd;
}
void input(struct stu *record)
{
    bzero(record,sizeof(struct stu));
    printf("输入学号:");
    scanf("%d",&(record->no));
    printf("输入姓名:");
    scanf("%s",record->name);
    printf("输入成绩:");
    scanf("%f",&(record->score));
}
void save(int fd,struct stu *record)
{
    write(fd,record,sizeof(struct stu));
}
int iscontinue()
{
    char c;
    printf("是否继续输入:\n");
    //fflush(stdin);
    
//fflush(stdout);
    scanf("\n%c",&c);    
    if(c=='Y' || c=='y')
    {
        return 1;
    }
    return 0;
}

int main()
{
    int fd;
    int r;
    struct stu s={0};
    fd=openfile("stu.dat");
    if(fd==-1) printf("openfile:%m\n"),exit(-1);
    
    while(1)
    {
        input(&s);
        save(fd,&s);
        r=iscontinue();
        if(r==0) break;
        system("clear");
    }
    close(fd);
    printf("输入完毕!\n");    
}
3.文件描述符号与重定向
     1.判定文件描述符号与终端的邦定关系
     int isatty(int fd)
     返回非0:fd输出终端
        0:fd输出被重定向
     2.防止重定向
       /dev/tty

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int fd;
    printf("Hello\n");
    write(1,"World\n",6);
    fd=open("/dev/tty",O_WRONLY);
    if(isatty(1))
    {
        write(1,"notredir\n",9);
    }
    else
    {
        write(1,"redir\n",6);
    }
    write(fd,"Killer\n",7);
}

总结:
  1.make的多目标依赖规则以及伪目标
  2.文件的创建与打开(了解设置位的作用)
  3.文件的读写(字符串/基本类型/结构体)
  4.了解描述符号与重定向

作业:
  1.完成上课的练习.
  2.写一个程序使用结构体读取1种的数据,
     并全部打印数据,
     并打印平均成绩
  3.写一个程序:
    查询1种的数据.比如:输入姓名,查询成绩
  4.写一个程序,录入保存如下数据:
    书名  出版社  价格  存储量  作者  
  5.写一个程序负责文件拷贝
    main 存在的文件  新的文件名
    要求:
      文件存在就拷贝,不存在提示错误.

 

posted @ 2012-12-30 16:39 鑫龙 阅读(444) | 评论 (0)编辑 收藏

一、引言

通常情况下,对函数库的链接是放在编译时期(compile time)完成的。所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行文件(executable file)。程序运行时,与函数库再无瓜葛,因为所有需要的函数已拷贝到自己门下。所以这些函数库被成为静态库(static libaray),通常文件名为“libxxx.a”的形式。

其实,我们也可以把对一些库函数的链接载入推迟到程序运行的时期(runtime)。这就是如雷贯耳的动态链接库(dynamic link library)技术。

二、动态链接库的特点与优势

首先让我们来看一下,把库函数推迟到程序运行时期载入的好处:

1. 可以实现进程之间的资源共享。

什么概念呢?就是说,某个程序的在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝了。如果有,则让其共享那一个拷贝;只有没有才链接载入。这样的模式虽然会带来一些“动态链接”额外的开销,却大大的节省了系统内存资源。C的标准库就是动态链接库,也就是说系统中所有运行的程序共享着同一个C标准库的代码段。

2. 将一些程序升级变得简单。用户只需要升级动态链接库,而无需重新编译链接其他原有的代码就可以完成整个程序的升级。Windows 就是一个很好的例子。

3. 甚至可以真正坐到链接载入完全由程序员在程序代码中控制。

程序员在编写程序的时候,可以明确的指明什么时候或者什么情况下,链接载入哪个动态链接库函数。你可以有一个相当大的软件,但每次运行的时候,由于不同的操作需求,只有一小部分程序被载入内存。所有的函数本着“有需求才调入”的原则,于是大大节省了系统资源。比如现在的软件通常都能打开若干种不同类型的文件,这些读写操作通常都用动态链接库来实现。在一次运行当中,一般只有一种类型的文件将会被打开。所以直到程序知道文件的类型以后再载入相应的读写函数,而不是一开始就将所有的读写函数都载入,然后才发觉在整个程序中根本没有用到它们。

三、动态链接库的创建

由于动态链接库函数的共享特性,它们不会被拷贝到可执行文件中。在编译的时候,编译器只会做一些函数名之类的检查。在程序运行的时候,被调用的动态链接库 函数被安置在内存的某个地方,所有调用它的程序将指向这个代码段。因此,这些代码必须实用相对地址,而不是绝对地址。在编译的时候,我们需要告诉编译器, 这些对象文件是用来做动态链接库的,所以要用地址不无关代码(Position Independent Code (PIC))。

对gcc编译器,只需添加上 -fPIC 标签,如:

gcc -fPIC -c file1.c
gcc -fPIC -c file2.c
gcc -shared libxxx.so file1.o file2.o

注意到最后一行,-shared 标签告诉编译器这是要建立动态链接库。这与静态链接库的建立很不一样,后者用的是 ar 命令。也注意到,动态链接库的名字形式为 “libxxx.so” 后缀名为 “.so”

四、动态链接库的使用

使用动态链接库,首先需要在编译期间让编译器检查一些语法与定义。

这与静态库的实用基本一样,用的是 -Lpath 和 -lxxx 标签。如:

gcc file1.o file2.o -Lpath -lxxx -o program.exe

编译器会先在path文件夹下搜索libxxx.so文件,如果没有找到,继续搜索libxxx.a(静态库)。

在程序运行期间,也需要告诉系统去哪里找你的动态链接库文件。在UNIX下是通过定义名为 LD_LIBRARY_PATH 的环境变量来实现的。只需将path赋值给此变量即可。csh 命令为:

setenv LD_LIBRARY_PATH your/full/path/to/dll

一切安排妥当后,你可以用 ldd 命令检查是否连接正常。

ldd program.exe


动态链接库*.so的编译与使用- -


动态库*.so在linux下用c和c++编程时经常会碰到,最近在网站找了几篇文章介绍动态库的编译和链接,总算搞懂了这个之前一直不太了解得东东,这里做个笔记,也为其它正为动态库链接库而苦恼的兄弟们提供一点帮助。
1、动态库的编译

下面通过一个例子来介绍如何生成一个动态库。这里有一个头文件:so_test.h,三个.c文件:test_a.c、test_b.c、test_c.c,我们将这几个文件编译成一个动态库:libtest.so。

so_test.h:

#include 
#include 

void test_a();
void test_b();
void test_c();


test_a.c:

#include "so_test.h"
void test_a()
{
printf("this is in test_a...\n");
}


test_b.c:
#include "so_test.h"
void test_b()
{
printf("this is in test_b...\n");
}

test_a.c:

#include "so_test.h"
void test_c()
{
printf("this is in test_c...\n");
}

将这几个文件编译成一个动态库:libtest.so
$ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so

2、动态库的链接

在1、中,我们已经成功生成了一个自己的动态链接库libtest.so,下面我们通过一个程序来调用这个库里的函数。程序的源文件为:test.c。

test.c:

#include "so_test.h"
int main()
{
test_a();
test_b();
test_c();
return 0;

}

l 将test.c与动态库libtest.so链接生成执行文件test:

$ gcc test.c -L. -ltest -o test

l 测试是否动态连接,如果列出libtest.so,那么应该是连接正常了

$ ldd test

l 执行test,可以看到它是如何调用动态库中的函数的。
3、编译参数解析
最主要的是GCC命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件

l -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

l -L.:表示要连接的库在当前目录中

l -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称

l LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。

l 当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
4、注意

调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。

posted @ 2012-12-30 15:16 鑫龙 阅读(416) | 评论 (0)编辑 收藏

  检查是否已经安装以下的辅助软件包
          [root@localhost ~]# rpm -q ncurses
          ncurses-5.5-24.
          [root@localhost ~]#  rpm -q ncurses-devel
          package ncurses-devel is not installed
    提示ncurses-devel没有安装,用yum安装:
          [root@localhost ~]#  yum install ncurses-devel
          Setting up Install Process
          Total download size: 1.6 M
          Is this ok [y/N]: y
          Downloading Packages:
          Installed:  ncurses-devel.i386 0:5.5-24.
          Complete! 

链接要加上ncurses库, cc -l ncurses xxxx.c

转自:http://blog.csdn.net/xumaojun/article/details/6229789

posted @ 2012-12-29 10:56 鑫龙 阅读(345) | 评论 (0)编辑 收藏

在写hadoop程序编译时,往往需要HADOOP_CLASSPATH路径,可通过以下方式进行在编译脚本中设置:
for f in $HADOOP_HOME/hadoop-*.jar; do
CLASSPATH=${CLASSPATH}:$f
done

for f in $HADOOP_HOME/lib/*.jar; do
CLASSPATH=${CLASSPATH}:$f
done

for f in $HIVE_HOME/lib/*.jar; do
CLASSPATH=${CLASSPATH}:$f
done
 
转自:http://blog.sina.com.cn/s/blog_62a9902f01017x7j.html

posted @ 2012-12-28 20:44 鑫龙 阅读(4663) | 评论 (1)编辑 收藏

brk/sbrk
维护一个位置。 brk/sbrk改变这个位置。
brk改变绝对位置
sbrk改变相对位置

昨天的补充:
永远记住:C的基本类型就那几种。
所有全新类型都是使用typedef重新定义的。
类型重定义的好处:
1. 维护方便
2. 便于移植(每个系统中都用同一个名,不用修改)
3. 容易理解

一、 映射虚拟内存
没有任何额外维护数据的内存分配 mmap/munmap
1. 函数说明:

void *mmap(
void *start, //指定映射的虚拟地址,如果为0,则由系统指定开始位置
size_t length,//指定映射空间的大小。 pagesize的倍数
int prot, //映射的权限 PROT_NONE PROT_READ PROT_WRITE PROT_WRITE PROT_EXEC
int flags, //映射的方式
int fd, //文件描述符号
offset_t off //文件中的映射开始位置(必须是0或pagesezi的倍数)
);

关于映射的方式flags:
内存映射:又叫匿名映射,最后两个参数无效
文件映射:映射到某个文件
只有文件映射,最后两个参数才有效
MAP_ANONYMOUS:内存映射
MAP_SHAREDMAP_PRIVATE:二选一,文件映射

2. 案例:

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

int main()
{
int *p = mmap(
NULL,
getpagesize(),
PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_SHARED,
0,
0);
*P = 20;
*(p+1) = 30;
*(p+2) = 40;
printf("%d\n", p[2]); //打印出40
munmap(p, 4096);
}
3. 总结:
选择什么样的内存管理方法?
STL
new

malloc小而多的数据
brk/sbrk同类型的大块数据,动态移动指针
mmap/munmap 控制内存的访问/使用文件映射/控制内存共享

二、编程工具与动态库
1. gcc
2. make
3. gdb
4. 其他工具
5. 动态库(共享库)

1. gcc
    -o 输出文件名
    -O-O1-O2-O3//编译优化
    -g-g1-g2-g3//产生调试信息 
    -Wallerror//-Wall 显示所有警告-Werror 将警告当成错误提示
    -w//关闭所有警告
    -c//只编译不连接,产生 .o文件(目标文件)
    -E//预编译
    -S//汇编。 产生 .s文件(汇编文件)

编译4过程是 -E(产生.i) -c(产生.o) -S(产生.s) 自动调用连接器ld

    -D//在命令行定义宏 (宏可以在代码中定义,也可以在命令行上定义)
    -x//指定编译的语言类型 C, C++, .S(汇编), none(自动判定)
    -std=c89  使用标准C89
    -std=c99  使用标准C99

三、 静态库的编译
1. 编译过程 (*.a) a是achieve的缩写
  1.1 编译成目标文件
    -static 可选
    gcc -c -static 代码文件.c  //生产可用于归档的目标代码:代码文件.0
  1.2 归档成静态库
    ar工具 (常用-r -t选项)
    ar -r 静态库 被归档的文件名(上一步代码文件.o) 
    ar -r add.a add.o
    nm工具(查看库中所蕴含的函数列表)
    nm 静态库或动态库或目标文件或执行文件
  1.3 使用静态库
    gcc 代码文件 静态库
小例子:
使用静态库完成如下程序
输入一个菱形半径,打印菱形
输入整型封装成IOTool
菱形打印封装成Graphic
计划:
1. 实现输入
2. 实现菱形
3. 编译静态库
4. 调用静态库

//iotool.c
#include <stdio.h>
int inputInt(const char *info)
{
int r; //返回值
printf("%s:", info);
scanf("%d", &r);
return r;
}
//graphic.c
#include <stdio.h>
void diamond(int r)
{
int x, y;
for(y=0; y<=2*r; y++)
{
for(x=0; x<=2*r; x++)
{
if(y == x+r || y == x-r ||y == -x+r || y == -x+3*r)
{
printf("*");
}
else
{
printf(" ");
}
}
printf("\n");

}
}

编译: gcc -c -static iotool.c

gcc -c -static graphic.c
ar -r demo1.a iotool.o graphic.o
ar -t demo1.a //相当于nm demo1.a

//main.c
main()
{
int r = inputInt("输入菱形半径:");
diamond(r);
}

编译: gcc main.c demo1.a -o main

执行:./main
把静态库作为代码的一部分来编译

总结:
1. 什么是库?
函数等代码封装的二进制已经编译的归档文件
2. ar归档工具
3. 采用库的方式管理代码优点:
容易组织代码
复用
保护代码版权
4. 静态库的“静态”的含义:
编译好的程序运行的时候不依赖库
库作为程序的一部分编译连接
5. 静态库的本质
就是目标文件的集合(归档)
6. -static可选

2. 库的规范与约定
库命名规则:
lib库名.a.主版本号.副版本号.批号
一般就写“lib库名.a”就行了。
ar -r libdemo2.a iotool.o graphic.o
库的使用规则
-l库名
-L库所在的目录
gcc main.c -o main -l demo2 -L.

四、 动态库的编译
1. 什么是动态库(共享库)
动态库是可以执行的,静态库不能执行
但动态库没有main,不能独立执行
动态库不会连接成程序的一部分
程序执行时,必须需要动态库文件
2. 工具
ldd查看程序需要调用的动态库 ,ldd只能查看可执行文件(共享库文件或elf文件)
nm (查看库中的函数符号)
3. 动态库的编译
3.1编译
-c -f pic(可选) (-f 指定文件格式 pic 位置无关代码)
3.2 连接
-shared

编译:gcc -c -fpic iotool.c
gcc -c -fpic graphic.c
(非标准)gcc -shared -odemo3.so iotool.o graphic.o
(标准)gcc -shared -olibdemo4.so iotool.o graphic.o
4. 使用动态库
gcc 代码文件名 动态库文件名
gcc 代码文件名 -l库名 -L动态库所在的路径
gcc main.c -ldemo4 -L. -o main

标准命名规则:
lib库名.so
lib库名.a

问题:
4.1 执行程序怎么加载动态库?
4.2 动态库没有作为执行程序的一部分,为什么连接需要制定动态库及目录?
因为连接器需要确认函数在动态库中的位置
动态库的加载:
1. 找到动态库
2. 加载动态库到内存(系统实现)
3. 映射到用户的内存空间(系统实现)
动态库查找规则:
/lib
/user/lib
LD_LIBRARY_PATH环境变量指定的路径中找
设置当前路径为环境变量:(自己定义的库最好设置好目录,或者放到上述公共目录)
export LD_LIBRARY_PATH=.:~:..:~Walle
缓冲机制:
系统把lib:/user/lib:LD_LIBRARY_PATH里的文件加载到缓冲
/sbin/ldconfig -v 刷新缓冲so中的搜索库的路径
小练习:
输入两个数,计算两个数的和。
要求:输入与计算两个数的和封装成动态库调用

五、 使用libdl.so库
动态库加载原理
动态库中函数的查找已经封装成哭libdl.so
libdl.so里面有4个函数:
dlopen//打开一个动态库
dlsym//在打开的动态库里找一个函数
dlclose//关闭动态库
dlerror//返回错误


//dldemo.c
#include <dlfcn.h>
main()
{
void *handle = dlopen("./libdemo4.so", RTLD_LAZY);
void (*fun)(int) = dlsym(handle, "diamond");
fun(5);
dlclose(handle);
}
gcc dldemo.c -o main -ldl

ldd main
./main

总结:
1. 编译连接动态库
2. 使用动态库
3. 怎么配置让程序调用动态库
4. 掌握某些工具的使用 nm ldd lddconfig objdump strit(去掉多余的信息)

六、 工具make的使用与makefile脚本
背景:
make编译脚本解释
编译脚本makefile
make -f 脚本文件 目标
脚本文件:
1. 文本文件 (例如 demo.mk)
2. 基本构成语法
基本单位目标target
目标名:依赖目标
\t目标指令
\t目标指令

//demo.mk
demo:iotool.c graphic.c main.c
gcc iotool.c -c
gcc graphic.c -c
gcc iotool.o graphic.o -shared -o libdemo.so
gcc main.c -ldemo -L. -o main
make -f demo.mk demo 会生产main可执行文件

posted @ 2012-12-27 13:27 鑫龙 阅读(397) | 评论 (0)编辑 收藏

brk() , sbrk() 的声明如下:
#include <unistd.h>
int brk(void *addr);
void *sbrk(intptr_t increment);
这两个函数都用来改变 "program break" (程序间断点)的位置,这个位置可参考下图:

如 man 里说的:

引用
brk()  and  sbrk() change the location of the program break, which defines the end of the process's data segment (i.e., the program break is the first location after the end of the uninitialized data segment).  
brk() 和 sbrk() 改变 "program brek" 的位置,这个位置定义了进程数据段的终止处(也就是说,program break 是在未初始化数据段终止处后的第一个位置)。
如此翻译过来,似乎会让人认为这个 program break 是和上图中矛盾的,上图中的 program break 是在堆的增长方向的第一个位置处(堆和栈的增长方向是相对的),而按照说明手册来理解,似乎是在 bss segment 结束那里(因为未初始化数据段一般认为是 bss segment)。


首先说明一点,一个程序一旦编译好后,text segment ,data segment 和 bss segment 是确定下来的,这也可以通过 objdump 观察到。下面通过一个程序来测试这个 program break 是不是在 bss segment 结束那里:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
 
 
int bssvar;    //声明一个味定义的变量,它会放在 bss segment 中
 
 
int main(void)
{
    char *pmem;
    long heap_gap_bss;
 
 
    printf ("end of bss section:%p\n", (long)&bssvar + 4);
 
 
    pmem = (char *)malloc(32);          //从堆中分配一块内存区,一般从堆的开始处获取
    if (pmem == NULL) {
        perror("malloc");
        exit (EXIT_FAILURE);
    }
 
 
    printf ("pmem:%p\n", pmem);
 
 
//计算堆的开始地址和 bss segment 结束处得空隙大小,注意每次加载程序时这个空隙都是变化的,但是在同一次加载中它不会改变
    heap_gap_bss = (long)pmem - (long)&bssvar - 4;          
    printf ("1-gap between heap and bss:%lu\n", heap_gap_bss);
 
 
    free (pmem);   //释放内存,归还给堆
     
    sbrk(32);        //调整 program break 位置(假设现在不知道这个位置在堆头还是堆尾)
     pmem = (char *)malloc(32);   //再一次获取内存区
        if (pmem == NULL) {
                perror("malloc");
                exit (EXIT_FAILURE);
        }
 
 
        printf ("pmem:%p\n", pmem);   //检查和第一次获取的内存区的起始地址是否一样
    heap_gap_bss = (long)pmem - (long)&bssvar - 4;  //计算调整 program break 后的空隙
    printf ("2-gap between heap and bss:%lu\n", heap_gap_bss);
 
 
    free(pmem);   //释放
    return 0;
}
下面,我们分别运行两次程序,并查看其输出:


引用
[beyes@localhost C]$ ./sbrk 
end of bss section:0x8049938
pmem:0x82ec008
1-gap between heap and bss:2762448
pmem:0x82ec008
2-gap between heap and bss:2762448
[beyes@localhost C]$ ./sbrk 
end of bss section:0x8049938
pmem:0x8dbc008
1-gap between heap and bss:14100176
pmem:0x8dbc008
2-gap between heap and bss:14100176
从上面的输出中,可以发现几点:
1. bss 段一旦在在程序编译好后,它的地址就已经规定下来。
2. 一般及简单的情况下,使用 malloc() 申请的内存,释放后,仍然归还回原处,再次申请同样大小的内存区时,还是从第 1 次那里获得。
3. bss segment 结束处和堆的开始处的空隙大小,并不因为 sbrk() 的调整而改变,也就是说明了 program break 不是调整堆头部。

所以,man 手册里所说的  “program break 是在未初始化数据段终止处后的第一个位置” ,不能将这个位置理解为堆头部。这时,可以猜想应该是在堆尾部,也就是堆增长方向的最前方。下面用程序进行检验:

当 sbrk() 中的参数为 0 时,我们可以找到 program break 的位置。那么根据这一点,检查一下每次在程序加载时,系统给堆的分配是不是等同大小的:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
 
 
int main(void)
{
        void *tret;
        char *pmem;
 
 
 
        pmem = (char *)malloc(32);
        if (pmem == NULL) {
                perror("malloc");
                exit (EXIT_FAILURE);
        }
 
 
        printf ("pmem:%p\n", pmem);
 
        tret = sbrk(0);
        if (tret != (void *)-1)
                printf ("heap size on each load: %lu\n", (long)tret - (long)pmem);
 
 
    return 0;
}
运行上面的程序 3 次:

引用
[beyes@localhost C]$ ./sbrk 
pmem:0x80c9008
heap size on each load: 135160
[beyes@localhost C]$ ./sbrk 
pmem:0x9682008
heap size on each load: 135160
[beyes@localhost C]$ ./sbrk 
pmem:0x9a7d008
heap size on each load: 135160
[beyes@localhost C]$ ./sbrk 
pmem:0x8d92008
heap size on each load: 135160
[beyes@localhost C]$ vi sbrk.c
从输出可以看到,虽然堆的头部地址在每次程序加载后都不一样,但是每次加载后,堆的大小默认分配是一致的。但是这不是不能改的,可以使用 sysctl 命令修改一下内核参数:
引用
#sysctl -w kernel/randomize_va_space=0
这么做之后,再运行 3 次这个程序看看:
引用
[beyes@localhost C]$ ./sbrk 
pmem:0x804a008
heap size on each load: 135160
[beyes@localhost C]$ ./sbrk 
pmem:0x804a008
heap size on each load: 135160
[beyes@localhost C]$ ./sbrk 
pmem:0x804a008
heap size on each load: 135160
从输出看到,每次加载后,堆头部的其实地址都一样了。但我们不需要这么做,每次堆都一样,容易带来缓冲区溢出攻击(以前老的 linux 内核就是特定地址加载的),所以还是需要保持 randomize_va_space 这个内核变量值为 1 。

下面就来验证 sbrk() 改变的 program break 位置在堆的增长方向处:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
 
 
int main(void)
{
        void *tret;
        char *pmem;
        int i;
        long sbrkret;
 
       pmem = (char *)malloc(32);
        if (pmem == NULL) {
                perror("malloc");
                exit (EXIT_FAILURE);
        }
 
 
        printf ("pmem:%p\n", pmem);
 
         for (i = 0; i < 65; i++) {
                sbrk(1);
                printf ("%d\n", sbrk(0) - (long)pmem - 0x20ff8);   //0x20ff8 就是堆预分配的固定大小;改变后要用 sbrk(0) 再次获取更新后的program break位置
        }
       free(pmem);
 
        
       return 0;
}
运行输出:

引用
[beyes@localhost C]$ ./sbrk 
pmem:0x804a008
1
2
3
4
5

... ...
61
62
63
64

从输出看到,sbrk(1) 每次让堆往栈的方向增加 1 个字节的大小空间。

而 brk() 这个函数的参数是一个地址,假如你已经知道了堆的起始地址,还有堆的大小,那么你就可以据此修改 brk() 中的地址参数已达到调整堆的目的。

实际上,在应用程序中,基本不直接使用这两个函数,取而代之的是 malloc() 一类函数,这一类库函数的执行效率会更高。 还需要注意一点,当使用 malloc() 分配过大的空间,比如超出 0x20ff8 这个常数(在我的系统(Fedora15)上是这样,别的系统可能会有变)时,malloc 不再从堆中分配空间,而是使用 mmap() 这个系统调用从映射区寻找可用的内存空间。


brk/sbrk我觉得:一个程序可寻址的逻辑地址范围是3G(本来是4G,有1G是内核空间不能用),但是这只是逻辑地址,并不可能全部给你用,所以就把预定约定可以给你用的那部分空间(code\data\bss\heap\stack)映射成了物理地址。但是预先约定的总有不够用的时候,此时可以通过brk/sbrk来增加逻辑地址到物理地址映射的范围,即扩大该程序堆空间的大小。

posted @ 2012-12-27 12:15 鑫龙 阅读(774) | 评论 (0)编辑 收藏

(1) _stdcall调用
  _stdcall 是Standard Call的缩写,是C++的标准调用方式,也是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,如果是调用类成员的话,最后一个入栈的是this指针。这些堆栈中的参数由被调函数自身在返回前清空,
使用的指令是 retn X,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈。
  WIN32 Api都采用_stdcall调用方式,这样的宏定义说明了问题:
  #define WINAPI _stdcall
  按C编译方式,_stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@number。
(2) _cdecl调用
  _cdecl是C Declaration[声明]的缩写,表示C/C++语言默认的函数调用方法,也是C/C++的缺省调用方式,所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。_cedcl约定的函数只能被C/C++调用,每一个调用它的
函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。被调用函数无需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。
  由于_cdecl调用方式的参数内存栈由调用者维护,所以变长参数的函数能(也只能)使用这种调用约定。
  由于Visual C++默认采用_cdecl 调用方式,所以VC中中调用DLL时,用户应使用_stdcall调用约定。
  按C编译方式,_cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。
(3) _fastcall调用
  _fastcall 是编译器指定的快速调用方式。由于大多数的函数参数个数很少,使用堆栈传递比较费时。因此_fastcall通常规定将前两个(或若干个)参数由寄存器传递,其余参数还是通过堆栈传递。不同编译器编译的程序规定的寄
存器不同。返回方式和_stdcall相当。
  _fastcall调用较快,它通过CPU内部寄存器传递参数。
 
(4)PASCAL调用
PASCAL 是Pascal语言的函数调用方式,也可以在C/C++中使用,参数压栈顺序与_cdecl和_stdcall相反。
 
归纳如下:
调用约定            入栈参数清理         参数入栈顺序
-----------        --------------         ----------------
cdecl               调用者处理            右->左
stdcall             函数自己处理        右->左
fastcall            函数自己处理        依赖于编译器
pascal             函数自己处理        左->右

posted @ 2012-12-27 11:41 鑫龙 阅读(309) | 评论 (0)编辑 收藏

仅列出标题
共20页: First 6 7 8 9 10 11 12 13 14 Last