分类 linux 下的文章

objdump命令的使用

objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它还有其他作用,下面以ELF格式可执行文件test为例详细介绍:

objdump -f test
显示test的文件头信息

objdump -d test
反汇编test中的需要执行指令的那些section

objdump -D test
与-d类似,但反汇编test中的所有section

objdump -h test
显示test的Section Header信息

objdump -x test
显示test的全部Header信息

objdump -s test
除了显示test的全部Header信息,还显示他们对应的十六进制文件代码

举例:

将C源代码和反汇编出来的指令对照:

  1. 编译成目标文件(要加-g选项)
    gcc -g -o test.c
  2. 输出C源代码和反汇编出来的指令对照的格式
    objdump -S test.o

如下:

00000000004004c4 <main>:
#include <stdio.h>
#define ARR_LENGTH 3
int main(void)
{
  4004c4:    55                       push   %rbp
  4004c5:    48 89 e5                 mov    %rsp,%rbp
  4004c8:    48 83 ec 30              sub    $0x30,%rsp
    int a=1,b=2,c=3,*p[ARR_LENGTH] = {&a,&b,&c};
  4004cc:    c7 45 f8 01 00 00 00     movl   $0x1,-0x8(%rbp)
  4004d3:    c7 45 f4 02 00 00 00     movl   $0x2,-0xc(%rbp)
  4004da:    c7 45 f0 03 00 00 00     movl   $0x3,-0x10(%rbp)
  4004e1:    48 8d 45 f8              lea    -0x8(%rbp),%rax
  4004e5:    48 89 45 d0              mov    %rax,-0x30(%rbp)
  4004e9:    48 8d 45 f4              lea    -0xc(%rbp),%rax
  4004ed:    48 89 45 d8              mov    %rax,-0x28(%rbp)
  4004f1:    48 8d 45 f0              lea    -0x10(%rbp),%rax
  4004f5:    48 89 45 e0              mov    %rax,-0x20(%rbp)
    for(int i = 0;i < ARR_LENGTH;i++)
  4004f9:    c7 45 fc 00 00 00 00     movl   $0x0,-0x4(%rbp)
  400500:    eb 27                    jmp    400529 <main+0x65>
    {
        printf("*p[%d] = %d\n",i,*p[i]);
  400502:    8b 45 fc                 mov    -0x4(%rbp),%eax
  400505:    48 98                    cltq   
  400507:    48 8b 44 c5 d0           mov    -0x30(%rbp,%rax,8),%rax
  40050c:    8b 10                    mov    (%rax),%edx
  40050e:    b8 38 06 40 00           mov    $0x400638,%eax
  400513:    8b 4d fc                 mov    -0x4(%rbp),%ecx
  400516:    89 ce                    mov    %ecx,%esi
  400518:    48 89 c7                 mov    %rax,%rdi
  40051b:    b8 00 00 00 00           mov    $0x0,%eax
  400520:    e8 93 fe ff ff           callq  4003b8 <printf@plt>

全面解析C语言中可变参数列表

在上一篇blogLinux内核源码分析--文件系统(三、buffer.c)中最后第二个函数struct buffer_head * breada(int dev,int first, ...) ,里面涉及到可变参数列表,所以就干脆来总结下可变参数列表问题。

大众版

首先来看下怎么使用,然后再来总结下其中一些问题:

 #include<stdio.h>
 #include<stdarg.h>
 
 int test(int num, ...)
 {
     int i, result = 0;
 
     va_list ap;//这里写的什么list,(搞得好像是得到可变参数列表头一样)其实它就是个字符指针:char * 
     va_start(ap, num);// 这里把上面得到的字符指针,后移动4个字节,就是跳过num的内存地址
     printf("num:%d, *ap:%d\n", num, *ap);// 这里打印下就会看出,*ap 跳过了num指向了下一个参数
     
     for (i = 0; i < num; i++)//这里num表示可变参数列表中有多少个参数(num本身算不算,由自己觉得,这里是不算入参数个数的)
     {   
         result = va_arg(ap, int);//这里把ap往后跳过4个字节(sizeof(int)大小)指向下一个参数,返回的是当前参数(而非下一个参数)
         printf("in for  result:%d,  *ap:%d\n", result, *ap);//这里打印下,可以看出,ap总是指向result后面的那个参数
     }   
     va_end(ap);//结束标志
 
     return result;
 }
 //下面是测试函数
 int main()
 {
     int i = 4, j = 1, k = 2, g = 3, z = 4, m = 10; 
     printf("result:%d\n", test(i, j, k, g, z, m));
     return 0;
 }

看完后估计大家都会觉得很简单,这是大众版的,就是可变参数列表中第一个参数用来表示可变参数列表有多少个(至于算不算上他自己,那就看你程序自己设计了);

真实版

下面来看下真实版的程序:


 #include<stdio.h>
 #include<stdarg.h>
 
 int test(int first, ...){
 
     va_list args;
     va_start(args, first);
     printf("args:%d  first:%d\n", *args, first);
 
     while( (first = va_arg(args, int)) >= 0 ){
         printf("*args:%d  first:%d\n", *args, first);
     }   
     va_end(args);
     return 0;
 }
 int main()
 {
     int a = 100, i = 1, j = 2, k = 3, g = -1; 
 
     printf("test1:\n");
     test(a, i, j, k, g); 
 
     printf("test2:\n");
     a = 200, i = 11, j = 12, k = 13; 
     test(a, i, j, g); 
 
     return 0;
 }

这个和上面是一样的,唯一不同的是可变参数列表的第一个参数,没有用来当作参数个数,而是把最后一个参数用负数作为结束标志,可变参数列表第一个参数在这里的作用仅仅是为了得到可变参数列表的起始地址;(貌似这个程序上面的那个有点冗余,但是对照这个程序兴许会更好理解可变参数列表的应用)

实际原理

可变参数列表的实现是由几个宏组成的,在文件include/stdarg.h(我看的源码是0.11版本的,但是上面编译是在2.6版本内核上的,根据编译运行得到的结果,可以推理出可变参数列表实现程序在这两个版本中是一样的,也就是说两个版本内核中可变参数列表代码是相同的)中:
va_list 定义某个变量,其本质就是:

typedef char *va_list;//字符指针类型

va_start(ap, type)开始获取可变参数列表中的第一个参数(...里面的第一个),也就是跳过第一个参数(这里的第一个是num或first)

#ifndef __sparc__
#define va_start(AP, LASTARG)                         \
 (AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))//ap指向下一个参数,lastarg不变
#else
#define va_start(AP, LASTARG)                         \
 (__builtin_saveregs (),                        \
  AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG))) //跳过下第一个参数,指向第二个参数内存地址
#endif

//对type向上取整 取int的整 4,然后乘上int整型4的倍数
#define __va_rounded_size(TYPE)  \
  (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

va_arg(args, int)循环获取到可变参数列表中的参数,args指向下一个参数地址,返回的则是当前参数地址

//  first=va_arg(args,int)
#define va_arg(AP, TYPE)                        \//ap指向下一个类型的参数
 (AP += __va_rounded_size (TYPE),                    \//返回ap - sizeof(type)参数,即前一个参数
  *((TYPE *) (AP - __va_rounded_size (TYPE))))

//对type向上取整 取int的整 4,然后乘上int整型4的倍数
#define __va_rounded_size(TYPE)  \
  (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

最后一个va_end(ap)结束标志(没有好像也没关系),可能只是在程序中作为一个可变参数列表的结束标志而已(stdarg.h里面只是仅仅定义了下,没有实现的代码部分)。从上面的分析就可以知道第二个程序(所谓的真实版)是怎么来的了,其实根据实现原理来讲第一个参数是没有规定表示可变参数个数的,而是用来得到可变参数列表的起始地址。

自己实现可变参数列表

如果看懂了上面的可变参数列表原理,那么自己动手写个可变参数列表程序解析下多个参数问题,其实很简单的:

#include<stdio.h>//这里不需要用到可变参数列表解析宏,所以不用包含stdarg.h头文件
 
 int test(int first, ...)
 {
     int count = 0;
 
     char *ap ;
     ap = (char*)(&first);//得到参数列表的起始地址
 
     ap = ap + 4;//加上4跳过第一个参数first
     while(count++ < first){//这里first参数表示有多少个参数
         printf("*ap:%d\n", *((int *)ap));//把参数列表中的参数都挨个打印出来
         ap += 4;//指向下一个参数
     }   
     return *ap;
 }
 //下面是测试程序
 int main(void)
 {
     int i = 1, j = 2, k = 3, z = 4, num = 4;
     printf("test1:\n");
     test(num, i, j, k, z); 
 
     num = 3;
     printf("\ntest2:\n");
     test(num, i, j, k); 
     return 0;
 
 }

可变参数列表缺陷

不知道大家有没有发现,上面的所有可变参数都是int型的,就算你用字符作为参数(char  c = 'a'),在可变参数里面(三个小点里面)也同样会为他分配4个字节地址空间;如果是浮点型那么就会报错,至于具体为什么报错我还没有去看那三个点的实现(就是可变参数列表内存分配问题,我估计在库文件里面,后期看到再分析下);为什么不能用其他类型(非整型)的参数呢?我估计是开始设计的时候,为了简便,统一用整型(这样更好实现嘛),可是现实中这样就存在一个缺陷问题,那怎么解决让可变参数列表中可以使用浮点型、字符串呢?
答案是我也不知道,我只是想到了个方案,用第一个参数来表示可变参数列表中的类型,当然不能用int型了,而是用字符数组来表示,这也有个限制那就是只能有一个字符串而且是放在最后(当然要传多个字符串也可以在数组中自己设置),因为字符串长度不知道,指针不知道偏移多少位,所以把它放在最后就好了。
#include<stdio.h>
 
 int test(char num[], int i, char c, float f, char *s)
 {
     int ti;
     char tc;
     float tf;
     char *ts;
 
     printf("int:%p, char:%p, float:%p, char:%p\n", &i, &c, &f, &s);
 
     int count = 0;
 
     char *ap ;
 
     ap =  num + sizeof(num);
 
     while(num[count] != '\0'){
         switch(num[count++]){
 
             case 'i':
                 ti = *((int *)ap);
                 ap += sizeof(int);
                 printf("ti:%d\n", ti);
                 break;
             case 'c':
                 tc = *ap++;
                 printf("tc:%c\n", tc);
                 break;
 
             case 'f':
                 tf = *((float*)ap);
                 ap += sizeof(float);
                 printf("tf:%f\n", tf);
                 break;

             case '1':
                 ts = ap;
                 printf("ts:%s\n", ts);
                 break;
 
             default:
                 printf("No the type!\n");
         }
     }
 
     return 0;
 }
 
 int main(void)
 {
     int i = 12;
     char c = 'a';
     float f = 5.20;
     char *s = "yuzhihui";
 
     char num[4] = {'\0'};
      num[0] = 'i';
      num[1] = 'c';
      num[2] = 'f';
      num[3] = '1';
 
     test(num, i, c, f, s);
 
     return 0;
 
 }

上面就是我设计的一个雏形,大概意思就是这样,但运行时得不到正确结果,原因很有可能是可变参数列表中出问题了。因为...可变参数列表其在内存中的地址是连续的,而我上面的却不一定是连续的(模拟的可变参数列表),还有字节之间对齐问题(因为整型刚好是4个字节对齐的不存在这个问题).但大概意思就是这样的。
这个缺陷的改变可能并没有太多实际意义,我也是突发奇想,高手轻喷。哈哈
如果有什么不正确之处,欢迎大家指正,一起努力,共同学习!!
原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/43734663

linux编程学习 01 linux IO 系统调用

一、文件I/O系统调用简介

1、标准库函数

遵守ISO标准,基于流的I/O,对文件指针(FILE结构体)进行操作

2、系统调用

兼容POSIX标准,基于文件描述符的I/O,对文件描述符进行操作,包括:
open(): 打开文件
create():创建文件
close(): 关闭文件
read(): 读取文件
write(): 写入文件
lseek(): 文件定位
这些不带缓存的函数都是内核提供的系统调用。它们不是ANSI C的组成部分,但是POSIX的组成部分。

二、文件描述符

1、对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。
2、在POSIX应用程序中,整数0、1、2被替换成符号常数 STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO。这些常数都定义在头文件 中。
3、文件描述符的范围是0-OPEN_MAX,早期的unix版本是19(允许每个进程打开20个文件),现在很多系统设置成23,linux为1024

标准文件指针

  • stdin 0
  • stdout 1
  • stderr 2

fdopen()

FILE *fdopen(int fd,const char* mode);

文件描述符 -----> 文件指针 (fd -----> FILE*)

fileno

int fileno(FILE *stream)

文件指针 -----> 文件描述符 (FILE* -----> fd)

三,函数介绍

一、open函数

1、 头文件(后面都是这个格式)

#include <sys/types.h>.        // 提供mode_t类型
#include <sys/stat.h>          // 提供open()函数的符号
#include <fcntl.h>             // 提供open()函数
#include <unistd.h>            // 提供close()函数
#include <errno.h>             // 提供strerror()函数和errno
#include <stdio.h>             // 提供输出函数
#include <string.h>            // 提供strerror函数

2、函数原型

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

返回: 若成功为文件描述符,若出错为-1
功能: 打开或创建一个文件
参数
pathname:要打开或者创建的文件路径
flags:用来说明此函数的多个选择项
用下列一个或者多个常数进行按位或运算构成flags参数,这些常数定义在/usr/include/bits/fcntl.h中,在include fcntl.h时,会包含这个文件

常量名解释
O_RDONLY只读
O_WRONLY只写
O_RDWR读写
O_CREAT如果指定的文件不存在,则按照mode参数指定的文件权限来创建文件
O_EXCL如果同时指定了O_CREAT,而文件已经存在,则出错。可测试一个文件是否存在。但在网络文件系统进行操作时却没有保证
O_DIRECTORY如果参数pathname不是一个目录,则open出错
O_TRUNC如果文件存在,而且为只读或只写成功打开,则将其长度截短为0
O_APPEND以追加模式打开文件,每次写时加到文件尾端,但在网络文件系统进行操作时没有保证
O_NONBLOCK如果pathname指定的是是一个FIFO,一个块特殊文件或者一个字符特殊文件,则此选择项为此文件的本次打开操作和后续I/O操作设置非阻塞方式

mode:新建文件的访问权限,对于open函数而言,仅当创建新文件时才使用第三个参数。

二、creat函数

int creat(const char *pathname, mode_t mode);
返回: 若成功为只写打开的文件描述符,若出错为-1
功能: 创建一个新文件
等同于open (pathname, O_WRONLY | O_CREAT | O_TRUNC, mode) ;
以只写方式打开所创建的文件。

三、close函数

int close(int fd);

返回: 若成功为0,若出错为-1
功能: 关闭一个打开的文件
参数
fd:已打开文件的文件描述符
当一个进程终止时,他所有打开的文件都由内核自动关闭

四、read函数

ssize_t read(int fd, void *buf, size_t count);
返回: 读到的字节数,若已到文件尾为0,若出错为-1
功能: 从打开文件中读数据
参数:
fd:读取文件的文件描述符
buf:存放读取数据的缓存
count:要求读取一次数据的字节数

有多种情况可使实际读到的字节小于要求读取的字节数

  • 读普通文件时,在读到要求字节数之前已经到达文件尾端
  • 当从终端设备读取时,通常一次只能读取一行
  • 当从网络读时,网络中的缓冲机构可能造成返回值小于要求要读的字节数
  • 某些面向记录的设备,如磁带,一次最多返回一个记录
  • 进程由于信号造成中断

读操作从当前位移量处开始,在成功返回之前,该位移量增加实际读到的字节数==

五、write函数

ssize_t write(int fd, const void *buf, size_t count);
返回: 若成功为已写的字节数,若出错为-1
功能: 向打开的文件中写数据
参数:
fd:写入文件的文件描述符
buf:存放待写数据的缓存
count:要求写入一次数据的字节数

其返回值通常与参数count的值相同,否则表示出错
write出错的原因:磁盘已满,或者超过了对一个给定进程的文件见长度限制
对于普通文件,写操作从文件当前位移量处开始。如果在打开该文件时,指定了O_APPEND选择项,则在每次写操作前,将文件位移量设置在文件的当前结尾处。在一次写成功之后,该文件位移量增加实际写的字节数

示例

int cp(char* source,char* destination)
{
        int s = open(source,O_RDONLY);
        int d = open(destination,O_CREAT | O_WRONLY | O_TRUNC,0777);
        if(s == -1 || d == -1)
        {
                fprintf(stderr,"open file error %s \n",strerror(errno));
                return 0;
        }
        // 每次读取的大小可以根据设备的block_size来决定,可以通过 tune2fs -l /dev/sda1 查看
        int buffer_length = 888,read_flag = 0;
        char buffer[buffer_length];
        while((read_flag = read(s,buffer,buffer_length)) != 0)
        {
                // 只写入读取到的数据
                if(read_flag > write(d,buffer,read_flag))
                {       
                        fprintf(stderr,"write file error %s \n",strerror(errno));
                        return 0;
                }
                memset(buffer,'\0',buffer_length);
        }
        close(s);
        close(d);
        return 1;
}

lseek函数

off_t lseek(int fd,off_t offset, int whence);

  • 头文件:
#include <sys/types.h>
#include <unistd.h>
  • 返回:若成功则返回新的文件位移量(绝对偏移量);
  • 功能:定位一个以打开的文件
  • 参数

    • fd:已打开文件的文件描述符
    • offset:位移量
    • whence:定位的位置

      • SEEK_SET:将该文件的位移量设置为距文件开始处offset个字节
      • SEEK_CUR:将该文件的位移量设置为其当前值加offset,offset可为正或负
      • SEEK_END: offset,offset可为正或负
  • lseek也可用来确定所涉及的文件是否可以设置位移量。如果文件描述符引用的是一个管道或FIFO,则lseek返回-1,并将errno设置为EPIPE。
  • 每个打开文件都有一个与其相关联的"当前文件偏移量"。它是一个非负整数,用以度量从文件开始处计算的字节数。通常,读,写操作都从当前文件偏移量处开始,并将偏移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND选择项,否则该位移量被设置为0.
  • 示例
// 创建黑洞文件
int file_hole(char* file_path)
{       
        int f = open(file_path,O_CREAT | O_WRONLY | O_TRUNC,0777);
        if(f == -1)
        {       
                perror("open or create file:");
                return 0;
        }
        char buffer[] = "abcdef";
        if(sizeof(buffer) != write(f,buffer,sizeof(buffer)))
        {       
                perror("write file:");
                return 0;
        }
        // 移出文件末尾,再进行写入
        lseek(f,8,SEEK_END); 
        if(sizeof(buffer) != write(f,buffer,sizeof(buffer)))
        {       
                perror("write file:");
                return 0;
        }
        close(f);
        return 1;
}
// cat函数
void cat(int argc,char* argv[])
{
        int in = STDIN_FILENO;
        int out = STDOUT_FILENO;
        if(argc == 1)
        {   
                cp_by_file_no(in,out);
        }   
        else
        {   
                for(int i = 1;i < argc;i++)
                {   
                        in = open(argv[i],O_RDONLY);
                        if(in == -1) 
                        {   
                                perror("open file error:");
                        }   
                        cp_by_file_no(in,out);
                }   
        }   
}

文件I/O的内核数据结构

  • 一个打开的文件在内核中使用三种数据结构(结构体)表示

    • 文件描述符表(使用open等函数返回的文件描述符其实就是文件描述表(结构体数组)的下表)

      • 文件描述符标志
      • 文件表项指针
    • 文件表项

      • 文件状态标志

        • 读,写,追加,同步和非阻塞等状态标志
      • 当前文件偏移量
      • i节点表项指针
      • 饮用计数器
    • i节点

      • 文件类型和对该文件的操作函数指针
      • 当前文件长度
      • 文件所有者
      • 文件所在的设备,文件访问权限
      • 指向文件数据在磁盘块上所在位置的指针等

dup与dup2函数

int dup(int oldfd);
int dup2(int oldfd,int newfd);

  • 头文件:
    #include <unistd.h>
  • 返回:成功返回新文件描述符,出错返回-1
  • 功能:文件描述符的复制
  • 参数

    • oldfd:原先的文件描述符
    • newer:新的文件描述符
  • 由dup返回的新文件描述符一定时当前可用文件描述符中的最小数值
  • 用dup2则可以用newfd参数指定新描述符的数值。如newfd已经打开,则先将其关闭。如若oldfd等于newfd,则dup2返回newfd,而不关闭它(就是把文件描述符表中下标为newfd的结构体中文件表项指针存放的地址切换成文件描述符表中下标为oldfd的结构体中文件表项指针存放的地址,就是指针间的赋值)
  • 在进程间通信时可用来改变进程的标准输入和标准输出设备
  • 示例
void cat(int argc,char* argv[])
{
        int flag_arr[4] = {-1,-1,-1,-1},i = 1,stdin_temp = -1,stdout_temp = -1,file_temp = -1,argc_temp = argc;
        if(argc > 2)
        {
                for(;i < (argc_temp - 1);i++)
                {
                        // 修改输入重定向
                        if(strcmp("+",argv[i]) == 0)
                        {
                                flag_arr[0] = i++;
                                flag_arr[1] = i;
                                // 备份下标准输入,函数执行完毕后恢复
                                stdin_temp = dup(STDIN_FILENO);
                                file_temp = open(argv[i],O_RDONLY);
                                if(file_temp == -1)
                                {
                                        perror("open input file error");
                                }
                                dup2(file_temp,STDIN_FILENO);
                                argc -= 2;
                        }
                        // 修改输出重定向
                        if(strcmp("-",argv[i]) == 0)
                        {
                                flag_arr[2] = i++;
                                flag_arr[3] = i;
                                // 备份下标准输入,函数执行完毕后恢复
                                stdout_temp = dup(STDOUT_FILENO);
                                file_temp = open(argv[i],O_CREAT | O_WRONLY | O_TRUNC,0777);
                                if(file_temp == -1)
                                {
                                        perror("create or open output file error");
                                }
                                dup2(file_temp,STDOUT_FILENO);
                                argc -= 2;
                        }
                }
        }
        // 同时输入了输入和输出
        if(flag_arr[0] != -1 && flag_arr[2] != -1)
        {
                argc = flag_arr[1] + 1;
                flag_arr[1] = -1;
        }
        int in = STDIN_FILENO;
        int out = STDOUT_FILENO;
        if(argc == 1)
        {
                cp_by_file_no(in,out);
        }
        else
        {
                for(i = 1;i < argc;i++)
                {
                        if(in_array(i,flag_arr,4) == 1)
                        {
                                continue;
                        }
                        in = open(argv[i],O_RDONLY);
                        if(in == -1)
                        {
                                perror("open file error:");
                        }
                        cp_by_file_no(in,out);
                }
        }
        // 恢复标准输入输出
        if(flag_arr[0] != -1)
        {
                dup2(stdin_temp,STDIN_FILENO);
        }
        if(flag_arr[2] != -1)
        {
                dup2(stdout_temp,STDOUT_FILENO);
        }
}

fcntl函数

int fcntl(int fd,int cmd);
int fcntl(int fd,int cmd,long arg);
int fcntl(int fd,int cmd,struct flock *lock);

  • 头文件:
#include <unistd.h>
#include <fcntl.h>
  • 返回:若成功则依赖于cmd,若出错为-1
  • 功能:可以改变已经打开文件的性质
  • cmd的常见取值

    • F_DUPFD

      • 复制文件描述符,新的文件描述符作为函数值返回
    • F_GETFD/F_SETFD

      • 获取/设置文件描述符,通过第三个参数设置
    • F_GETFL/F_SETFL

      • 获取/设置文件描述符标志,通过第三个参数设置
      • 可以更改的几个标志是:O_APPEND,O_NONBLOCK,SYNC,O_ASYNC(O_RDONLY,O_WRONLY,O_RDWR不适用)
  • 常见使用

    • 复制一个现存的描述符,新文件描述符作为函数值返回(cmd=F_DUPFD)
    • 获得/设置文件描述符标志(cmd=F_GETFD或F_SETFD)
    • 获得/设置文件状态标志(cmd=F_GETFL或F_SETFL)
    • 获得/设置文件锁(cmd=F_SETLK,cmd=F_GETLK、F_SETLKW)

      • 文件锁必须用到第三个参数,第三个参数为struck flock结构体指针
  • 示例
int set_fl(int fd,int flag)
{
        flag |= fcntl(fd,F_GETFL);
        if(fcntl(fd,F_SETFL,flag) < 0)
        {
                return 0;
        }
        return 1;
}
int clear_fl(int fd)
{
        int flag = fcntl(fd,F_GETFL);
        flag &= ~flag;
        if(fcntl(fd,F_SETFL,flag) < 0)
        {
                return 0;
        }
        return 1;
}

I/O处理方式

  • I/O阻塞模型

    • 若所调用的I/O函数没有完成相关功能就会使进程挂起,直到相关数据到达才会返回。如:终端,网络设备的访问
  • 非阻塞模型

    • 当请求的I/O操作不能完成时,则不让进程休眠,而且返回一个错误。如open,read,write访问
  • I/O多路转接模型

    • 如果请求的I/O操作阻塞,且她不是真正阻塞I/O,而且让其中的一个函数等待,在这期间,I/O还能进行其他操作。如:select函数。
  • 信号驱动I/O模型

    • 在这种模型下,通过安装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动I/O。
  • 异步I/O模型

    • 在这种模型下,当一个描述符已准备好,可以启动I/O时,进程会通知内核。有内核进行后续处理,这种用法现在较少。
        char str[888] = {'\0'};
        // 设置非阻塞
        set_fl(STDIN_FILENO,O_NONBLOCK);
        // 下面的代码不会等待
        read(STDIN_FILENO,str,888);
        printf("%s\n",str);

文件和目录

struct stat结构体

struct stat{
    mode_t    st_mode;    /* file type & permission */
    ino_t     st_ino;     /* i-node number */
    dev_t     st_dev;     /* device number(file system) */
    dev_t     st_rdev;    /* device number for special files */
    nlink_t   st_nlink;   /* number of links */
    uid_t     st_uid;     /* user ID of owner */
    gid_t     st_gid;     /* group ID of owner */
    off_t     st_size;    /* size in byte */
    time_t    st_atime;   /* time of last access */
    time_t    st_mtime;   /* time of last modification */
    time_t    st_ctime;   /* time of last file status change */
    blksize_t st_blksize; /* best I/O block size */
    blkcnt_t  st_blocks;  /* number of disk blocks allocated */
};

文件属性操作函数

int stat(const char* pathname,struct stat* buf);
int fstat(int fd,struct stat* buf);
int lstat(const char* pathname,struct stat* buf);

  • 头文件:
#include <sys/types.h>
#include <sys/stat.h>
  • 返回:成功返回0,出错返回-1
  • 功能:返回一个pathname路径或fd指定的文件的属性信息,存储在结构体buf中
  • 参数

    • pathname:文件路径名字
    • buf:struct stat结构体指针
  • lstat类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息

linux中七种文件类型和七种宏

文件类型
普通文件(regular file)S_ISREG()
目录文件(directory file)S_ISDIR()
块特殊文件(block special file)S_ISBLK()
字符特殊文件(character special file)S_ISCHR()
管道文件 FIFO(named pipe)S_ISFIFO()
套接字(socket)S_ISSOCK()
符号链接(symbolic link)S_ISLNK()
  • 示例
void file_type(char* path)
{
        struct stat s;
        if(lstat(path,&s) == -1)
        {       
                perror("incorrect file");
                return;
        }
        char* str = "unknow type";
        if(S_ISREG(s.st_mode))
        {       
                str = "regular file";
        }
        else if(S_ISDIR(s.st_mode))
        {       
                str = "directory";
        }
        else if(S_ISBLK(s.st_mode))
        {
                str = "block special file";
        }
        else if(S_ISCHR(s.st_mode))
        {
                str = "character special file";
        }
        else if(S_ISFIFO(s.st_mode))
        {
                str = "FIFO";
        }
        else if(S_ISSOCK(s.st_mode))
        {
                str = "socket file";
        }
        else if(S_ISLNK(s.st_mode))
        {
                str = "symbolic link";
        }
        printf("%s : %s\n",path,str);
}
void print_file_stat(char* path)
{
        struct stat* s = malloc(sizeof(struct stat));
        if(stat(path,s) == -1)
        {
                perror("incorrect file : ");
        }
        printf("mode \t %d\nino \t %d\ndev \t %d\nrdev \t %d\nnlink \t %d\nuid \t %d\ngid \t %d\nsize \t %d\natime \t %d\nmtime \t %d\nctime \t %d\nblksize \t %d\nblocks \t %d\n",
                (int)s->st_mode,(int)s->st_ino,(int)s->st_dev,(int)s->st_rdev,(int)s->st_nlink,(int)s->st_uid,(int)s->st_gid,(int)s->st_size,(int)s->st_atime,(int)s->st_mtime,(int)s->st_ctime,(int)s->st_blksize,(int)s->st_blocks);
        free(s);
}

文件权限

  • 9种文件访问权限位

    • 用户权限

      • S_IRUSR,S_IWUSR,S_IXUSR
    • 组权限

      • S_IRGRP,S_IWGRP,S_IXGRP
    • 其他权限

      • S_IROTH,S_IWOTH,S_IXOTH
  • 文件权限通过按位或方式构造
  • 示例:
// 创建文件是给权限
int f1 = open("test.txt",O_CREAT | O_RDONLY,S_IWUSR | S_IRUSR | S_IWGRP);
int f2 = open("test.txt",O_CREAT | O_RDONLY,0620); // 与上面效果一致
// 输出文件权限
void file_permission(char* path)
{
        struct stat s;
        if(lstat(path,&s) == -1)
        {
                perror("incorret file");
                return;
        }
        printf("%c%c%c%c%c%c%c%c%c\n"
                ,(S_IRUSR & s.st_mode) ? 'r' : '-'
                ,(S_IWUSR & s.st_mode) ? 'w' : '-'
                ,(S_IXUSR & s.st_mode) ? 'x' : '-'
                ,(S_IRGRP & s.st_mode) ? 'r' : '-'
                ,(S_IWGRP & s.st_mode) ? 'w' : '-'
                ,(S_IXGRP & s.st_mode) ? 'x' : '-'
                ,(S_IROTH & s.st_mode) ? 'r' : '-'
                ,(S_IWOTH & s.st_mode) ? 'w' : '-'
                ,(S_IXOTH & s.st_mode) ? 'x' : '-'
        );
}

access函数

int access(const char* pathname, int mode);

  • 返回:成功执行时返回0,若出错为-1
  • 功能:检查是否可以对指定文件进行某种操作
  • 头文件
    #include <unistd.h>
  • 参数

    • pathname:文件路径
    • mode:文件访问权限

      • R_OK:判断文件是否有读权限
      • W_OK:判断文件是否有写权限
      • X_OK:判断文件是否有可执行权限
      • F_OK:判断文件是否存在
  • 示例
void file_access(char* path)
{
        if(access(path,F_OK) == -1)
        {
                printf("%s does not exist\n",path);
                return;
        }
        if(access(path,R_OK) == -1)
        {
                printf("%d cannot read file:%s\n",getpid(),path);
        }       
        if(access(path,W_OK) == -1)
        {
                printf("%d cannot write file:%s\n",getpid(),path);
        }
        if(access(path,X_OK) == -1)
        {
                printf("%d cannot excute file:%s\n",getpid(),path);
        }
}

linux文件系统的结构

  • 文件操作相关的基本元素是:目录结构,索引节点和文件的数据本身

    • 目录结构(目录项)
    • 索引节点(i节点)
    • 文件的数据
  • 文件系统(硬盘)的三个区域

    • 超级块:存放文件系统本身的结构信息
    • i-节点表:存放i节点信息列表
    • 数据区:存放文件内容

软链接与硬链接的区别

硬链接:会创建新目录项并指向原文件的i节点,并且文件的链接数会加1
软链接:会创建新目录项,新i节点,新数据块,新数据块存放的是原文件的路径

link函数

int link(const char* existing path,const char* newpath);

  • 返回:成功返回0,出错返回-1
  • 功能:创建一个指向现存文件链接(硬链接)
  • 创建条件

    • 针对文件创建链接
    • 必须是同一分区
    • 只有超级用户才能对目录建立链接
  • 示例
if(-1 == link(argv[1],argv[2]))
{
        printf("fail to create %s\n",argv[2]);
        return 1;
}

unlink函数

int link(const char* existing path,const char* newpath);

  • 返回:成功返回0,出错返回-1
  • 功能:删除pathname指定的硬链接,并将由pathname所引用的文件链接计数减1
  • 文件删除条件

    • 链接计数为0
    • 无其他进程打开该文件
  • 示例
if(argc < 2)
{
        printf("incorrect parameter\n");
        return 1;
}
if(access(argv[1],F_OK) == -1)
{
        printf("%s does not exist\n",argv[1]);
        return 1;
}
if(-1 == unlink(argv[1]))
{
        printf("fail to delete %s\n",argv[1]);
        return 1;
}

remove函数

int remove(const char* pathname);

  • 返回:成功返回0,出错返回-1
  • 功能:解除对一个文件或目录的链接
  • 头文件
#include <unistd.h>
  • 对于文件,remove的功能与unlink相同
  • 对于目录,remove的功能与rmdir相同

rename函数

int rename(const char* oldname,const char* newname);

  • 返回:成功返回0,出错返回-1
  • 功能:解除对一个文件或目录的链接
  • 头文件
#include <unistd.h>

symlink函数

int symlink(const char* actualpath,const char* sympath);

  • 返回:成功返回0,出错返回-1
  • 功能:创建一个符号链接(软链接)
  • 头文件
#include <unistd.h>
  • 创建符号链接并不要求actualpath存在
  • 可以跨文件系统建立符号链接
  • 示例
if(argc < 3)
{
        printf("incorrect parameter\n");
        return 1;
}
if(access(argv[1],F_OK) == -1)
{
        printf("%s does not exist\n",argv[1]);
        return 1;
}
if(access(argv[2],F_OK) == 0)
{
        printf("%s has alreay existed\n",argv[2]);
        return 1;
}
if(-1 == symlink(argv[1],argv[2]))
{
        printf("fail to create %s\n",argv[2]);
        return 1;
}

readlink函数

int readlink(const char * path ,char * buf,size_t bufsiz);

  • 返回:成功返回0,出错返回-1
  • 功能:打开该链接本身,并读该链接中的名字
  • 头文件
#include <unistd.h>
  • 示例
if(argc < 2)
{
        printf("incorrect parameter\n");
        return 1;
}
if(access(argv[1],F_OK) == -1)
{
        printf("%s does not exist\n",argv[1]);
        return 1;
}
const int buffer_size = 1024;
char buffer[1024] = {'\0'};
if(-1 == readlink(argv[1],buffer,buffer_size))
{
        printf("fail to delete %s\n",argv[1]);
        return 1;
}
write(STDOUT_FILENO,buffer,buffer_size);
write(STDOUT_FILENO,"\n",1);

gcc以及gdb的基本使用

gcc语法:gcc [options] [filenames]
1、基本选项
1)-c:只是编译不链接,生成目标文件”.o”
2)-S:只是编译不汇编,生成汇编代码
3)-E:只进行预编译,不做其它处理
4)-g:在可执行程序中包含标准调试信息
5)-o file:指定输出文件
6)-v :打印出编译器内部编译各过程的命令行信息和编译器的版本
7)-std=name:指定C语言的标准(如:c99等)
8)-I dir :在头文件的搜索路径列表中添加dir目录
2、警告和出错选项
1)-ansi:支持符合ANSI标准的C程序
2)-pedantic:允许发出ANSI C标准所列的全部警告信息
3)-pedantic-error:允许发出ANSI C标准所列的全部错误信息
4)-w:关闭所有警告
5)-Wall:允许发出gcc所提供的所有有用的报警信息
3、优化
-O:减小代码的长度和执行时间,效果等价于O1,其中包括线程跳转和延迟退栈
-O2:除完成所有O1级别的优化之外,同时进行一些额外的调整工作,如处理器指令调度
-O3:除完成所有O2级别优化之外,还包括循环展开和其他一些与处理器特性相关的优化工作
数字越大优化的等级越高,也就意味着程序的运行越快,一般采用O2选项,他在优化长度,编译时间和代码大小之间取得了一个比较理想的平衡点
4、制作库文件
-L dir 在库文件搜索列表中添加dir目录
-static 链接静态库
-lname 链接名为name的库文件
-shared 表明是用共享库

一、gdb简介
1、GDB是GNU开源组织发布的一个强大的Unix/Linux下的程序调试工具
二、gdb作用
1、启动用户程序后,可以按照用户的要求随意运行程序。
2、可让被调试的程序在用户所设定的断点处停住
3、当程序被停住时,可以检查此时用户程序中所发生的事。
4、可动态改变用户程序的执行环境
三、gdb语法
1、gcc -g [其它选项] [文件名]
2、gdb 可执行文件‘
3、gdb进行调试的是可执行文件而不是源代码
4、对.c源文件进行编译一定要加上选项”-g”,这样编译出的可执行文件才包含调试信息。
四、gdb调试命令
1、l(list):查看所载入的文件
2、b(break):设置断点,程序运行到断点即可停止。
3、nfo b:查看设置的断点情况
4、r(run):从第一行开始运行代码,或者指定行开始,可在r后面加上行号。
5、p n:查看变量n的值
6、n(next):单步运行下一行代码(遇到函数不会进入函数)
7、s(step):单步运行下一行代码(遇到函数会进入函数)
8、c(continue):恢复程序的运行,执行剩余的程序。