Linux应用编程

打开文件

API open函数

头文件包含:

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h>

函数原型:

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

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

参数:

pathname:为文件的路径,可以带路径描述。

flags:文件打开方式。一会仔细写

mode:当flags满足一定条件就会使用mode位。

mode使用4个数字来指定权限的,其中后面三个很重要,对应我们要创建的这个文件的权限标志。譬如一般创建一个可读可写不可执行的文件就用0666

返回值:

返回值为文件描述符,如果失败则返回-1,(文件描述符不可能是负数)

open函数flags详解

O_RDONLY:以只读方式打开文件

O_WRONLY:以只写方式打开文件

O_RDWR:以读写方式打开文件

以读写或者只写方式写文件时。是在文件的前面进行写操作,覆盖原有内容

O_TRUNC:若文件已经存在,那么会删除文件中的全部原有数据,并且设置文件大小为0.如果该文件不存在,就创建一个新的文件,并用第三个参数为其设置权限

O_APPEND:以添加方式打开文件,在打开文件的同时,文件指针指向文件的末尾,即将写入的数据添加到文件的末尾

O_CREAT:如果改文件不存在,就创建一个新的文件,并用第三个参数为其设置权限。如果文件按已经存在的话,则删除原来内容。

O_EXCL:如果使用O_CREAT时文件存在,则返回错误消息。这一参数可测试文件是否存在。此时open是原子操作,防止多个进程同时创建同一个文件

如果我CREAT要创建的是一个已经存在的名字的文件,则给我报错,不要去创建。这个效果就要靠O_EXCL标志和O_CREAT标志来结合使用。当这连个标志一起的时候,则没有文件时创建文件,有这个文件时会报错提醒我们。

O_NOCTTY:使用本参数时,若文件为终端,那么该终端不会成为调用open()的那个进程的控制终端

O_NONBLOCK: 如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I/O操作设置非阻塞方式。

O_SYNC:使每次write都等到物理I/O操作完成。

O_RSYNC:read 等待所有写入同一区域的写操作完成后再进行

在open()函数中,falgs参数可以通过组合构成,但前3个标准常量(O_RDONLY,O_WRONLY,和O_RDWR)不能互相组合。 O_NONBLOCK (1)阻塞与非阻塞。如果一个函数是阻塞式的,则我们调用这个函数时当前进程有可能被卡住(阻塞住,实质是这个函数内部要完成的事情条件不具备,当前没法做,要等待条件成熟),函数被阻塞住了就不能立刻返回;如果一个函数是非阻塞式的那么我们调用这个函数后一定会立即返回,但是函数有没有完成任务不一定。

(2)阻塞和非阻塞是两种不同的设计思路,并没有好坏。总的来说,阻塞式的结果有保障但是时间没保障;非阻塞式的时间有保障但是结果没保障。

(3)操作系统提供的API和由API封装而成的库函数,有很多本身就是被设计为阻塞式或者非阻塞式的,所以我们应用程度调用这些函数的时候心里得非常清楚。

(4)我们打开一个文件默认就是阻塞式的,如果你希望以非阻塞的方式打开文件,则flag中要加O_NONBLOCK标志。

O_SYNC (1)write阻塞等待底层完成写入才返回到应用层。

(2)无O_SYNC时write只是将内容写入底层缓冲区即可返回,然后底层(操作系统中负责实现open、write这些操作的那些代码,也包含OS中读写硬盘等底层硬件的代码)在合适的时候会将buf中的内容一次性的同步到硬盘中。这种设计是为了提升硬件操作的性能和销量,提升硬件寿命;但是有时候我们希望硬件不好等待,直接将我们的内容写入硬盘中,这时候就可以用O_SYNC标志。

关闭文件

close函数

头文件包含

#include <unistd.h>

函数原型:

int close(int fd)

参数:

fd文件描述符

返回值:

0成功,-1出错

读文件

read函数

头文件:

#include <unistd.h>

函数原型:

ssize_t read(int fd, void *buf, size_t count);

参数:

fd: 将要读取数据的文件描述词。

buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。

count: 表示调用一次read操作,应该读多少数量的字符。

返回值:

ssize_t其实就是Linux中typedef之后的int而已

返回所读取的字节数;0(读到EOF);-1(出错)。

以下几种情况会导致读取到的字节数小于 count :

A. 读取普通文件时,读到文件末尾还不够 count 字节。例如:如果文件只有 30 字节,而我们想读取 100 字节,那么实际读到的只有 30 字节,read 函数返回 30 。此时再使用 read 函数作用于这个文件会导致 read 返回 0 。

B. 从终端设备(terminal device)读取时,一般情况下每次只能读取一行。

C. 从网络读取时,网络缓存可能导致读取的字节数小于 count字节。

D. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 count 。

E. 从面向记录(record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。

F. 在读取了部分数据时被信号中断。 读操作始于 cfo 。在成功返回之前,cfo 增加,增量为实际读取到的字节数。

写文件

write()函数

头文件:

#include <unistd.h>

函数原型:

ssize_t write(int fd, const void *buf, size_t count);

返回值:

写入文件的字节数(成功);-1(出错)

参数:

fd:文件描述符

buf:缓冲区

count:一次写的字符数

终止一个进程用exit函数,而return只能在main函数中推出

记得包含相应头文件

文件读写的一些细节

errno和perror

(1)errno就是error number,意思就是错误号码。linux系统中对各种常见错误做了个编号,当函数执行错误时,函数会返回一个特定的errno编号来告诉我们这个函数到底哪里错了。

(2)errno是由OS来维护的一个全局变量,任何OS内部函数都可以通过设置errno来告诉上层调用者究竟刚才发生了一个什么错误。

(3)errno本身实质是一个int类型的数字,每个数字编号对应一种错误。当我们只看errno时只能得到一个错误编号数字(譬如-37),不适应于人看。

(4)linux系统提供了一个函数perror(意思print error),perror函数内部会读取errno并且将这个不好认的数字直接给转成对应的错误信息字符串,然后print打印出来。

read和write的count

(1)count和返回值的关系。count参数表示我们想要写或者读的字节数,返回值表示实际完成的要写或者读的字节数。实现的有可能等于想要读写的,也有可能小于(说明没完成任务)

(2)count再和阻塞非阻塞结合起来,就会更加复杂。如果一个函数是阻塞式的,则我们要读取30个,结果暂时只有20个时就会被阻塞住,等待剩余的10个可以读。

(3)有时候我们写正式程序时,我们要读取或者写入的是一个很庞大的文件(譬如文件有2MB),我们不可能把count设置为210241024,而应该去把count设置为一个合适的数字(譬如2048、4096),然后通过多次读取来实现全部读完。

lseek函数

功能描述: 用于在指定的文件描述符中将将文件指针定位到相应位置。

头文件:

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

函数原型:

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

参数:

fd;文件描述符

offset:偏移量,每一个读写操作所需要移动的距离,单位是字节,可正可负(向前移,向后移)

whence:就是参照物

SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小

SEEK_CUR:当前位置为指针的位置,新位置为当前位置加上偏移量

SEEK_END:当前位置为文件的结尾,新位置为文件大小加上偏移量的大小

返回值:

成功:返回当前位移

失败:返回-1

用lseek计算文件长度

(1)linux中并没有一个函数可以直接返回一个文件的长度。但是我们做项目时经常会需要知道一个文件的长度,怎么办?自己利用lseek来写一个函数得到文件长度即可。

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


int cal_len(const char *pathname)
{
    int fd = -1;        // fd 就是file descriptor,文件描述符     
        int ret = -1;
    
    // 第一步:打开文件     
    fd = open(pathname, O_RDONLY);
    if (-1 == fd)        // 有时候也写成: (fd < 0)         
    {
        //printf("\n");         
        perror("文件打开错误");
        // return -1;         
        return -1;
    }
    
    // 此时文件指针指向文件开头     
    // 我们用lseek将文件指针移动到末尾,然后返回值就是文件指针距离文件开头的偏移量,也就是文件的长度了     
    ret = lseek(fd, 0, SEEK_END);
    
    return ret;
}



int main(int argc, char *argv[])
{
    int fd = -1;        // fd 就是file descriptor,文件描述符     
    int ret = -1;
    
    if (argc != 2)
    {
        printf("usage: %s filename\n", argv[0]);
        _exit(-1);
    }
    

    printf("文件长度是:%d字节\n", cal_len(argv[1]));
    
    
    
    
    return 0;
}

用lseek构建空洞文件

(1)空洞文件就是这个文件中有一段是空的。

(2)普通文件中间是不能有空的,因为我们write时文件指针是依次从前到后去移动的,不可能绕过前面直接到后面。

(3)我们打开一个文件后,用lseek往后跳过一段,再write写入一段,就会构成一个空洞文件。

(4)空洞文件方法对多线程共同操作文件是及其有用的。有时候我们创建一个很大的文件,如果从头开始依次构建时间很长。

有一种思路就是将文件分为多段,然后多线程来操作每个线程负责其中一段的写入。

多次打开同一文件与O_APPEND

重复打开同一文件读取

分别读说明:我们使用open两次打开同一个文件时,fd1和fd2所对应的文件指针是不同的2个独立的指针。文件指针是包含在动态文件的文件管理表中的,所以可以看出linux系统的进程中不同fd对应的是不同的独立的文件管理表。

重复打开同一文件写入

有时候我们希望接续写而不是分别写?办法就是在open时加O_APPEND标志即可

复制文件描述符dup和dup2函数

头文件:

#include <unistd.h>

函数原型:

int dup(int oldfd);

int dup2(int oldfd, int newfd);

当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。

  dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项。

fcntl函数

fcntl系统调用 功能描述:根据文件描述词来操作文件的特性。

函数原型:

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

参数:

第一个参数是fd表示要操作哪个文件,

第二个参数是cmd表示要进行哪个命令操作。

变参是用来传递参数的,要配合cmd来使用。

有以下操作命令可供使用

一、F_DUPFD:复制文件描述词。

二、FD_CLOEXEC :设置close-on-exec标志。

如果FD_CLOEXEC位是0,执行execve的过程中,文件保持打开。反之则关闭。

三、F_GETFD:读取文件描述词标志。

四、F_SETFD:设置文件描述词标志。

五、F_GETFL:读取文件状态标志。

六、F_SETFL:设置文件状态标志。

其中O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOCTTY 和 O_TRUNC不受影响,能更改的标志有 O_APPEND,O_ASYNC, O_DIRECT, O_NOATIME 和 O_NONBLOCK。

七、F_GETLK, F_SETLK 和 F_SETLKW :获取,释放或测试记录锁,使用到的参数是以下结构体指针:

F_SETLK:在指定的字节范围获取锁(F_RDLCK, F_WRLCK)或释放锁(F_UNLCK)。如果和另一个进程的锁操作发生冲突,返回 -1并将errno设置为EACCES或EAGAIN。 F_SETLKW:行为如同F_SETLK,除了不能获取锁时会睡眠等待外。如果在等待的过程中接收到信号,会即时返回并将errno置为EINTR。 F_GETLK:获取文件锁信息。 F_UNLCK:释放文件锁。 为了设置读锁,文件必须以读的方式打开。为了设置写锁,文件必须以写的方式打开。为了设置读写锁,文件必须以读写的方式打开。

stat 函数讲解

头文件:

#include <sys/stat.h>

定义函数:

int stat ( const char file_name , struct stat buf );

函数说明:

通过文件名filename获取文件信息,并保存在buf所指的结构体stat中

返回值:

执行成功则返回0,失败返回-1,错误代码存于errno

fstat函数

int fstat(int fd, struct stat *buf); 传参是文件描述符

lstat函数

int lstat(const char restrict pathname, struct stat restrict buf); lstat函数和stat和fstat的区别是lstat是直接查看链接文件的属性。而stat和fstat是看的连接文件指向的文件的属性

access函数检查权限设置

(1)文本权限管控其实蛮复杂,一般很难很容易的确定对一个文件是否具有某种权限。设计优秀的软件应该是:在操作某个文件之前先判断当前是否有权限做这个操作,如果有再做如果没有则提供错误信息给用户。

(2)access函数可以测试得到当前执行程序的那个用户在当前那个环境下对目标文件是否具有某种操作权限。

umask与文件权限掩码

(1)文件掩码是linux系统中维护的一个全局设置,umask的作用是用来设定我们系统中新创建的文件的默认权限的。

(2)umask命令就是用umask API实现的

opendir

头文件:

#include <sys/types.h>

#include <dirent.h>

函数原型:

DIR opendir(const char name);

描述:

opendir函数打开一个与给定的目录名name相对应的目录流,并返回一个指向该目录流的指针。打开后,该目录流指向了目录中的第一个目录项。

返回值:

opendir函数,打开成功,返回指向目录流的指针;打开失败,则返回NULL,并设置相应的错误代码errno。

readdir

头文件:

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

函数原型:

struct dirent readdir(DIR dir);

描述:

readdir函数返回一个指向dirent结构体的指针,该结构体代表了由dir指向的目录流中的下一个目录项;如果读到end-of-file或者出现了错误,那么返回NULL。

readdir函数返回的值会被后续调用的(针对同一目录流的)readdir函数返回值所覆盖。

返回值:

readdir函数,成功时返回一个指向dirent结构体的指针;失败时或读到end-of-file时,返回NULL,并且设置相应的错误代码errno。

closedir

头文件:

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

函数原型:

int closedir(DIR *dir);

描述:

closedir函数关闭与指针dir相联系的目录流。关闭后,目录流描述符dir不再可用。

返回值:

closedir函数,成功时返回0;失败是返回-1,并设置相应的错误代码errno。

linux系统时间API和库函数

用的时候直接去man手册查即可

time(API)

(1)time能得到一个当前时间距离标准起点时间1970-01-01 00:00:00 +0000(UTC)过去了多少秒

ctime库函数

(1)ctime可以从time_t出发得到一个容易观察的字符串格式的当前时间。ctime中的参数也是调用time这个API返回回来的时间得到的数字,ctime也是不能离开time来使用的

(2)ctime好处是很简单好用,可以直接得到当前时间的字符串格式,直接打印来看。坏处是ctime的打印时间格式是固定的,没法按照我们的想法去变。

(3)实验结果可以看出ctime函数得到的时间是考虑了计算机中的本地时间的(计算机中的时区设置)

gmtime和localtime

(1)gmtime获取的时间中:年份是以1970为基准的差值,月份是0表示1月,小时数是以UTC时间的0时区为标准的小时数(北京是东8区,因此北京时间比这个时间大8)

(2)猜测localtime和gmtime的唯一区别就是localtime以当前计算机中设置的时区为小时的时间基准,其余一样。实践证明我们的猜测是正确的。

mktime

(1)从OS中读取时间时用不到mktime的,这个mktime是用来向操作系统设置时间时用的。

asctime

(1)asctime得到一个固定格式的字符串格式的当前时间,效果上和ctime一样的。区别是ctime从time_t出发,而asctime从struct tm出发。

strftime

(1)asctime和ctime得到的时间字符串都是固定格式的,没法用户自定义格式

(2)如果需要用户自定义时间的格式,则需要用strftime。

gettimeofday和settimeofday

(1)前面讲到的基于time函数的那个系列都是以秒为单位来获取时间的,没有比秒更精确的时间。

(2)有时候我们程序希望得到非常精确的时间(譬如以us为单位),这时候就只能通过gettimeofday来实现了。

linux生成随机数

rand是一个库函数并不是一个API

(1)单纯使用rand重复调用n次,就会得到一个0-RAND_MAX之间的伪随机数,如果需要调整范围,可以得到随机数序列后再进行计算。

(2)单纯使用rand来得到伪随机数序列有缺陷,每次执行程序得到的伪随机序列是同一个序列,没法得到其他序列

(3)原因是因为rand内部的算法其实是通过一个种子(seed,其实就是一个原始参数,int类型),rand内部默认是使用1作为seed的,种子一定的算法也是一定的,那么每次得到的伪随机序列肯定是同一个。

(4)所以要想每次执行这个程序获取的伪随机序列不同,则每次都要给不同的种子。用srand函数来设置种子。

版权属于:孟超 本文链接:https://mengchao.xyz/index.php/archives/229/ 转载时须注明出处及本声明

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • C++之模板、IO流、异常

    泛型(Generic Programming),即是指具有在多种数据类型上皆可操作的含意。泛型编程的代表作品 STL 是一种高效、泛型、可交互操作的软件组件。泛...

    用户5426759
  • C++的前世今生

    1979 年,美国 AT&T 公司贝尔实验室的 Bjarne Stroustrup 博士在 C 语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。...

    用户5426759
  • C++之友元

    采用类的机制后实现了数据的隐藏与封装, 类的数据成员一般定义为私有成员, 成员函数一般定义为公有的, 依此提供类与外界间的通信接口。但是, 有时需要定义一些函数...

    用户5426759
  • 文件的读写

    明天依旧可好
  • 10.2 打开与关闭文件

    例如:fopen(“a1”,“r”),表示要打开名字为“a1”的文件,使用文件方式为“读入”。

    闫小林
  • C语言的学习

     r  只读  rb只读  r+ rb+(不带b的为已存在的文本文件,带b的为二进制文件(binary),带+号的为读写文件)

    LhWorld哥陪你聊算法
  • Matlab基本语法7

    基本编程技巧 脚本m文件和函数m文件,脚本是一系列命令、语句的简单组合。脚本文件中的变量都是全局变量,程序运行后,这些变量保存在matlab的基本工作空间内,一...

    瓜大三哥
  • 【C语言基础】fopen函数使用

    r代表read的简写,+代表可读可写,w代表write,b代表bit二进制位,t代表text r 打开只读文件,该文件必须存在 r+ 打开可读可写的文件,该文件...

    程序员互动联盟
  • Python 之文件读写操作

            使用 open 打开文件后,格式:open(filename,mode),最后一定要调用文件对象的 close() 方法,如图所示:

    py3study
  • 如何学python 第十九课 文件操作

    今天我们来说说文件操作。文件操作在程序编写里有着举足轻重的作用。文件操作,主要包含文件的输入和输出。学会了文件操作,就可以写出更符合实际需求的脚本。 我会先介绍...

    用户1631416

扫码关注云+社区

领取腾讯云代金券