`
forfuture1978
  • 浏览: 412950 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

高级Linux程序设计第二章:编写良好的Linux软件

阅读更多

1、同运行环境交互

1.1、命令行

  • 当一个程序从shell启动的时候,其参数列表包括程序名称及所有的命令行参数

% ls -s /

其参数列表包含三项:

第一项是程序名称ls,第二项和第三项分别是两个命令行参数,-s和/

  • main函数可以通过argc和argv两个参数来访问命令行参数列表:argc是命令行参数的个数,argv是命令行参数字符串指针所组成的数组

#include <stdio.h>

int main (int argc, char* argv[])

{

    printf (“The name of this program is ‘%s’.\n”, argv[0]);

    printf (“This program was invoked with %d arguments.\n”, argc - 1);

    /* Were any command-line arguments specified? */

    if (argc > 1) {

        /* Yes, print them. */

        int i;

        printf (“The arguments are:\n”);

        for (i = 1; i < argc; ++i)

            printf ("%s\n", argv[i]);

    }

    return 0;

}

  • 命令行参数分两类:选项和参数

  • 选项有两种格式:

    • 短格式:包括一个连字符和一个字母。短格式选项有利于用户的尽快输入。

    • 长格式:包括两个连字符,及一个有小写字母,大写字母及连字符组成的选项名称。长格式选项更易记易读。

短格式

长格式

目的

-h

--help

显示用法概要

-o filename

--output filename

指定输出文件名

-v

--verbose

打印详细信息

  • 函数getopt_long可以解析短格式和长格式的选项

  • Include <getopt.h>.

  • 提供两个数据结构

    • 第一个数据结构一是一个字符串,包含所有的短格式选项,每个选项一个字母。需要跟一个参数的选项后面有一个冒号。char* const short_options = “ho:v”;

    • 第二个数据结构是一个option结构体组成的数组。数组中的每一项对应一个长选项,包括四个域:

      • 第一个域是长选项的名称

      • 第二个域表示此选项是否需要跟一个参数

      • 第三个域是NULL

      • 第四个域是一个字符,是此长选项对应的短选项

      • 数组的最后一项为四个域都是0的项

 

const struct option long_options[] = {

{ “help”, 0, NULL, ‘h’ },

{ “output”, 1, NULL, ‘o’ },

{ “verbose”, 0, NULL, ‘v’ },

{ NULL, 0, NULL, 0 }

};

  • getopt_long函数的参数依次为argc, argv, 短选项字符串,长选项数组

  • 每次getopt_long被调用时,其解析一个选项,返回相应的短选项。当没有选项的时候返回-1

  • 应该在循环中调用getopt_long,然后用switch根据返回的短选项进行相应的处理

  • 当getopt_long遇到一个非法的选项时,其打印出错误信息,并返回'?'

  • 当一个选项需要跟一个参数的时候,有一个全局变量optarg指向此参数字符串

  • 当getopt_long完成解析所有的选项后,有一个全局变量optind包含第一个非选项参数在argv中的偏移量

 

#include <getopt.h>

#include <stdio.h>

#include <stdlib.h>

/* The name of this program. */

const char* program_name;

/* Prints usage information for this program to STREAM (typically stdout or stderr), and exit the program with EXIT_CODE. Does not return. */

void print_usage (FILE* stream, int exit_code)

{

    fprintf (stream, “Usage: %s options [ inputfile ... ]\n”, program_name);

    fprintf (stream,

        “ -h --help Display this usage information.\n”

        “ -o --output filename Write output to file.\n”

        “ -v --verbose Print verbose messages.\n”);

    exit (exit_code);

}

/* Main program entry point. ARGC contains number of argument list elements; ARGV is an array of pointers to them. */

int main (int argc, char* argv[])

{

    int next_option;

    /* A string listing valid short options letters. */

    const char* const short_options = “ho:v”;

    /* An array describing valid long options. */

    const struct option long_options[] = {

    { “help”, 0, NULL, ‘h’ },

    { “output”, 1, NULL, ‘o’ },

    { “verbose”, 0, NULL, ‘v’ },

    { NULL, 0, NULL, 0 } /* Required at end of array. */

    };

    /* The name of the file to receive program output, or NULL for

    standard output. */

    const char* output_filename = NULL;

    /* Whether to display verbose messages. */

    int verbose = 0;

    /* Remember the name of the program, to incorporate in messages.

    The name is stored in argv[0]. */

    program_name = argv[0];

    do {

        next_option = getopt_long (argc, argv, short_options, long_options, NULL);

        switch (next_option)

       {

        case ‘h’: /* -h or --help */

        /* User has requested usage information. Print it to standard output, and exit with exit code zero (normal termination). */

            print_usage (stdout, 0);

        case ‘o’: /* -o or --output */

        /* This option takes an argument, the name of the output file. */

            output_filename = optarg;

            break;

        case ‘v’: /* -v or --verbose */

            verbose = 1;

            break;

        case ‘?’: /* The user specified an invalid option. */

        /* Print usage information to standard error, and exit with exit code one (indicating abnormal termination). */

            print_usage (stderr, 1);

        case -1: /* Done with options. */

            break;

        default: /* Something else: unexpected. */

            abort ();

        }

    } while (next_option != -1);

    /* Done with options. OPTIND points to first nonoption argument. For demonstration purposes, print them if the verbose option was specified. */

    if (verbose) {

        int i;

        for (i = optind; i < argc; ++i)

            printf (“Argument: %s\n”, argv[i]);

    }

    /* The main program goes here. */

    return 0;

}

 

1.2、标准输入输出

  • C标准库提供三个流:

    • 标准输入流:stdin

    • 标准输出流:stdout

    • 标准错误流:stderr

  • 三个流出了用C标准库访问外,还可以直接通过Unix API直接对文件描述符进行操作

  • stdin的文件描述符为0,stdout为1,stderr为2

  • 标准输出流和标准错误流都可以重定向到一个文件或者一个管道(pipe)

% program > output_file.txt 2>&1

% program 2>&1 | filter

2<&1表示文件描述符2也即stderr将合并到文件描述符1也即stdout中。

欲重定向stdout和stderr到一个文件,2>&1必须跟在一个文件后面。

欲重定向stdout和stderr到一个管道,2>&1必须在一个管道前面。

  • stdout有缓冲,此缓存可以通过fflush(stdout)显式的写出。

  • stderr没有缓冲。

1.3、程序退出码

  • 当一个程序结束的时候,其在退出码中返回程序的退出状态。

  • 退出码是一个整形数。

  • 退出码为0表示程序成功退出,退出码非零表示程序因错误而退出。

  • 最近运行的程序的退出码可以通过一个特殊的shell变量$?得到。

 

% ls /

bin coda etc lib misc nfs proc sbin usr

boot dev home lost+found mnt opt root tmp var

% echo $?

0

% ls bogusfile

ls: bogusfile: No such file or directory

% echo $?

1

 

1.4、环境变量

  • 环境变量是一组变量及其值的组合。

  • 常用的环境变量:

    • USER保存用户名

    • HOME保存主目录的路径

    • PATH保存一个以冒号分隔的一系列目录的列表。Linux将从这些目录中寻找运行的命令。

  • printenv命令可以打印所有的环境变量。

  • 在程序中,可以用getenv函数得到环境变量的值。

  • 可以用函数setenv和unsetenv来设置和清除环境变量。

  • 可以通过一个特殊的全局变量environ来得到所有的环境变量。

 

#include <stdio.h>

/* The ENVIRON variable contains the environment. */

extern char** environ;

int main ()

{

    char** var;

    for (var = environ; *var != NULL; ++var)

    printf (“%s\n”, *var);

    return 0;

}

  • 当一个程序运行起来的时候,其继承了启动程序的环境变量。

  • 环境变量多用于给程序传递配置信息。

 

1.5、使用临时文件

  • 临时文件存储在/tmp文件夹中。

  • 不同的进程应该使用不同的临时文件。

  • 没有授权的用户不能通过改变或者替换临时文件来改变程序运行的行为。

  • 临时文件名应该以不可预测的方式生成。

  • mkstemp函数

    • 根据一个文件名模板创建一个独一无二的文件名。

    • 其创建的文件应被设置了除当前用户外其他用户无法访问的权限。

    • 其以可读可写的方式打开临时文件。

    • 被mkstemp的临时文件不会被自动删除。

    • 如果一个临时文件仅仅在一个程序内部使用,而不会被其他程序使用,则应该在mkstemp生成临时文件后,对临时文件进行unlink,从而程序退出时临时文件会被删除。

#include <stdlib.h>

#include <unistd.h>

/* A handle for a temporary file created with write_temp_file. In this implementation, it’s just a file descriptor. */

typedef int temp_file_handle;

/* Writes LENGTH bytes from BUFFER into a temporary file. The temporary file is immediately unlinked. Returns a handle to the temporary file. */

temp_file_handle write_temp_file (char* buffer, size_t length)

{

    /* Create the filename and file. The XXXXXX will be replaced with characters that make the filename unique. */

    char temp_filename[] = “/tmp/temp_file.XXXXXX”;

    int fd = mkstemp (temp_filename);

    /* Unlink the file immediately, so that it will be removed when the file descriptor is closed. */

    unlink (temp_filename);

    /* Write the number of bytes to the file first. */

    write (fd, &length, sizeof (length));

    /* Now write the data itself. */

    write (fd, buffer, length);

    /* Use the file descriptor as the handle for the temporary file. */

    return fd;

}

/* Reads the contents of a temporary file TEMP_FILE created with write_temp_file. The return value is a newly allocated buffer of those contents, which the caller must deallocate with free. LENGTH is set to the size of the contents, in bytes. The temporary file is removed. */

char* read_temp_file (temp_file_handle temp_file, size_t* length)

{

    char* buffer;

    /* The TEMP_FILE handle is a file descriptor to the temporary file. */

    int fd = temp_file;

    /* Rewind to the beginning of the file. */

    lseek (fd, 0, SEEK_SET);

    /* Read the size of the data in the temporary file. */

    read (fd, length, sizeof (*length));

    /* Allocate a buffer and read the data. */

    buffer = (char*) malloc (*length);

    read (fd, buffer, *length);

    /* Close the file descriptor, which will cause the temporary file to go away. */

    close (fd);

    return buffer;

}

  • 如果不需要把临时文件传给另一个程序,则可以用tmpfile函数

    • 其创建和打开一个临时文件

    • 返回一个指向文件的指针

    • 临时文件已经被unlink了,当文件指针被关闭的时候(fclose)或者程序结束的时候,临时文件会自动删除。

 

2、编写及使用库

2.1、静态链接库(Archives)

  • 静态链接库仅仅将一系列对象文件(.obj)归档为一个文件。

  • 当链接器遇到一个静态链接库的时候,其将需要的对象文件提取出来,并将它链接到程序文件中。

  • 静态链接库可用ar命令创建。

% ar cr libtest.a test1.o test2.o

  • 当链接静态链接库的时候,静态链接库的指定顺序是很重要的。

  • 当链接器依次解析命令行中指定的静态链接库时,其首先在已经解析过的对象文件中寻找所有被引用了,但是没有定义的标识符,然后在静态链接库中将包含这些标识符的对象文件抽取出来,链接到程序文件中。

2.2、动态链接库(Shared Libraries)

  • 当一个动态链接库被链接到一个程序文件中的时候,最后的程序文件并不包括动态链接库中的代码,而是仅仅包括对动态链接库的引用。

  • 动态链接库可被多个程序共享。

  • 动态链接库不仅仅是一组对象文件的简单归档,而是多个对象文件的重新组合。

  • 当一个程序链接一个动态链接库的时候,其或者包括动态链接库的所有代码,或者不包括动态链接库的任何代码,而不像静态链接库一样包括程序需要的那一部分。

  • 当创建一个动态链接库德时候,需要用-fPIC参数编译对象文件。

% gcc -c -fPIC test1.c

  • 可以将多个对象文件合并成一个动态链接库。

% gcc -shared -fPIC -o libtest.so test1.o test2.o

  • 链接一个动态链接库和链接一个静态链接库一样。

% gcc -o app app.o -L. –ltest

  • 由于链接的时候没有指定.so还是.a,链接器优先选择动态链接库,除非显式指定选择静态链接库。

% gcc -static -o app app.o -L. –ltest

  • ldd命令显示链接到程序的所有动态链接库。

  • 当链接一个动态链接库到一个程序的时候,链接器并不保存动态链接库的全路径,而仅仅是动态链接库的名称。

  • 当程序运行的时候,系统首先寻找动态链接库,然后加载它。

  • 默认情况下,系统在/lib和/usr/lib文件夹下寻找动态链接库。

  • 当链接动态链接库到程序的时候,如果指定-Wl,-rpath参数,则系统会在/usr/local/lib下寻找动态链接库。

  • 设定LD_LIBRARY_PATH环境变量,程序运行时会在此环境变量指定的文件夹下寻找动态链接库。

Create test1.c

void f1()

{

printf("Test 1 fucntion 1");

}

gcc -c -fPIC test1.c

-rw-rw-r-- 1 liuchao liuchao 1124 Jul 31 19:51 test1.o

Create test2.c

void f2()

{

printf("Test 2 function 2");

}

gcc -c -fPIC test2.c

-rw-rw-r-- 1 liuchao liuchao 1124 Jul 31 19:52 test2.o

Create libstatictest.a

ar cr libstatictest.a test1.o test2.o

-rw-rw-r-- 1 liuchao liuchao 2508 Jul 31 19:53 libstatictest.a

Create libdynamictest.so

gcc -shared -fPIC -o libdynamictest.so test1.o test2.o

-rwxrwxr-x 1 liuchao liuchao 4573 Jul 31 19:54 libdynamictest.so

Create app.c

int main(int argc, char* argv[])

{

f2();

}

gcc -c app.c

-rw-rw-r-- 1 liuchao liuchao 772 Jul 31 19:55 app.o

Create staticapp

gcc -o staticapp app.o -L. –lstatictest

-rwxrwxr-x 1 liuchao liuchao 4861 Jul 31 19:56 staticapp

Create dynamicapp

gcc -o dynamicapp app.o -L. -ldynamictest

Run staticapp

./staticapp

Test 2 function 2

Run dynamicapp

./dynamicapp

./dynamicapp: error while loading shared libraries: libdynamictest.so: cannot open shared object file: No such file or directory

Ldd staticapp

ldd staticapp

linux-gate.so.1 => (0x0060d000)

libc.so.6 => /lib/libc.so.6 (0x007a3000)

/lib/ld-linux.so.2 (0x00786000)

Ldd dynamicapp

ldd dynamicapp

linux-gate.so.1 => (0x00861000)

libdynamictest.so => not found

libc.so.6 => /lib/libc.so.6 (0x00111000)

/lib/ld-linux.so.2 (0x00786000)

export LD_LIBRARY_PATH=.

export LD_LIBRARY_PATH=.

echo $LD_LIBRARY_PATH

.

./dynamicapp

Test 2 function 2

2.3、库依赖

  • 一个库会经常依赖另一个库:例如libtiff库包含读写TIFF图片文件的函数,其依赖libjpeg库和libz库。

  • 创建使用libtiff库的程序:

#include <stdio.h>

#include <tiffio.h>

int main (int argc, char** argv)

{

TIFF* tiff;

tiff = TIFFOpen (argv[1], “r”);

TIFFClose (tiff);

return 0;

}

  • 编译程序,链接libtiff

% gcc -o tifftest tifftest.c –ltiff
  • 链接器选择动态链接库libtiff。由于libtiff依赖libjpeg和libz,所以他们也被链接进来。

% ldd tifftest

libtiff.so.3 => /usr/lib/libtiff.so.3 (0x4001d000)

libc.so.6 => /lib/libc.so.6 (0x40060000)

libjpeg.so.62 => /usr/lib/libjpeg.so.62 (0x40155000)

libz.so.1 => /usr/lib/libz.so.1 (0x40174000)

/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

  • 如果是静态链接库,其依赖的库不会被链接进来。

% gcc -static -o tifftest tifftest.c -ltiff

/usr/bin/../lib/libtiff.a(tif_jpeg.o): In function ‘TIFFjpeg_error_exit’:

tif_jpeg.o(.text+0x2a): undefined reference to ‘jpeg_abort’

/usr/bin/../lib/libtiff.a(tif_jpeg.o): In function ‘TIFFjpeg_create_compress’:

tif_jpeg.o(.text+0x8d): undefined reference to ‘jpeg_std_error’

tif_jpeg.o(.text+0xcf): undefined reference to ‘jpeg_CreateCompress’

...

  • 如果欲链接静态链接库及其依赖的静态链接库,必须显式指定。

% gcc -static -o tifftest tifftest.c -ltiff -ljpeg –lz
  • 如果两个静态链接库是相互依赖的,则应该在命令行上指定两次。

% gcc -static -o app app.o -lfoo -lbar –lfoo

例如程序app中的函数void a()在libfoo.a的A.o中实现,A.o中的函数void b()在libbar.a的B.o中实现,B.o中的函数void c()在libfoo.a的C.o中实现。则链接器的工作过程如下:

  • 当扫描到-lfoo的时候,发现app的void a()没有定义,于是将其中的A.o提取出来,链接到app中。
  • 当扫描到-lbar的时候,发现A.o中的函数void b()没有定义,于是将其中的B.o提取出来,链接到app中。
  • 当扫描到-lfoo的时候,发现B.o的void c()函数没有定义,于是将其中的C.o提取出来,链接到app中。

 

2.4、动态加载和卸载库

  • 调用dlopen函数可以加载动态链接库libtest.so

dlopen (“libtest.so”, RTLD_LAZY)
  • 要使用动态加载函数,要include<dlfcn.h>,并且链接的时候要指定-ldl参数来链接libdl库。

  • dlopen的返回值是一个void *的指针,作为访问动态链接库的句柄。

  • 可以讲句柄传给函数dlsym来得到加载的动态链接库中的某个函数的地址。

void* handle = dlopen (“libtest.so”, RTLD_LAZY);

void (*test)() = dlsym (handle, “my_function”);

(*test)();

dlclose (handle);

  • dlopen和dlsym失败的时候返回NULL,可以用函数dlerror得到可描述失败原因的信息。

  • dlclose函数卸载动态链接库。

2
1
分享到:
评论

相关推荐

    嵌入式Linux程序设计案例与实验教程(配套光盘)第二部分

    第1章 Linux开发基础.1 1.1 Linux系统概述1 1.1.1 Linux简介1 1.1.2 Linux系统的特点1 1.1.3 Linux系统的组成2 1.2 Linux系统的使用2 实验1.1 熟悉Linux基本命令与文件目录系统2 1.3 全屏幕编辑器与vi6 ...

    嵌入式Linux程序设计案例与实验教程(配套光盘)第一部分

    第1章 Linux开发基础.1 1.1 Linux系统概述1 1.1.1 Linux简介1 1.1.2 Linux系统的特点1 1.1.3 Linux系统的组成2 1.2 Linux系统的使用2 实验1.1 熟悉Linux基本命令与文件目录系统2 1.3 全屏幕编辑器与vi6 ...

    Linux高性能服务器编程PDF带目录高清版

    第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多...

    嵌入式Linux程序设计案例与实验教程(配套光盘)第三部分

    第1章 Linux开发基础.1 1.1 Linux系统概述1 1.1.1 Linux简介1 1.1.2 Linux系统的特点1 1.1.3 Linux系统的组成2 1.2 Linux系统的使用2 实验1.1 熟悉Linux基本命令与文件目录系统2 1.3 全屏幕编辑器与vi6 ...

    嵌入式Linux程序设计案例与实验教程-实例代码

    第1章 Linux开发基础.1 1.1 Linux系统概述1 1.1.1 Linux简介1 1.1.2 Linux系统的特点1 1.1.3 Linux系统的组成2 1.2 Linux系统的使用2 实验1.1 熟悉Linux基本命令与文件目录系统2 1.3 全屏幕编辑器与vi...

    Linux高性能服务器编程清晰PDF+源码.7z

    第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、 I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多...

    游双-Linux高性能服务器编程(高清pdf+源码)

    第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多...

    Linux高性能服务器编程高清PDF(带书签)+源码

    第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多...

    边干边学Linux__第二版_doc格式

    第二部分 Linux 内核分析与实践 第9章 编译Linux内核 9.1 Linux内核 9.2 查找并且下载一份内核源代码 9.3 部署内核源代码 9.4 配置内核 9.5 编译内核和模块 9.6 了解Linux内核的启动 9.7 应用grub配置启动文件 9.8 ...

    LINUX设备驱动第三版_588及代码.rar

    第二章 构造和运行模块 设置测试系统 Hello World模块 核心模块与应用程序的对比 编译和装载 内核符号表 预备知识 初始化和关闭 模块参数 在用户空间编写驱动程序 快速参考 第三章 字符设备驱动程序 ...

    linux设备驱动程序

    第二章 构造和运行模块 设置测试系统 Hello world模块 核心模块与应用程序的对比 编译和装载 内核符号表 预备知识 初始化和关闭 模块参数 在用户空间编写驱动程序 快速参考 ch03.第三章 字符设备驱动程序 scull的...

    C程序设计语言(第2版·新版中文)

    windows程序设计第五版(中文版) 贪吃蛇 游戏 C++ C# 程序开发范例宝典(第二版) 源代码 20~21 C、C++面试题大全 Java程序员就业面试大全 程序员面霸手册(笔试面试的葵花宝典) visual c++6.0应用编程150例...

    TCP/IP协议与网络编程技术

    第7章:介绍了网络程序设计有关的基础知识、一个网络程序入门实例和Winsock中编写网络程序常用的建立连接、传输数据、关闭连接等有关的函数。 第8章:介绍了TCP程序设计流程、基于C/C++的TCP程序设计实例和基于Java...

    操作系统实验 体验linux基本操作 子进程的创建

    通过研习参考书《Linux程序设计》(第4版)(N.Matthew,R. Stones(译者:陈健,宋健建),人民邮电出版社,2010)第11章“进程和信号”的内容,熟悉进程创建、进程协同工作等相关函数如fork()、system()、exec()、...

    Android程序设计基础

    丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9787115215369 上架时间:2009-11-6 出版日期:2009 年11月 开本:16开 页码:196 版次:1-1 编辑推荐  你的第一本Android书.  Pragmatic系列...

    软件工程期中知识点总结

    第一章 概述 1.软件工程注重于大规模的软件系统,由多个人编写,代码量一般都是成千上万行,而程序只是小型的软件,一个人就可以完成。软件工程就是讲解如何...第二章 软件项目管理 第三章 需求工程 第四章 软件设计

    linux管理员指南

    附录 附录A介绍随Red Hat Linux发行版本附送的各种程序设计语言。附录B列出Linux操作系统中常用的一些软件的情况,这将使你能够把Linux作为你最基本的桌面操作系统。这本书的第一稿是使用StarOffice 5.0 for Linux...

    Linux DeviceDrivers 3rd Edition

    第二章 构造和运行模块 21 设置测试系统 21 Hello World模块 22 核心模块与应用程序的对比 24 编译和装载 28 内核符号表 33 预备知识 35 初始化和关闭 36 模块参数 40 在用户空间编写驱动程序 42 .快速...

    linux c 程序设计大全 源代码(编译运行测试之后验证可用)

    1-9章的内容为C语言基础,只编写编译测试了部分代码,10-21中部分重名的代码实例一般都是编写了第二个实例,部分重名的代码会用1,2区别,例如copy1.c,copy2.c等。22章之后的代码尚未亲测,后续会传上来。

Global site tag (gtag.js) - Google Analytics