博客主页:PannLZ
系列专栏:《Linux系统之路》
不要让自己再留有遗憾,加油吧!
open()调用既能打开一个业已存在的文件,也能创建并打开一个新文件。
#include
#include
int open(const char* pathname,int flags,.../*mode_t mode*/);
//return file descriptor on success,or -1 on error
stat是status的缩写
fcntl是file control的缩写
要打开的文件由参数 pathname 来标识。如果 pathname 是一符号链接,会对其进行解引用。
如果调用成功,open()将返回一文件描述符,用于在后续函数调用中指代该文件。若发生错误,
则返回−1,并将 errno 置为相应的错误标志。
参数 flags 为位掩码,用于指定文件的访问模式
标 志 | 用 途 |
---|---|
O_RDONLY | 以只读方式打开 |
O_WRONLY | 以只写方式打开 |
O_RDWR | 以读写方式打开 |
O_CLOEXEC | 设置 close-on-exec 标志(自 Linux 2.6.23 版本开始) |
O_CREAT | 若文件不存在则创建之 |
O_DIRECT | 无缓冲的输入/输出 |
O_DIRECTORY | 如果 pathname 不是目录,则失败 |
O_EXCL | 结合 O_CREAT 参数使用,专门用于创建文件 |
O_LARGEFILE | 在 32 位系统中使用此标志打开大文件 |
O_NOATIME | 调用 read()时,不修改文件最近访问时间(自 Linux 2.6.8版本开始) |
O_NOCTTY | 不要让 pathname(所指向的终端设备)成为控制终端 |
O_NOFOLLOW | 对符号链接不予解引用 |
O_TRUNC | 截断已有文件,使其长度为零 |
O_APPEND | 总在文件尾部追加数据 |
O_ASYNC | 当 I/O 操作可行时,产生信号(signal)通知进程 |
O_DSYNC | 提供同步的 I/O 数据完整性(自 Linux 2.6.33 版本开始) |
O_NONBLOCK | 以非阻塞方式打开 |
O_SYNC | 以同步方式写入文件 |
当调用 open()创建新文件时,位掩码参数 mode 指定了文件的访问权限。
访 问 模 式 | 描述 |
---|---|
O_RDONLY | 以只读方式打开文件 |
O_WRONLY | 以只写方式打开文件 |
O_RDWR | 以读写方式打开文件 |
read()系统调用从文件描述符 fd 所指代的打开文件中读取数据。
#include
ssize_t read(int fd, void *buf, size_t count);
count 参数指定最多能读取的字节数。(size_t 数据类型属于无符号整数类型。)
buffer 参数提供用来存放输入数据的内存缓冲区地址。缓冲区至少应有 count 个字节。\
write()系统调用将数据写入一个已打开的文件中。
#include
ssize_t write(int fd, const void *buf, size_t count);
buffer 参数为要写入文件中数据的内存地址
count参数为欲从 buffer 写入文件的数据字节数
fd 参数为一文件描述符,指代数据要写入的文件
close()系统调用关闭一个打开的文件描述符,并将其释放回调用进程,供该进程继续使用。 当一进程终止时,将自动关闭其已打开的所有文件描述符。
#include
int close(int fd);
文件偏移量是指执行下一个 read()或 write()操作的文件起始位置,会以相对于文 件头部起始点的文件当前位置来表示。文件第一个字节的偏移量为 0。
#include
#include
off_t lseek(int fd, off_t offset, int whence);
fd是文件描述符
offset是偏移量
whence是偏移量的基准位置。它的取值有三个
- SEEK_SET: 开始位置
- SEEK_CUR: 当前位置
- SEEK_END: 末尾位置
为什么开始位置的后缀是**_SET**
实际上,在man手册中可以看出。这三个宏的描述是
- SEEK_SET The offset is set to offset bytes.
- SEEK_CUR The offset is set to its current location plus offset bytes.
- SEEK_END The offset is set to the size of the file plus offset bytes.
利用系统调用 stat()、lstat()以及 fstat(),可获取与文件有关的信息,其中大部分提取自文件 i 节点。
#include
#include
#include
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
stat检查符号链接时,实际检查的是符号链接所引用的文件
lstat检查符号链接时,检查的是符号链接本身
fstat功能与stat相同,不过它的参数是文件的描述符
上述所有系统调用都会在缓冲区中返回一个由 statbuf 指向的 stat 结构,其格式如下:
struct stat {
dev_t st_dev; /* 设备号(主设备号,次设备号)*/
ino_t st_ino; /* inode的数量 */
mode_t st_mode; /* 文件的类型和存取的权限 */
nlink_t st_nlink; /* 硬链接的数量 */
uid_t st_uid; /* 所用者的uid */
gid_t st_gid; /* 所有者的组id */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* 总大小,字节数 */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* 最后访问时间(access)*/
time_t st_mtime; /* 最后修改时间(modification)文件内容的改动时间 */
time_t st_ctime; /* 最后改动时间(change)文件属性的改动时间 */
};
从父进程派生出子进程,子进程完全拷贝父进程的stack,data,heap segment。
两者并不共享地址空间,所以的变量是独立的,一方修改,另一方不会变化。
#include
pid_t fork(void);
理解 fork()的诀窍是,要意识到,完成对其调用后将存在两个进程,且每个进程都会从 fork()
的返回处继续执行。
习惯用语
pid_t childPid /*used in parent after successful fork() to record PID of child*/
swith(childPid = fork()){
case -1:
/*Handle error*/ /*fork() failed*/
case 0:
/*perform action specific to child*/ /*child of successful fork() come here*/
default:
/*Perform action specific to parent*/ /*Parent comes here after successful fork()*/
}
调用 fork()之后,系统将率先“垂青”于哪个进程(即调度其使用 CPU),是无法确定的
#include
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start)(void*),void *arg);
Return 0 on success
新线程通过调用带有参数 arg 的函数 start(即 start(arg))而开始执行。
将参数 arg 声明为 void*类型,意味着可以将指向任意对象的指针传递给 start()函数。
如果需要向 start()传递多个参
数,可以将 arg 指向一个结构,该结构的各个字段则对应于待传递的参数。
参数 attr 是指向 pthread_attr_t 对象的指针,该对象指定了新线程的各种属性。
函数 pthread_join()等待由 thread 标识的线程终止。(如果线程已经终止,pthread_join()会立即返回)。这种操作被称为连接(joining)。
#include
int pthread_join(pthread_t thread,void **retval);
若 retval 为一非空指针,将会保存线程终止时返回值的拷贝,该返回值亦即线程调用return 或 pthred_exit()时所指定的值。
如向 pthread_join()传入一个之前已然连接过的线程 ID,将会导致无法预知的行为。
若线程并未分离(detached),则必须使用ptherad_join()来进行连接。如果未能
连接,那么线程终止时将产生僵尸线程,与僵尸进程(zombie process)的概念相类似
默认情况下,线程是可连接的(joinable),也就是说,当线程退出时,其他线程可以通过调
用 pthread_join()获取其返回状态。有时,程序员并不关心线程的返回状态,只是希望系统在线程终止时能够自动清理并移除之。在这种情况下,可以调用 pthread_detach()并向 thread 参数传入指定线程的标识符,将该线程标记为处于分离(detached)状态。
#include
int pthread_detatch(pthread_t thread);
使用 pthread_detach(),线程可以自行分离:pthread_detach(pthread_self());
一旦线程处于分离状态,就不能再使用 pthread_join()来获取其状态,也无法使其重返“可连接”状态。
使用pthread_create创建的线程有两种状态:joinable和unjoinable。默认是joinable 状态
pthread_detach()和pthread_join()就是控制子线程回收资源的两种不同的方式。同一进程间的线程具有共享和独立的资源,其中共享的资源有堆、全局变量、静态变量、文件等公用资源。而独享的资源有栈和寄存器,这两种方式就是决定子线程结束时如何回收独享的资源。
如果是joinable状态,则该线程结束后(通过pthread_exit结束或者线程执行体任务执行完毕)不会释放线程所占用堆栈和线程描述符(总计8K多)等资源,除非在主线程调用了pthread_join函数之后才会释放。pthread_join函数一般应用在主线程需要等待子线程结束后才继续执行的场景。(pthread_join是一个阻塞函数,调用方会阻塞到pthread_join所指定的tid的线程结束后才被回收,但是在此之前,调用方是霸占系统资源的。 )
如果是unjoinable状态,则该线程结束后会自动释放占用资源。实现方式是在创建时指定属性,或者在线程执行体的最开始处添加一行:pthread_detach(pthread_self());不会阻塞,调用它后,线程运行结束后会自动释放资源,后者非常方便。
pthread_detach()即主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收。
pthread_join()即是子线程合入主线程,主线程会一直阻塞,直到子线程执行结束,然后回收子线程资源,并继续执行。
可连接的线程 能够被其他线程回收或杀死,在其被杀死前,内存空间不会自动被释放。
可分离的线程 不能被其他线程回收或杀死,其内存空间在它终止时由系统自动释放。
在编译(**gcc**)包含pthread函数的程序时,要显式地链接线程库,即**-lpthread**。