01. linux系统编程入门
入门系统编程,首先理解一下
- 基本的系统调用和库函数的区别
- 一切皆文件的思想,都是通过文件描述符来进行操作
- strace命令
文件读写系统调用
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main (void)
{
int fd;
fd = open("hello", O_RDWR|O_CREAT, 0666);
// 0666 (oct) = 110 110 110 (binary) = rw-rw-rw- (文件权限)
//所有者(User)| 同组 Group | 其他人 Other的权限:可读 (r),可写 (w),不可执行 (-)
if (fd == -1)
{
printf("open file failed!\n");
return -1;
}
char string[20] = "hello world!\n";
write (fd, string, 14);
fsync (fd); // 同步刷新到磁盘中
char *buf = (char*)malloc (20);
memset(buf, 0, 20);
lseek(fd, 0, SEEK_SET);
// SEEK_SET:文件开始 | SEEK_CUR:文件当前位置 | SEEK_END:文件末尾
read(fd, buf, 14);
printf("%s", buf);
free(buf);
close(fd);
return 0;
}
复制文件
将源文件读出来后写到另一个文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFFERSIZE 4096
int main(int argc, char *argv[])
{
if (argc != 3)
{
printf("Usage: copy src dest\n");
return 1;
}
int srcfd = open(argv[1], O_RDONLY); // 以只读的方式打开文件
if (srcfd == -1)
{
perror("open srcfd failed");
return 1;
}
int dstfd = open(argv[2], O_WRONLY|O_CREAT, 0666); // 以只写的方式,不存在时创建的情况打开文件
if (dstfd == -1)
{
perror("open destfd failed");
return 1;
}
int len = 0;
char buffer[BUFFERSIZE] = {0};
// 读写都会返回读写的长度,需要对长度进行检查
while ((len = read(srcfd, buffer, BUFFERSIZE)) > 0)
{
if (write(dstfd, buffer, len) != len) // 对写操作进行检查
{
perror("Write error");
return 2;
}
}
// 读操作如果返回 len<0 就是出现了问题
if (len < 0)
{
perror("read error");
return 3;
}
close(srcfd);
close(dstfd);
return 0;
}
文件系统调用参数选项

以上是文件的系统调用,然后我们来理解一下文件的C标准库
文件C标准库读写文件
#include <stdio.h>
#include <stdlib.h>
struct student
{
char name[10];
int age;
float score;
} stu[2];
int main (void)
{
for (int i = 0; i < 2; i ++)
{
printf("please input name age score:\n");
scanf("%s %d %f", stu[i].name, &stu[i].age, &stu[i].score);
}
FILE *fp;
if ((fp = fopen("hello.dat", "w+")) == NULL)
{
printf("fopen failed!\n");
return -1;
}
fwrite(&stu, sizeof(struct student), 2, fp);
if (ferror(fp) != 0) // 检测文件流fp是否发生了错误
{
printf("fwrite failed!\n");
clearerr(fp);
return -1;
}
fflush(fp);
rewind(fp); // 等价于 fseek(fp, 0, SEEK_SET)
struct student *buf = (struct student*)malloc (2*sizeof(struct student));
if (ferror(fp) != 0)
{
printf("fread failed!\n");
clearerr(fp);
return -1;
}
printf("姓名\t年龄\t分数\t\n");
for (int i = 0; i < 2; i ++)
printf("%s\t%d\t%f\n", buf[i].name, buf[i].age, buf[i].score);
fclose(fp);
free(buf);
buf = NULL;
return 0;
}
以上有几个点:
FILE *: 是 C 语言标准库中的 文件指针,用于管理 文件流。它指向一个 FILE 结构体,该结构体维护了:- 文件描述符
- 缓冲区
- 当前读/写位置
- 状态(EOF、错误等)
- 因为是一个结构体有进一步的包装,所以判断异常要使用
ferror(fp) != 0
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);- 用于向文件写入二进制数据
- prt:指向要写入的数据的指针
- size:每个元素的大小
- nmemb:要写入的元素大小
- stream:目标文件流指针
系统调用和C标准库
所以对于系统调用和C标准库,它的不同就比如说在linux下对文件的系统调用是open()等等,然后有c标准库,它使用了FILE *,然后fopen来打开,实际上底层还是使用的open()
只是对这个方法的有一层封装,让其更实用一点,而且FILE *本身有缓存区,FILE * 结构有缓冲区(buffering),这意味着 fread()、fwrite() 可能不会每次都直接调用 read()、write(),而是先写到缓冲区,等缓冲区满了才调用系统调用,这样能减少系统调用次数,提高性能,而且也提供了更多方便的方法
另一个原因就是因为说如果是不同的平台比如说win下他对文件的系统调用可能不是open(),如果使用c标准库,你只用 fopen() 而不用管 Linux、Windows、macOS 具体的系统调用,它们会自动适配不同的操作系统。,从而实现跨平台性,这也是很重要的


对linux就是使用虚拟文件系统(VFS), 一个 抽象层 统一了文件系统接口,让应用程序和内核不需要关心底层使用的是什么文件系统,在内核中提供了统一的文件操作接口
另外,系统调用的流程是:
- 软中断:X86下int 0x80;ARM架构下 SWI软中断指令
- 寄存器保存相关参数:参数、系统调用号
- 进入内核态,执行内核特权指令代码函数
- 返回值保存到寄存器
- 返回到用户态、系统调用结束
strace命令的使用
当你想要知道你的程序调用的方法,花费时间时,可以使用strace来观察
-e 的作用就是指定显示,这里就是指定显示open和close的调用
<0.000089> 就是 -T 的作用显示系统调用花费的时间
$ strace -T -e open,close ./a.out
close(3) = 0 <0.000089>
close(3) = 0 <0.000113>
close(3) = 0 <0.000086>
open hello.dat failed!
+++ exited with 255 +++
-tt 就是在前面显示具体的时间戳 07:42:19.519597 这种
$ strace -tt -T -e open,close ./a.out
07:42:19.519597 close(3) = 0 <0.000137>
07:42:19.524255 close(3) = 0 <0.000112>
07:42:19.528934 close(3) = 0 <0.000452>
open hello.dat failed!
07:42:19.532642 +++ exited with 255 +++
-o log 作用就是将输出写到 log 文件中
$ strace -tt -T -e open,close -o log ./a.out
open hello.dat failed!
lqy@lqy:~/projects/system_prog/00. 入门篇/08.strace$ ls
08.strace.c a.out log stract命令使用.md test.dat
明明指定了看open()的系统调用,为什么没有?
- 不显示open的系统调用是因为在现代 Linux 版本(glibc 2.26+)
open()可能被openat()取代,所以strace -e open可能不会捕获
$ strace -tt -T -e open,openat,close ./a.out
07:49:51.845942 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 <0.000249>
07:49:51.849882 close(3) = 0 <0.000096>
07:49:51.850700 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 <0.000136>
07:49:51.854783 close(3) = 0 <0.000254>
07:49:51.858475 openat(AT_FDCWD, "test.dat", O_RDONLY|O_CREAT, 051010) = 3 <0.000129>
// 这里open返回的 = 3 就是说这个打开后给的文件描述符是 3
// 打开失败就是返回的文件描述符是 -1
07:49:51.859134 openat(AT_FDCWD, "hello.dat", O_WRONLY) = -1 ENOENT (没有那个文件或目录) <0.000114>
07:49:51.860171 close(3) = 0 <0.000116>
open hello.dat failed!
07:49:51.862608 +++ exited with 255 +++
显示错误信息 errno
系统编程错误一般通过函数的返回值表示,执行成功,返回0或正确值,执行失败,返回-1,并把系统全局变量errno赋值,指示具体错误,一般返回值只知道是否执行成功,而更具体的错误由errno指出,使用perror可以输出具体错误原因
全局变量errno由操作系统维护:当系统调用或调动库函数出错时,会重置该值
int main (void)
{
int fd;
fd = open("hello.dat", O_WRONLY);
if (fd == -1)
{
// 会输出 open hello.dat failed!: No such file or directoryopen hello.dat failed:
// 输出了用户定义的部分后,后面会加上erron执行的错误
perror ("open hello.dat failed!");
return -1;
}
close(fd);
return 0;
浙公网安备 33010602011771号