系统调用(System Call)
服务的入口点,程序由此向内核请求服务。这些可直接进入内核的入口点被称为 系统调用。
操作系统提供的服务
l 进程控制
l 文件系统控制
l 内存管理
l 网络管理
l 用户管理
l 进程间通信
l ...
系统调用
n 为什么用户程序不能直接访问内核提供的服务?
在Linux中,为了更好地保护内核空间,程序的运行空间分为 内核空间和 用户空间(也就是常称的内核态和用户态),它们分别运行在不同的级别上,在逻辑上是相互隔离的。因此, 用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间的函数。
n 思考:是不是系统调用或库函数一定会执行成功?怎么知道执行成功或失败?执行失败了怎么办?
几乎所有的系统调用或库函数都会返回某种类型的状态值,以表明是否执行成功!
需要对状态值进行检查!!
需要对执行失败的情况进行处理,至少也要显示错误信息!!!
fd = open(pathname, flags, mode); /* system call to open a file */
if (fd == -1) {
/* Code to handle the error */
}
...
if (close(fd) == -1) {
/* Code to handle the error */
}
n 思考:上面的open函数和close函数出错都是返回-1,仅凭这个信息能确定具体的出错原因吗?
l 通过errno
当系统调用执行失败,会设置全局整型变量errno的值,以表明具体的出错原因。
ERRORS
cnt = read(fd, buf, numbytes);
if (cnt == -1) {
if (errno == EINTR)
fprintf(stderr, "read was interrupted by a signal\n");
else {
/* Some other error occurred */
}
}
n errno是一个整型值,看起来不直观。有没有办法基于errno的值,直接显示相应的错误消息呢?
u 方案一
#include <stdio.h>
void perror(const char *msg);
u 方案二
#include <string.h>
char * strerror(int errnum);
Returns pointer to error string corresponding to errnum
n “文件”这个名词不陌生,什么是文件?
Ø 系统资源(内存、硬盘、一般设备、进程间通信的通道等)的一个抽象
Ø 对系统资源进行访问的一个通用接口。
n 采用这种“文件”的方式有什么好处?
对资源提供通用的操作接口,可以极大地简化系统 编程接口的设计。
n 既然文件是一个通用的接口,由于系统资源多种多样,是不是意味着文件类型也多种多样?
常见的文件类型(可以通过文件来访问的系统资源)有:
Ø 普通文件
文件的内容以字节 为单位进行存储和访问。
Ø 目录
目录是一种特殊的文件,目录可以像普通文件一样打开、关闭以及进行相应的操作。
Ø 管道
管道是Linux中的一种进程间通信的机制。
Ø 设备文件
设备文件没有具体的内容, 对设备文件的读写操作实际上与某个设备的输入输出操作关联在一起。
Ø 符号链接
符号链接的内容是指向另一个文件的路径。当对符号链接进行操作时,系统会根据情况将这个操作转移到它所指向的文件上去,而不是对它本身进行操作。
Ø socket
socket也是一种进程间通信的方式,与管道不同的是,它们可以在不同的主机上进行通信,也就是网络通信。
n 既然有多个文件类型,是不是意味着在程序中有多种方式表示文件呢?实际编程中,如何表示文件?
u 所有执行I/O操作的系统调用使用 文件描述符来表示打开的文件。
u 文件描述符是一个非负整数。
u 文件描述符可以表示各种类型的打开的文件。
u 对文件的操作只要使用 文件描述符即可指定所操作的文件。
n 如何获得文件描述符?获得文件描述符以后如何进行文件操作?
文件操作的一般过程
n 打开文件,打开成功后,应用程序将获得文件描述符。
n 应用程序使用文件描述符对文件进行读写等操作。
n 全部操作完毕后,应用程序需要将文件关闭以释放用于管理打开文件的内存。
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);
Returns file descriptor on success, or –1 on error
各参数及返回值的含义如下:
u pathname:要打开或创建的文件名称。
flags:标志位,指定打开文件的操作方式。
mode:指定新文件的访问权限。(仅当创建新文件时才使用该参数)
u 返回值:若成功返回文件描述符,否则返回-1并设置变量errno的值。
v 基本取值:
O_RDOWRONLY:以只写方式打开文件。
O_RDWRNLY:以只读方式打开文件。
O_RDWR:以读写方式打开文件。
注意:上述三个常量必须指定且只能指定一个。
v 可选取值(部分):
O_CREAT:如果被打开的文件不存在,则自动创建这个文件。
O_EXCL:如果O_CREAT标志已经使用,那么当由pathname参数指定的文件已经存在时,open返回失败。
O_TRUNC:如果被打开的文件存在并且是以可写的方式打开的,则清空文件原有的内容。
O_APPEND:新写入的内容将被附加在文件原来的内容之后,即打开后文件的读写位置被置于文件尾。
n 思考:这些参数如何组合使用?
read()系统调用从打开的文件中读取数据。
#include <unistd.h>
ssize_t read(int fd, void * buf, size_t count);
Returns number of bytes read, 0 on EOF, or –1 on error
各参数及返回值的含义如下:
u fd:要读取的文件的描述符。
u buf:读取到的数据要放入的缓冲区。
u count:要读取的字节数。
u 返回值:若成功返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1并设置变量errno的值。
注意:
1. 这里的size_t是无符号整型,ssize_t是有符号整型。
2. buf指向的内存空间必须事先分配好。
n 使用read()时,可能遇到哪些情况?
u 返回值等于count
u 返回一个大于0小于count的值
u 返回0
u 阻塞
u 返回-1且errno的值为EINTR
u 返回-1且errno的值为EAGAIN
u ...
阻塞方式进行操作
ssize_t ret;
while (len != 0 && (ret = read (fd, buf, len)) != 0) {
if (-1 == ret) {
if (EINTR == errno)
continue;
perror("read");
break;
}
len -= ret;
buf += ret;
}
其中:len初始值为要读取的字节数。
非阻塞方式进行操作
ssize_t nr;
start:
nr = read (fd, buf, len);
if (-1 == nr) {
if (EINTR == errno)
goto start;
if (EAGAIN == errno)
/* 处理其他事务,在恰当的时候再调用read */
else
/* 有错误发生,处理错误 */
}
errno如果是EINTR,可以再次进行读操作;如果是EAGAIN,表示要读取的文件现在没有可供读取的数据。
write()系统调用向打开的文件写数据。
#include <unistd.h>
ssize_t write(int fd, const void * buf, size_t count);
Returns number of bytes written, or –1 on error
各参数及返回值的含义如下:
u fd:要写入的文件的描述符。
u buf:要写入的数据所存放的缓冲区。
u count:要写入的字节数。
u 返回值:若成功返回已写的字节数,出错则返回-1并设置变量errno的值。
与读操作一样,在调用 write 函数向文件写数据时,并不能保证 一次写完所提供的全部数据,并且也会有各种各样的异常情况, 这就需要采取措施以保证数据的可靠写入。
ssize_t ret; while (len != 0 && (ret = write (fd, buf, len)) != 0) { if (-1 == ret) { if (EINTR == errno) continue; perror ("write"); break; } len -= ret; buf += ret; }
close()系统调用关闭一个打开的文件。
#include <unistd.h>
int close(int fd);
Returns 0 on success, or –1 on error
各参数及返回值的含义如下:
u fd:要关闭的文件的描述符。
u 返回值:若成功返回0,出错则返回-1。
注:当一个进程终止时,内核会自动关闭它所有打开的文件。
n 除了对文件进行读写,还可以进行什么操作?
lseek系统调用可以改变文件偏移量(File Offset)。文件偏移量是一个整数,表示距文件起始处的字节数。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fildes, off_t offset, int whence);
Returns new file offset if successful, or –1 on error
whence必需是以下三个常量之一:
SEEK_SET:将文件偏移量设置在距文件开始处offset个字节。
SEEK_CUR:将文件偏移量设置在其当前值加offset,offset可正可负。
SEEK_END:将文件偏移量设置为文件长度加offset,offset可正可负。
n 从以上对文件的各种操作中,是不是可以推测出文件描述符和打开的文件是一一对应的关系?
l为了搞清楚上述问题,首先需要了解内核如何表示打开的文件
内核使用三种数据结构表示一个打开的文件!
Ø 文件描述符表(每个进程都有)
(1) 文件描述符标志(file descriptor flags),如:close_on_exec。
(2) 指向一个文件表项(file table entry)的指针。
Ø 打开文件表(open file table)
(1) 文件状态标志(file status flags),如读、写、非阻塞等。
(2) 当前文件偏移量(file offset)。
(3) 文件访问模式(read-only, write-only, or read-write)。
(4) 指向i-node表项的指针。
Ø 文件系统i-node表
(1) 文件类型(regular file, socket, or FIFO)和权限。
(2) 指向文件表的指针。
(3) 文件的属性(file size等)。
l 有了以上知识后,再看看文件操作
Ø 每次完成write后,在文件表项中的当前文件偏移量(file offset)增加所写的字节数。如果当前文件偏移量超过了当前文件长度(file size),则在i-node表项中的文件长度被设置为当前文件偏移量(也就是说文件加长了)。
O_APPEND标志打开一个文件,则 每次 对这种具有添写标志的文件执行写操作时,文件表项中的当前文件偏移量 首先 被设置为i-node表项中的文件长度,这就使得每次写的数据都添加到文件的当前尾端处。
Ø 若一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i-node表项中的当前文件长度。
Ø lseek只修改文件表项中的当前文件偏移量,没有进行任何I/O操作。
n 思考:如何向文件结尾添加新的内容?可不可以采用这种方式?
lseek(fd, 0L, SEEK_END);
write(fd, buf, 100);
n 上述代码,什么时候能正常工作?什么时候不能?出错的原因是什么?怎么解决?
l 方案一
O_APPEND选项,这就使内核每次对这种文件进行写之前,都将当前文件偏移量设置为该文件的尾端。于是,每次写之前就不需要调用lseek函数了。
l 方案二
pread()和pwrite()
#include <unistd.h>
ssize_t pread(int fd, void * buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void * buf, size_t count, off_t offset);
调用pread相当于顺序调用lseek和read,但是又与这种顺序调用不同,不同之处在于调用pread时,无法中断其定位操作和读操作。pwrite与之类似(lseek和write)。
如果该操作原子 地执行,则要么执行完所有步骤,要么一步也不执行,不可能只 执行所有步骤的一个子集。
C标准库提供了 操作文件的标准I/O函数库,与系统调用相比,主要差别是实现了一个 跨平台的用户态缓冲的解决方案。
一个I/O缓冲的例子如下:
open.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int main()
{
close(1);
// 打开一个文件
int fd = open ("test.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IXUSR);
//int fd = open ("test.txt", O_RDONLY);
if (fd == -1)
{
printf ("打开文件失败\n");
perror ("open");
printf ("%s\n", strerror(errno));
}
printf ("fd = %d\n", fd);
printf ("asdadasdad\n", fd);
printf ("asdadasdad\n", fd);
printf ("asdadasdad\n", fd);
printf ("asdadasdad\n", fd);
printf ("asdadasdad\n", fd);
printf ("asdadasdad\n", fd);
printf ("asdadasdad\n", fd);
// fflush(stdout);
close(fd);
return 0;
}
read.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#define SIZE 1024
int main1()
{
int fd = open("BTree.c", O_RDONLY, 0777);
if (fd == -1)
{
perror ("open");
return -1;
}
char buf[SIZE] = {0};
ssize_t ret = read(fd, buf, SIZE-1);
if (ret == -1)
{
perror ("read");
}
// 返回值为0 代表读到文件结尾
if (ret == 0)
{
printf ("文件读取结束\n");
}
printf ("len = %d\n", strlen(buf));
printf ("读到 %d 字节: %s\n", ret, buf);
return 0;
}
// 缓冲区覆盖问题
int main2()
{
int fd = open("BTree.c", O_RDONLY, 0777);
if (fd == -1)
{
perror ("open");
return -1;
}
char buf[SIZE] = {0};
while (1)
{
ssize_t ret = read(fd, buf, SIZE-1);
if (ret == -1)
{
perror ("read");
}
// 返回值为0 代表读到文件结尾
if (ret == 0)
{
printf ("文件读取结束\n");
break;
}
//printf ("len = %d\n", strlen(buf));
// printf ("读到 %d 字节: %s\n", ret, buf);
printf ("%s", buf);
}
return 0;
}
// 读取数据之前清空缓冲区
int main3()
{
int fd = open("BTree.c", O_RDONLY, 0777);
if (fd == -1)
{
perror ("open");
return -1;
}
char buf[SIZE] = {0};
while (1)
{
memset (buf, 0, SIZE); // 清空缓冲区
ssize_t ret = read(fd, buf, SIZE-1);
if (ret == -1)
{
perror ("read");
}
if (ret == 0)
{
printf ("文件读取结束\n");
break;
}
printf ("%s", buf);
}
return 0;
}
// 每次读完数据之后将下一个字节置为 '\0';
int main4()
{
int fd = open("BTree.c", O_RDONLY, 0777);
if (fd == -1)
{
perror ("open");
return -1;
}
char buf[SIZE] = {0};
while (1)
{
ssize_t ret = read(fd, buf, SIZE-1);
if (ret == -1)
{
perror ("read");
}
if (ret == 0)
{
printf ("文件读取结束\n");
break;
}
buf[ret] = '\0';
printf ("%s", buf);
}
return 0;
}
// 读一个完整的大数据
int main()
{
int fd = open("BTree.c", O_RDONLY, 0777);
if (fd == -1)
{
perror ("open");
return -1;
}
char buf[SIZE] = {0};
char *p = buf;
int count = SIZE-1; // 每一次要读的数据个数
ssize_t ret = 0;
while (ret = read(fd, p, count))
{
// 出错
if (ret == -1)
{
if (errno == EAGAIN || errno == EINTR)
{
continue;
}
break;
}
printf ("asdasdsadsa\n");
// 读完
if (count == ret)
{
break;
}
count -= ret; // 下一次要读的数据
p += ret;
}
printf ("len = %d\n", strlen(buf));
//printf ("%s\n", buf);
return 0;
}
write.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define SIZE 1024
int main()
{
int fd = open("abc", O_WRONLY|O_CREAT, 0777);
if (fd == -1)
{
perror ("open");
return -1;
}
char buf[SIZE] = {0};
while (1)
{
fgets (buf, SIZE, stdin);
if (strncmp ("end", buf, 3) == 0)
break;
ssize_t ret = write(fd, buf, strlen(buf));
if (ret == -1)
{
perror ("write");
}
printf ("要写的字节数 :%d, 实际写的字节数: %d\n", SIZE, ret);
}
close(fd);
return 0;
}
文件复制
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define SIZE 1024
int main()
{
// 打开要读的文件
int fd1 = open("1.ppt", O_RDONLY);
if (fd1 == -1)
{
perror ("open fd1");
return -1;
}
int fd2 = open("2.ppt", O_WRONLY|O_CREAT, 0777);
if (fd2 == -1)
{
perror ("open fd2");
return -1;
}
int ret = 0;
char buf[SIZE] = {0};
while (ret = read (fd1, buf, SIZE))
{
if (ret == -1)
{
perror("read");
break;
}
write (fd2, buf, ret);
}
printf ("文件复制完成\n");
close (fd1);
close (fd2);
return 0;
}
lseek
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define SIZE 1024
int main()
{
// 打开要读的文件
int fd = open("abc2", O_RDWR|O_CREAT, 0777);
if (fd == -1)
{
perror ("open fd1");
return -1;
}
// 设置这个文件的偏移指针
lseek (fd, 20, SEEK_SET);
char *buf = "hello";
write (fd, buf, strlen(buf));
close (fd);
return 0;
}
大文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define SIZE 1024
int main()
{
// 打开要读的文件
int fd = open("big", O_RDWR|O_CREAT, 0777);
if (fd == -1)
{
perror ("open fd1");
return -1;
}
// 设置这个文件的偏移指针到 1G处
lseek (fd, 1024*1024*1024, SEEK_SET);
char *buf = "hello";
write (fd, "a", 1);
close (fd);
return 0;
}
fread
#include <stdio.h>
#define SIZE 1024
int main()
{
// FILE *fp = fopen("abc", "rb+");
FILE *fp = fopen("BTree.c", "ab+");
if (fp == NULL)
{
perror ("fopen");
return -1;
}
char buf[SIZE] = {0};
// feof 判断是否读到文件结尾,如果读到文件结尾,它返回一个非0 的值
int ret;
while (ret = fread(buf, sizeof(char), SIZE-1, fp))
{
buf[ret*sizeof(char)] = '\0';
printf ("%s\n", buf);
}
if(ret == 0 && !feof(fp))
{
perror ("fread");
return -1;
}
printf ("文件读取结束\n");
return 0;
}
fwrite.c
#include <stdio.h>
#define SIZE 1024
int main()
{
// FILE *fp = fopen("abc", "rb+");
FILE *fp = fopen("1.ppt", "ab+");
if (fp == NULL)
{
perror ("fopen");
return -1;
}
FILE *fp1 = fopen("2.ppt", "ab+");
if (fp1 == NULL)
{
perror ("fopen");
return -1;
}
char buf[SIZE] = {0};
// feof 判断是否读到文件结尾,如果读到文件结尾,它返回一个非0 的值
int ret;
while (ret = fread(buf, sizeof(char), SIZE, fp))
{
fwrite(buf, sizeof(char), ret, fp1);
}
if(ret == 0 && !feof(fp))
{
perror ("fread");
return -1;
}
printf ("文件读取结束\n");
fclose(fp);
fclose(fp1);
return 0;
}
数据读取
#include <stdio.h>
#define SIZE 1024
typedef struct student
{
int id;
char name[20];
}STU;
void write_data(STU *a)
{
FILE *fp = fopen("student", "ab+");
if (fp == NULL)
{
perror ("fopen");
return;
}
// 要写入个数
int len = sizeof(a)/sizeof(a[0]);
fwrite(&len, sizeof(int), 1, fp);
int i;
for (i = 0; i < len; i++)
{
// 写入数据长度
len = sizeof(a[i]);
fwrite(&len, sizeof(int), 1, fp);
// 写入数据
fwrite(&a[i], sizeof(STU), 1, fp);
}
fclose(fp);
}
void read_data()
{
FILE *fp = fopen("student", "ab+");
if (fp == NULL)
{
perror ("fopen");
return;
}
// 读记录的个数
int count;
fread (&count, sizeof(int), 1, fp);
printf ("记录个数是:%d\n", count);
int i;
STU tmp;
for (i = 0; i < count; i++)
{
int len;
fread (&len, sizeof(int), 1, fp);
// 读取数据
fread (&tmp, len, 1, fp);
printf ("id = %d, name = %s\n", tmp.id, tmp.name);
}
fclose(fp);
}
int main()
{
int i;
STU a[20];
for (i = 0; i < 20; i++)
{
a[i].id = i;
sprintf (a[i].name, "zhang%d", i);
}
// 写入数据
// write_data(a);
// 读数据
read_data();
return 0;
}