Linux系统编程2

umask

umask可用来设定[权限掩码]。[权限掩码]是由3个八进制的数字所组成,将现有的存取权限减掉权限掩码后,即可产生建立文件时预设的权限。

linux创建文件文件默认权限为644,目录默认权限为755,默认权限掩码为022。

open函数

定义

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

头文件

1
2
#include <unistd.h>
#include <fcntl.h>

参数

1、pathname:

相对该文件的绝对路径

2、flags

flags参数表示打开文件所采用的操作

  • O_RDONLY:只读模式
  • O_WRONLY:只写模式
  • O_RDWR:可读可写

以上三个参数是必须的,下边的选项是选用的,使用时需要与必选参数|起来,如O_RDONLY|O_CREAT

  • O_APPEND 表示追加,如果原来文件里面有内容,则这次写入会写在文件的最末尾。

  • O_CREAT 如果指定文件不存在,则创建这个文件 fd = open("./2.txt",O_RDONLY|O_CREAT,0644);

  • O_EXCL 表示如果要创建的文件已存在,则出错,同时返回 -1,并且修改 errno 的值。

  • O_TRUNC 表示截断,如果文件存在,并且以只写、读写方式打开,则将其长度截断为0。

  • O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。

  • O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)

3、mode:

mode参数表示设置文件访问权限的初始值,和用户掩码umask有关,用于创建文件时使用,即第二个参数flags为O_CREAT时才有用

返回值

正常则返回一个整数,即文件描述符(int型),出现错误返回-1,并且errno被设置成对应的值

常见错误:

  • 打开文件不存在
  • 以写方式打开只读文件
  • 以只写或读写方式打开目录,只能用只读方式打开目录

read和write函数

1
2
3
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
// 参数:文件描述符,缓冲区指针,缓冲区大小

成功:返回读到的字节数

错误,返回-1,设置errno为对应值

1
2
3
4
// write() 将从 buf 开始的缓冲区中写入 count 个字节到文件描述符 fd 引用的文件。
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
// count为要写入的字节数

使用read和write实现cp操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

int main()
{
char buf[1024];
int n; //每次读到的字节数
int fd_rd = open("./1.txt",O_RDONLY);
int fd_wr = open("./2.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
if(fd_rd == -1)
{
perror("open read_txt error");//输出错误提示,并输出strerror(errno)的系统错误信息
exit(1);
}
if(fd_wr == -1)
{
perror("open write_txt error");
exit(1);
}
// 先把数据读入缓冲区
while( (n = read(fd_rd,&buf,sizeof(buf))) != 0)
{
if(n<0)
{
perror("read error");
break;
}
write(fd_wr, buf, n); //这里是n

}
close(fd_rd);
close(fd_wr);
return 0;
}

文件描述符

image-20231201204236724

PCB进程控制块,本质是结构体。其中有个指针变量,指向文件描述符表。

文件描述符表是一个数组,其中的下标0、1、2、….1023就是文件描述符,而数组中存的是每个文件的每个文件结构体file的指针。

1
2
3
4
5
6
7
8
struct file
{
文件的偏移量
文件的访问权限
文件的打开标志
文件在内核缓冲区的首地址
....
}

其中

0-STDIN_FILENO

1-STDOUT_FILENO

2-STDERR_FILENO

阻塞和非阻塞

读常规文件是不会阻塞的,不管读多少字节,read 一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用 read 读终端设备就会阻塞,如果网络上没有接收到数据包,调用 read 从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。
产生阻塞的场景。设备文件、读网络文件

/dev/tty —–终端文件 open("/dev/tty,O_RDWR|O_NONBLOCK")

fcntl函数

获取文件状态:F_GETFL int flags = fctl(0,F_GETFL)

设置文件状态:F_SETFL fcntl(STDIN_FILENO,F_SETFL,flags)

image-20231202122934603

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int main()
{
char buf[10];
int flags,n;
if((flags = fcntl(STDIN_FILENO,F_GETFL))==-1) //获取stdin属性信息
{
perror("fcntl error");
exit(1);
}

flags |= O_NONBLOCK; //增加O_NONBLOCK位信息
int ret;
if((ret = fcntl(STDIN_FILENO,F_SETFL,flags))==-1) //设置标准输入文件为非阻塞状态
{
perror("fcntl set error");
exit(1);
}

tryagain:
n = read(STDIN_FILENO,buf,10);
if(n<0) //当以非阻塞状态读文件时,如果缓冲区中没有数据,则返回-1,同时设置errno为EGAIN,因此这样判断

{
if(errno != EAGAIN) {
perror("read /dev/tty");
exit(1);
}
sleep(3);
write(STDOUT_FILENO, "try again\n", strlen("try again\n")); //向标准输出中写入try again,表示一次尝试
goto tryagain;
}
//如果从缓冲区中读到数据,就写入标准输出文件中
write(STDOUT_FILENO,buf,n);
return 0;
}

lseek函数

Linux系统中使用系统函数来修改文件偏移量(读写位置)

1
2
3
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

参数whence

SEEK_SET:   从文件头部开始偏移offset个字节。

SEEK_CUR:  从文件当前读写的指针位置开始,增加offset个字节的偏移量。

SEEK_END:  文件偏移量设置为文件的大小加上偏移量字节。

返回值

成功:返回值是较文件起始位置向后的偏移量

失败:返回-1

注意:打开文件后,文件的读和写使用同一个偏移位置

应用场景:

  • 文件打开后,文件的读和写使用同一个偏移位置

  • 使用lseek获取文件大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
int main(int agrc, char *argv[])
{
int fd = open(argv[1],O_RDWR);
if(fd==-1)
{
perror("open error");
exit(1);
}

int len = lseek(fd, 0, SEEK_END); //获取文件大小,单位:字节
printf("length=%d\n",len);
return 0;
}
  • 使用lseek拓展文件大小;要想使文件大小真正改变,必须使用IO操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
int main(int agrc, char *argv[])
{
int fd = open(argv[1],O_RDWR);
if(fd==-1)
{
perror("open error");
exit(1);
}

int len = lseek(fd, 100, SEEK_END); //len增加100,此时ls -l Read.txt其大小没变
printf("length=%d\n",len);
write(fd,"\0",1); // 调用IO操作,写入磁盘。此时才发生变化
return 0;
}

使用act -A Read.txt查看文件,包括特殊字符

也可以使用truncate函数直接拓展,truncate(char *path, off_t length)

如果文件以前大于此大小,则多余的数据将丢失。 如果之前的文件较短,则会扩展它,扩展部分读取为空字节(’\0’)。