Linux/Unix系统编程 一:通用的I/O模型

一、文件

linux宗旨:一切皆文件
在这里插入图片描述

1、文件类型

d    目录
l    符号链接(软硬连接)
s    套接字文件
b    块设备文件
c    字符设备文件
p    命名管道文件
-    普通文件,或者更准确的说,不属于以上几种类型的文件

2、文件权限

在这里插入图片描述

r    读权限
w    写权限
x    执行权限

3、权限设置

1、查看用户列表

cat /etc/passwd

通常在Linux系统中,用户的关键信息被存放在系统的/etc/passwd文件中,系统的每一个合法用户账号对应于该文件中的一行记录。这行记录定义了每个用户账号的属性。

onlylove:x:1000:1000:lq,,,:/home/onlylove:/bin/bash

每一行用户记录的各个数据段用“:”分隔,分别定义了用户的各方面属性。各个字段的顺序和含义如下:

注册名:口令:用户标识号:组标识号:用户名:用户主目录:命令解释程序

1、注册名
2、口令
3、用户标识号

是Linux系统中惟一的用户标识,用于区别不同的用户,别称UID。

0:           超级用户
1 - 499:     系统用户(伪用户)
500 - 65535: 普通用户
4、组标识号

组标识号与用户标识号类似,也是一个整数,被系统内部用来标识组。别称GID。

5、用户名
6、用户主目录
7、命令解释程序

2、查看用户组列表

cat /etc/group

将用户分组是Linux系统中对用户进行管理及控制访问权限的一种手段。每个用户都属于某个用户组;一个组中可以有多个用户,一个用户也可以属于不 同的组。

onlylove:x:1000:
sambashare:x:132:onlylove

用户组的所有信息都存放在/etc/group文件中。此文件的格式是由冒号(:)隔开若干个字段,这些字段具体如下:

组名:口令:组标识号:组内用户列表

1、组名
2、口令

口令字段存放的是用户组加密后的口令字。一般Linux系统的用户组都没有口令,即这个字段一般为空,或者是*。

x表示没有设置密码

3、组标识号

组标识号与用户标识号类似,也是一个整数,被系统内部用来标识组。别称GID。

4、组内用户列表

3、用户密码文件

cat /etc/shadow

前面介绍了 /etc/passwd 文件,由于该文件允许所有用户读取,易导致用户密码泄露,因此 Linux 系统将用户的密码信息从 /etc/passwd 文件中分离出来,并单独放到了此文件中。

/etc/shadow 文件只有 root 用户拥有读权限,其他用户没有任何权限,这样就保证了用户密码的安全性。

root:$6$gLGU8blJmh.YQjIt$CiCtocxXn6E5lyHh64oHCON/ntfSeI/cwHxVQeH07ae9cQ0m83WAG6NnzIAbDJjDOOxloyApqrU1fY1N4Zw8s.:18886:0:99999:7:::
onlylove:$1$Gdh.2E0J$CSARuBpEkHVu/T753H452.:18812:0:99999:7:::

文件中每行代表一个用户,同样使用 “:” 作为分隔符,不同之处在于,每行用户信息被划分为 9 个字段。每个字段的含义如下:

用户名:加密密码:最后一次修改时间:最小修改时间间隔:密码有效期:密码需要变更前的警告天数:密码过期后的宽限时间:账号失效时间:保留字段

1、用户名
2、加密密码

这里保存的是真正加密的密码。目前 Linux 的密码采用的是 SHA512 散列加密算法,原来采用的是 MD5 或 DES 加密算法。SHA512 散列加密算法的加密等级更高,也更加安全。

注意,这串密码产生的乱码不能手工修改,如果手工修改,系统将无法识别密码,导致密码失效。很多软件透过这个功能,在密码串前加上 “!”、"*" 或 “x” 使密码暂时失效

所有伪用户的密码都是 “!!” 或 “*”,代表没有密码是不能登录的。当然,新创建的用户如果不设定密码,那么它的密码项也是 “!!”,代表这个用户没有密码,不能登录。

3、最后一次修改时间

Linux 计算日期的时间是以 1970 年 1 月 1 日作为 1 不断累加得到的时间

4、最小时间间隔
5、密码有效期

该字段的默认值为 99999,也就是 273 年,可认为是永久生效。

6、密码需要变更前的警告天数
7、密码过期后的宽限天数
8、账号失效时间
9、保留

二、文件描述符

一个非负整数,通常是小整数,来指代打开的文件设备。

文件描述符用以表示所有类型的已打开文件,包括管道(pipe)、FIFO、socket、终端、设备和普通文件。

针对每个进程,文件描述符都自成一套。

标准文件描述符:

文件描述符用途POSIX名称stdio流
0标准输入STDIN_FILENOstdin
1标准输出STDOUT_FILENOstdout
2标准错误STDERR_FILENOstderr

三、系统调用接口

接口作用
fd = open(pathname, flags, mode)打开pathname所标识的文件,并返回文件描述符。
numread = read(fd, buffer, count)读取打开的文件,并返回读取字节数。
numwritten = write(fd, buffer, count)向打开文件写入数据,并返回写入数据的字节数
status = close(fd)释放文件描述符fd及相关资源

四、接口分析

1、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);

1、参数

1、pathname

要打开的文件由参数 pathname 来标识。如果 pathname 是一符号链接,会对其进行解引用。

2、flags

flags 为位掩码,用于指定文件的访问模式。

flags 参数值介绍

标志用途
O_RDONLY以只读方式打开
O_WRONLY以只写方式打开
O_RDWR以读写方式打开
O_CLOEXEC设置 close-on-exec 标志
O_CREAT若文件不存在则创建
O_DIRECT无缓存的输入/输出
O_DIRECTORY如果 pathname 不是目录,则失败
O_EXCL结合 O_CREAT 参数使用,专门用于创建文件
O_LARGEFILE在 32 位系统中使用此标志打开大文件
O_NOATIME调用 read() 时,不修改文件最近访问时间
O_NOCTTY不要让 pathname(所指向的终端设备)成为控制终端
O_NOFOLLOW对符号链接不予解引用
O_TRUNC截断已有文件,使其长度为零
O_APPEND总在文件尾部追加数据
O_ASYNC当I/O操作可行时,产生信号(signal)通知进程
O_DSYNC提供同步的I/O数据完整性
O_NONBLOCK以非阻塞方式打开
O_SYNC以同步方式写入文件
3、mode

创建文件时指定创建文件的权限

创建新文件的访问权限不仅仅依赖参数 mode,而且受到进程的 umask 值和父目录的默认访问控制列表影响。

标志用途
S_IRUSR所有者拥有读权限
S_IWUSR所有者拥有写权限
S_IXUSR所有者拥有执行权限
S_IRGRP群组拥有读权限
S_IWGRP群组拥有写权限
S_IXGRP群组拥有执行权限
S_IROTH其他用户拥有读权限
S_IWOTH其他用户拥有写权限
S_IXOTH其他用户拥有执行权限

2、返回值

如果调用成功,open() 将返回一文件描述符,用于在后续函数调用中指代该文件。

如果调用失败,则返回 -1,并将 errno 置为相应的错误标志。
函数错误

标志用途
EACCES文件权限不允许调用进程以 flags 参数指定方式打开文件
EISDIR所指文件属于目录,而调用者企图打开该文件进行写操作
EMFILE进程已打开的文件描述符数量达到了进程资源限制所设定的上限
ENFILE文件打开数量已达到系统允许的上限
ENOENT1、文件不存在且未指定O_CREAT标志
2、指定O_CREAT标志,但 pathname 参数所指定路径的目录不存在
EROFS指定的文件隶属于只读文件系统,而调用者企图以写方式打开文件
ETXTBSY指定的文件为可执行文件,且正在运行

2、creat()

在早期的 UNIX 实现中,open() 只有两个参数,无法创建新文件,而是使用 creat() 系统调用来创建一个新文件。

#include <fcntl.h>

int creat(const char *pathname, mode_t mode);

creat() 系统调用根据 pathname 参数创建并打开一个文件,若文件已存在,则文件打开,并清空文件内容,将其长度清0。

creat() 返回一个文件描述符,供后续系统调用使用。

creat() 系统调用等价于如下 open() 调用:

fd = open(pathname,O_WRONLY|O_CREAT|O_TRUNC,mode);

3、read()

#include <unistd.h>

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

1、参数

1、fd

open() 系统调用返回的文件描述符。

2、buf

保存读取数据缓存。

3、count

size_t数据类型属于无符号整数类型。

指示buf缓存至少还有多少字节。

2、返回值

ssize_t数据类型属于有符号整数类型

read() 调用成功,将返回实际读取的字节数,如果遇到文件结束(EOF)则返回0。

read() 调用失败,则返回 -1。

4、write()

#include <unistd.h>

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

1、参数

1、fd

open() 系统调用返回的文件描述符。

2、buf

保存要写入数据缓存。

3、count

size_t数据类型属于无符号整数类型。

要写入的字节。

2、返回值

ssize_t数据类型属于有符号整数类型

write() 调用成功,将返回实际写入的字节数。

write() 调用失败,则返回 -1。

5、close()

#include <unistd.h>

int close(int fd);

1、参数

1、fd

open() 系统调用返回的文件描述符。

2、返回值

close() 调用成功,将返回 0。

close() 调用失败,则返回 -1。

6、lseek()

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

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

1、参数

1、fd

open() 系统调用返回的文件描述符。

2、offset

offset 指定一个以字节为单位的数值。

3、whence

whence 表明应参照那个基点来解释 offset 参数。

标志用途
SEEK_SET从文件头部起始点开始的 offset 个字节
SEEK_CUR从文件当前偏移量开始的 offset 个字节
SEEK_END从文件尾部开始的 offset 个字节(offset参数从文件最后一个字节之后下一个字节算起)

2、返回值

返回新的文件偏移量。

3、备注

lseek() 并不适用所有类型的文件,不允许将lseek() 应用于管道、FIFO、socket或者终端。

7、文件空洞

文件偏移量已经跨过文件结尾,从文件结尾后到新写入数据间的这段空间被称为文件空洞。调用 read() 读取将返回0。

8、ioctl()

#include <sys/ioctl.h>

int ioctl(int fd, unsigned long request, ...);

ioctl() 系统调用为执行文件和设备操作提供一种多用途机制。

1、参数

1、fd

open() 系统调用返回的文件描述符。

2、request

指定在 fd 上执行的控制操作。

2、返回值

五、示例

1、实现一个简版cp命令

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

#ifndef BUF_SIZE
#define BUF_SIZE 1024
#endif

int main(int argc,char *argv[])
{
    int input_fd,output_fd;
    ssize_t read_num;
    char buf[BUF_SIZE];
    // 参数检查,打印帮助信息
    if(argc != 3 || strcmp(argv[1],"--help") == 0)
        printf("%s old-file new-file\n",argv[1]);
    // 打开输入文件
    input_fd = open(argv[1],O_RDONLY);
    if(input_fd == -1){
        printf("open file %s \n",argv[1]);
        return 1;
    }
    // 打开输出文件
    output_fd = open(argv[2],O_CREAT|O_WRONLY|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
    if(output_fd == -1){
        printf("open file %s \n",argv[2]);
        close(input_fd);
        return 1;
    }
    // 从旧文件拷贝数据到新文件
    while((read_num = read(input_fd,buf,BUF_SIZE)) > 0)
        if(write(output_fd,buf,read_num) != read_num)
            printf("couldn't write whole buffer\n");
    // 关闭打开的文件
    if(close(input_fd) == -1)
        printf("close inpput file\n");
    if(close(output_fd) == -1)
        printf("close output file\n");
    return 0;
}
TAR := copy
OBJ := copy.o
SOU := copy.c
CC := gcc

$(TAR): $(OBJ)
	$(CC) $(OBJ) -o $(TAR)

#$(OBJ):$(SOU)

#%.o:%.c
#	$(CC) -c %.c -o %.o

.PHONY : clean
clean:
	rm $(OBJ) $(TAR)

测试
在这里插入图片描述
在这里插入图片描述

2、lseek()和read()、write()协作使用

程序说明:第一个参数为要打开的文件名称,余下参数指定在文件上执行的输入输出操作。每个表示操作的参数都以一个字母开头,紧跟以相关值(中间无空格分隔)。

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

int main(int argc, char *argv[])
{
    int fd,ap,j;
    size_t len = 0;
    off_t offset = 0;
    char *endptr = NULL,*buf = NULL;
    ssize_t read_num = 0,write_num = 0;
    // 检查输入参数、答应帮助信息
    if(argc < 3 || strcmp(argv[1],"--help") == 0)
        printf("%s file {r<length>|R<length>|w<string>|s<offset>}……\n",argv[0]);
    // 打开文件
    fd = open(argv[1],O_RDWR|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
    if(fd == -1){
        printf("open file\n");
        return 1;
    }
    // 根据命令执行相应操作
    for(ap=2;ap<argc;ap++){
        switch(argv[ap][0]){
        case 'r':   /* 从当前偏移量以text形式显示 */
        case 'R':   /* 从当前偏移量以hex形式显示 */
            len = strtol(&argv[ap][1],&endptr,0);
            buf = malloc(len);
            if(buf == NULL){
                printf("R - malloc\n");
                return 1;
            }
            read_num = read(fd,buf,len);
            if(read_num == -1){
                printf("R - read\n");
                return 1;
            }
            if(read_num == 0){
                printf("%s end-of-file\n",argv[ap]);
            }else{
                printf("%s: ",argv[ap]);
                for(j=0;j<read_num;j++){
                    if(argv[ap][0] == 'r')
                        printf("%c ",isprint(buf[j]) ? buf[j] : '?');
                    else
                        printf("%02x ",(unsigned int)buf[j]);
                }
                printf("\n");
            }
            break;
        case 'w':   /* 从当前偏移量写入string字符串 */
            write_num = write(fd,&argv[ap][1],strlen(&argv[ap][1]));
            if(write_num == -1){
                printf("w - write\n");
                return 1;
            }
            printf("%s: worte %ld bytes\n",argv[ap],write_num);
            break;
        case 's':   /* 改变文件偏移量 */
            offset = strtol(&argv[ap][1],&endptr,0);
            if(lseek(fd,offset,SEEK_SET) == -1){
                printf("s - lseek\n");
                return 1;
            }
            printf("%s: seek succeede\n",argv[ap]);
            break;
        default:
            printf("Argument must start whit [rRws]: %s\n",argv[ap]);
            break;
        }
    }
    return 0;
}
TAR := seek_io
OBJ := seek_io.o
SOU := seek_io.c
CC := gcc

$(TAR): $(OBJ)
	$(CC) $(OBJ) -o $(TAR)

#$(OBJ):$(SOU)

#%.o:%.c
#	$(CC) -c %.c -o %.o

.PHONY : clean
clean:
	rm $(OBJ) $(TAR)

测试
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值