tags: C Syscall Linux
写在前面
无论是做网络编程还是系统编程, 逃不开的一个内容就是C系统调用的学习, 正如C++的STL一样, 学习OS也有如下的三步骤:
- 会使用: 熟悉API
- 懂原理: 分析源码
- 写扩展: 实际开发
现在就来熟悉一下系统调用吧. 环境Ubuntu x86_64.
源码部分也参考了apue以及Linux/UNIX系统编程手册.
预备知识
什么是系统调用
- 系统调用将处理器从用户态切换到核心态, 以便让CPU访问受到保护的内核内存数据.
- 其组成是固定的, 每一个系统调用都由唯一一个数字来标识.
程序运行四区
非常重要, 全图背诵.
标准文件描述符
文件描述符 | 用途 | POSIX名称 | stdio流 |
---|---|---|---|
0 | 标准输入 | STDIN_FILENO | stdin |
1 | 标准输出 | STDOUT_FILENO | stdout |
2 | 标准错误 | STDERR_FILENO | stderr |
针对stdout调用freopen()函数后,无法保证stdout变量值仍然为1。
常用头文件
头文件 | 包括的常用函数/常量 | 作用 |
---|---|---|
sys/types.h | 类型定义 | |
sys/stat.h | 状态定义 | |
stdio.h | printf ,fprintf | 标准I/O函数 |
stdlib.h | malloc ,free | 标准库函数 |
unistd.h | sleep , | 部分系统调用 |
errno.h | 错误状态码 | |
string.h | memset ,strcpy | 字符创相关操作 (堆内存分配与初始化) |
limits.h | INT_MAX | 系统限制 |
fcntl.h | fcntl , | 文件I/O函数(高级) |
文件I/O
文件访问模式
常用的文件访问模式如下表.
访问模式 | 描述 | 访问模式 | 描述 |
---|---|---|---|
O_RDONLY | 只读打开 | O_CREAT | 不存在则创建 |
O_WRONLY | 只写打开 | O_TRUNC | 截断已有文件 (长度置为0) |
O_RDWR | 读写打开 | O_APPEND | 文件尾部追加 |
备注:
- 调用
open()
时, O_RDONLY、O_WRONLY和O_RDWR标志在flags参数中不能同时使用,只能指定其中一种 - O_TRUNC: 如果文件已经存在且为普通文件,那么将清空文件内容,将其长度置0。在Linux下使用此标志,无论以读、写方式打开文件,都可清空文件内容(在这两种情况下,都必须拥有对文件的写权限)
权限位
权限位st_mode | 含义 | 八进制值 | 英文注记 |
---|---|---|---|
S_IRUSR | 用户读 | 4 | READ USER |
S_IWUSR | 用户写 | 2 | WRITE USER |
S_IXUSR | 用户执行 | 1 | EXEC USER |
S_IRGRP | 组读 | 4 | READ GROUP |
S_IWGRP | 组写 | 2 | WRITE GROUP |
S_IXGRP | 组执行 | 1 | EXEC GROUP |
S_IROTH | 其他用户读 | 4 | READ OTHER |
S_IWOTH | 其他用户写 | 2 | WRITE OTHER |
S_IXOTH | 其他用户执行 | 1 | EXEC OTHER |
例如常用的可执行文件权限位: 755, 就对应了rwx-r-xr-x
, 而默认创建目录的权限位为:
open: 创建文件
fd = open(pathname, flags, mode)
函数打开pathname所标识的文件,并返回文件描述符,用以在后续函数调用中指代打开的文件。如果文件不存在,open()函数可以创建之,这取决于对位掩码参数flags的设置。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
void error_handling(char* msg);
int fd;
void t1() {
// create and write
char buf[] = "Let's go!";
fd = open("data", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (fd == -1) error_handling("open() error");
printf("file descripter: %d\n", fd);
if (write(fd, buf, sizeof(buf)) == -1) error_handling("write() error");
close(fd);
}
void t2() {
// append to log
char buf[] = "abc\n";
fd = open("log", O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
if (fd == -1) error_handling("open() error");
printf("file descripter: %d\n", fd);
if (write(fd, buf, sizeof(buf)) == -1) error_handling("write() error");
close(fd);
}
int main(int argc, char* argv[]) {
t1();
t2();
return 0;
}
void error_handling(char* msg) {
fputs(msg, stderr);
fputc('\n', stderr);
/* printf("aa\n"); */
exit(1);
}
read: 读出流
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
void t1() {
int fd = open("data", O_RDONLY);
char buf[100];
read(fd, buf, 10);
printf("buf=%s\n", buf); // buf=Let's go!
close(fd);
}
int main(int argc, char *argv[]) {
t1();
return 0;
}
write: 写入流
例子见open()
.
close: 关闭文件
lseek: 改变文件偏移量
对于每个打开的文件,系统内核会记录其文件偏移量,有时也将文件偏移量称为读写偏移量或指针。文件偏移量是指执行下一个read()或write()操作的文件起始位置,会以相对于文件头部起始点的文件当前位置来表示。文件第一个字节的偏移量为0。
文件打开时,会将文件偏移量设置为指向文件开始,以后每次read()或write()调用将自动对其进行调整,以指向已读或已写数据后的下一字节。因此,连续的read()和write()调用将按顺序递进,对文件进行操作。
针对文件描述符fd参数所指代的已打开文件,lseek()系统调用依照offset和whence参数值调整该文件的偏移量。
offset参数指定了一个以字节为单位的数值。(SUSv3规定off_t数据类型为有符号整型数。)whence参数则表明应参照哪个基点来解释offset参数,应为下列其中之一:
- SEEK_SET: 将文件偏移量设置为从文件头部起始点开始的offset个字节
- SEEK_CUR: 相对于当前文件偏移量,将文件偏移量调整offset个字节.
- SEEK_END: 将文件偏移量设置为起始于文件尾部的offset个字节。也就是说,offset参数应该从文件最后一个字节之后的下一个字节算起.
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
void t1() {
int fd = open("data", O_WRONLY, S_IWUSR);
int cur = lseek(fd, 0, SEEK_CUR);
printf("cur seek = %d\n", cur);
cur = lseek(fd, 2, SEEK_SET);
printf("cur seek = %d\n", cur);
}
int main(int argc, char *argv[]) {
t1();
return 0;
}
文件信息
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
int main(int argc, char *argv[]) {
struct stat sb;
if (stat("data", &sb) == -1) fprintf(stderr, "stat-error");
printf("size=%ld\n", sb.st_size);
printf("mode=%u\n", sb.st_mode);
printf("uid=%u\n", sb.st_uid);
printf("gid=%u\n", sb.st_gid);
printf("hard link number=%ld\n", sb.st_nlink);
putchar('\n');
printf("dev-id=%ld\n",sb.st_dev);
printf("rdev-id=%ld\n", sb.st_rdev);
printf("i-node=%ld\n", sb.st_ino);
printf("block-size=%ld\n", sb.st_blksize);
printf("blocks=%ld\n", sb.st_blocks);
putchar('\n');
printf("last access time=%s", ctime(&sb.st_atime));
printf("last modify time=%s", ctime(&sb.st_mtime));
printf("last status change time=%s\n", ctime(&sb.st_ctime));
return 0;
}
/* size=10 */
/* mode=33152 */
/* uid=1001 */
/* gid=1001 */
/* hard link number=1 */
/* */
/* dev-id=64513 */
/* rdev-id=0 */
/* i-node=1212099 */
/* block-size=4096 */
/* blocks=8 */
/* */
/* last access time=Thu Feb 9 17:42:47 2023 */
/* last modify time=Sun Feb 5 01:19:56 2023 */
/* last status change time=Sun Feb 5 01:19:56 2023 */
目录I/O
mkdir: 创建目录
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
// rw-r--r--
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
// rwxrwxr-x
#define DIR_MODE (FILE_MODE | S_IXUSR | S_IWGRP | S_IXGRP | S_IXOTH)
int main(int argc, char *argv[]) {
mkdir("new_dir", DIR_MODE);
return 0;
}
getpwd: 获取当前目录
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
char *ptr;
size_t size = 100;
ptr = (char *)malloc(size);
getcwd(ptr, size);
printf("cwd = %s\n", ptr); // cwd = /home/zorch/code/c_cpp_code/syscall/dirio
return 0;
}
chdir: 更改当前目录
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
char *ptr;
size_t size = 100;
ptr = (char *)malloc(size);
chdir("/usr/local/lib");
getcwd(ptr, size);
printf("cwd = %s\n", ptr); // cwd = /usr/local/lib
return 0;
}