常用函数
1. linux系统IO函数
1.1 open/close函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// open函数只有一个, 是变参函数
// 作用: 打开或者的创建一个文件
// 打开文件
int open(const char *pathname, int flags);
参数:
- pathname: 要打开的文件的路径(相对/绝对)
- flag: 打开的文件权限
- 三选一(必须):
- O_RDONLY: 只读
- O_WRONLY: 只写
- O_RDWR: 读写
- 附加属性:
- O_APPEND: 写文件的时候追加
- O_CREAT: 创建新文件
- O_EXCL: 检测文件是否存在
- 必须和O_CREAT一起使用
- O_EXCL|O_CREAT
- 如果文件存在, open调用失败, 返回-1
- 如果文件不存在, 创建新文件
返回值:
- 成功: 文件描述符
- 失败: -1
// 创建文件
int open(const char *pathname, int flags, mode_t mode);
参数:
- mode: 八进制的整形数, 指定的新文件的权限
- 文件所有者/所属组/其他人对文件的访问权限
- 最终权限需要计算: (mode & ~umask)
777 111111111
775 111111101
&
775 111111101
#include <unistd.h>
// 关闭文件
// 参数: fd: open函数的返回值, 也就是要关闭的文件对应的文件描述符
int close(int fd);
1.2 read/write函数
#include <unistd.h>
// 读文件
ssize_t read(int fd, void *buf, size_t count);
参数:
- fd: open函数的返回值, 也就是要读的文件对应的文件描述符
- buf: 指向一块有效的内存地址, 存储读到的数据
- count: 描述了buf参数对应的内存大小
返回值:
- 失败: -1
- 成功:
>0: 实际读出的字节数
=0: 文件读完了
errno:
EAGAIN 通常指非阻塞模式下文件读完
#include <unistd.h>
// 写文件
ssize_t write(int fd, const void *buf, size_t count);
参数:
- fd: open函数的返回值, 也就是要写的文件对应的文件描述符
- buf: 存储了要写入磁盘的数据
- count: 参数buf中存储的数据的长度 == 要往文件里写入的数据长度
返回值:
- 成功:
>=0: 实际写入文件的字节数
- 失败: -1
// 1. 拷贝文件(比较大)
int fd = open("bigfile", O_RDONLY);
int fd1 = open("copyfile", O_WRONLY|O_CREATE, 0664);
// 2.
int len = 0;
char buf[1024];
while((len=read(fd, buf, sizeof(buf)) != 0)
{
write(fd1, buf, len);
}
close(fd);
close(fd1);
1.3 lseek函数
#include <sys/types.h>
#include <unistd.h>
// 移动文件指针
off_t lseek(int fd, off_t offset, int whence);
参数:
- fd: open函数的返回值, 也就是要操作文件对应的文件描述符
- offset: 偏移量, 整形数, 代表的意思要结合第三个参数看
- whence:
- SEEK_SET: 设置文件指针的位置 : 从文件的开头位置开始偏移
- SEEK_CUR: 当前文件指针的位置 + 第二个参数的偏移量:从文件的当前位置开始偏移
- SEEK_END: 文件的大小 + 第二个参数的偏移量:从文件的末尾位置开始偏移
返回值:
- 成功: 文件指针最后的位置
- 失败: -1
// 三个操作
// 1. 移动到文件头
lseek(fd, 0, SEEK_SET);
// 2. 获取文件指针当前的位置
lseek(fd, 0, SEEK_CUR);
// 3. 获取文件长度
lseek(fd, 0, SEEK_END);
// 4. 拓展文件大小
lseek(fd, 100, SEEK_END);
// 如果想要文件拓展成功, 必须要再进行一次写操作
write(fd, "a", 1);
1.4 truncate拓展或者截断
#include <unistd.h>
#include <sys/types.h>
// 文件拓展或截断
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
参数:
- path: 磁盘文件路径
- fd: 通过open函数打开了磁盘文件得到了文件描述符
- length: 文件的最终长度
- 假设源文件长度 > length, 文件变小了
- 文件尾部数据被删除
- 假设源文件长度 < length, 文件变大了
- 文件被拓展, 增加的部分被填充了0
1.5 stat/lstat查看文件属性
// 函数原型 -> 查看文件的属性信息
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限, 16位整形数
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
blksize_t st_blksize; //块大小(文件系统的I/O 缓冲区大小)
blkcnt_t st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间(文件内容)
time_t st_ctime; //最后一次改变时间(指属性)
};
int stat(const char *pathname, struct stat *buf);
参数:
- pathname: 要操作的文件的路径和名字 /home/robin/a.txt ./a.txt
- buf: 结构体指针, 传出参数, stat函数会将得到的属性信息写入该指针对应的内存中
int lstat(const char *path, struct stat *buf);
可以判断是否是软连接文件
- 获取文件大小
int main()
{
struct stat st;
stat("abc.txt", &st);
printf("abc.txt size: %d\n", (int)st.st_size);
return 0;
}
-
获取文件类型
S_ISREG(m) is it a regular file? S_ISDIR(m) directory? S_ISCHR(m) character device? S_ISBLK(m) block device? S_ISFIFO(m) FIFO (named pipe)? S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.) S_ISSOCK(m) socket? (Not in POSIX.1-1996.) int main() { struct stat st; stat("./hello", &st); // 判断文件类型 if(S_ISREG(st.st_mode)) { printf("该文件是一个普通文件\n"); } else if(S_ISDIR(st.st_mode)) { printf("这是一个目录\n"); } printf("abc.txt size: %d\n", (int)st.st_size); return 0; }
-
获取对文件的操作权限
S_ISREG(m) is it a regular file? S_ISDIR(m) directory? S_ISCHR(m) character device? S_ISBLK(m) block device? S_ISFIFO(m) FIFO (named pipe)? S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.) S_ISSOCK(m) socket? (Not in POSIX.1-1996.) int main() { struct stat st; stat("./hello", &st); // 判断文件类型 if(S_ISREG(st.st_mode)) { printf("该文件是一个普通文件\n"); } else if(S_ISDIR(st.st_mode)) { printf("这是一个目录\n"); } printf("abc.txt size: %d\n", (int)st.st_size); return 0; }
1.6 opendir 打开目录函数
#include <dirent.h>
// 打开目录
DIR *opendir(const char *name);
参数: name: 要打开的目录的名字
返回值:
- 成功: 目录指针
- 失败: NULL
1.7 readdir 读目录函数
#include <dirent.h>
struct dirent {
ino_t d_ino; /* 通过这个变量找到文件在磁盘位置 */
off_t d_off; /* 偏移量, 用不到 */
unsigned short d_reclen; /* 变量d_name中存储的实际数据长度 */
unsigned char d_type; /* 当前文件的类型 */
char d_name[256]; /* 当前文件的文件名 */
};
文件类型 d_type :
DT_BLK This is a block device.
DT_CHR This is a character device.
DT_DIR This is a directory.
DT_FIFO This is a named pipe (FIFO).
DT_LNK This is a symbolic link.
DT_REG This is a regular file.
DT_SOCK This is a UNIX domain socket.
DT_UNKNOWN The file type is unknown.
// 因为在读一个目录的时候, 目录中文件个数一般>1
// 读这个目录的时候,可以通过readdir不停的读该目录, 当读完之后返回NULL
struct dirent *readdir(DIR *dirp);
参数:
- dirp: 打开的目录指针, opendir 的返回值
返回值:
- 结构体指针, 这个结构体中包含了读到的该文件的属性
- 没有在该目录中读到文件, 返回值为NULL
1.8 closedir目录关闭
#include <sys/types.h>
#include <dirent.h>
// 关闭目录
int closedir(DIR *dirp);
参数是 opendir 的返回值
-
读指定目录下普通文件的个数
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <dirent.h> int getRegFiles(const char* path) { // 1. 打开目录 DIR* dir = opendir(path); if(dir == NULL) { perror("opendir"); return 0; } // 2. 遍历当前目录 struct dirent* ptr = NULL; int count = 0; while((ptr = readdir(dir)) != NULL) { // 如果是目录 . .. if(strcmp(ptr->d_name, ".")==0 || strcmp(ptr->d_name, "..") == 0) { continue; } // 假设读到的当前文件是目录 if(ptr->d_type == DT_DIR) { // 目录 char newPath[1024]; sprintf(newPath, "%s/%s", path, ptr->d_name); // 读当前目录的子目录 count += getRegFiles(newPath); } else if(ptr->d_type == DT_REG) { // 普通文件 count ++; } } closedir(dir); return count; } int main(int argc, char* argv[]) { // ./a.out path if(argc < 2) { printf("./a.out path\n"); return 0; } int num = getRegFiles(argv[1]); printf("%s 目录中普通文件个数: %d\n", argv[1], num); return 0; }
1.9 dup、dup2、fcntl
#include <unistd.h>
// 复制文件描述符
// 返回值和参数指向的是同一个磁盘文件
int dup(int oldfd);
参数: 要被复制的文件描述符
返回值: 被复制出的文件描述符
#include <unistd.h>
/*
场景1:
oldfd -> a.txt
newfd -> b.txt
函数调用成功:
newfd不在指向b.txt, newfd指向了a.txt
场景2:
oldfd -> a.txt
newfd -> 不指向任何文件
函数调用成功:
newfd指向了a.txt
场景2:
oldfd -> a.txt
newfd -> a.txt
函数调用成功:
什么事儿也不干
*/
// 复制文件描述符/重定向文件描述符
int dup2(int oldfd, int newfd);
参数oldfd: 要被拷贝的文件描述符
参数newfd: 要将oldfd中的信息拷贝到newfd中
返回值返回 newfd 的值
// 函数原型
int fcntl(int fd, int cmd, ... /* arg */ );
// 1. 复制文件描述符
int fcntl(int fd, int cmd, ... /* arg */ );
参数fd: 要复制的文件描述符
参数cmd: 控制这个函数到底做什么, 现在要复制文件描述符使用: F_DUPFD
返回值: 得到的复制完成之后的新的文件描述符
// 举例
int fd = open("a.txt", O_RDWR);
int newfd = fcntl(fd, F_DUPFD);
// 2. 修改已经打开的文件的flag属性
int open(const char *pathname, int flags);
int fcntl(int fd, int cmd, ... /* arg */ );
参数fd: 打开的文件的文件描述符
参数cmd:
- 获取文件设置的flags属性: F_GETFL
- 设置文件的flags属性: F_SETFL
- 可以被设置的属性: O_APPEND, O_NONBLOCK
- 不能被设置的属性:O_RDONLY, O_WRONLY,O_RDWR,O_CREAT, O_EXCL,O_TRUNC
2. 标准C库文件函数
2.1 fopen/fclose 函数
FILE * fopen(const char * filename, const char * mode);
功能:打开文件
参数:
filename:需要打开的文件名,根据需要加上路径
mode:打开文件的权限设置
返回值:
成功:文件指针
失败:NULL
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qf2xnKaf-1629332614411)(C:\Users\notbu\Desktop\assets\1.png)]
int fclose(FILE * stream);
功能:关闭先前fopen()打开的文件。此动作让缓冲区的数据写入文件中,并释放系统所提供的文件资源。
参数:
stream:文件指针
返回值:
成功:0
失败:-1
2.2 字符读写fgetc/fputc/feof
int fputc(int ch, FILE * stream);
功能:将ch转换为unsigned char后写入stream指定的文件中
参数:
ch:需要写入文件的字符
stream:文件指针
返回值:
成功:成功写入文件的字符
失败:返回-1
int fgetc(FILE * stream);
功能:从stream指定的文件中读取一个字符
参数:
stream:文件指针
返回值:
成功:返回读取到的字符
失败:-1
int feof(FILE * stream);
功能:检测是否读取到了文件结尾
参数:
stream:文件指针
返回值:
非0值:已经到文件结尾
0:没有到文件结尾
2.3 行读写fgets/fputs
int fputs(const char * str, FILE * stream);
功能:将str所指定的字符串写入到stream指定的文件中, 字符串结束符 '\0' 不写入文件。
参数:
str:字符串
stream:文件指针
返回值:
成功:0
失败:-1
char * fgets(char * str, int size, FILE * stream);
功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。
参数:
str:字符串
size:指定最大读取字符串的长度(size - 1)
stream:文件指针
返回值:
成功:成功读取的字符串
读到文件尾或出错: NULL
###2.4 块读写fwrite/fread
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式给文件写入内容
参数:
ptr:准备写入文件数据的地址
size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小
nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb
stream:已经打开的文件指针
返回值:
成功:实际成功写入文件数据的块数,此值和nmemb相等
失败:0
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式从文件中读取内容
参数:
ptr:存放读取出来数据的内存空间
size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小
nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb
stream:已经打开的文件指针
返回值:
成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。
失败:0
2.4 格式化读写fscanf/fprintf
int fprintf(FILE * stream, const char * format, ...);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 '\0' 为止。
参数:
stream:已经打开的文件
format:字符串格式,用法和printf()一样
返回值:
成功:实际写入文件的字符个数
失败:-1
int fscanf(FILE * stream, const char * format, ...);
功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。
参数:
stream:已经打开的文件
format:字符串格式,用法和scanf()一样
返回值:
成功:实际从文件中读取的字符个数
失败: - 1
注意:fscanf遇到空格和换行时结束。
2.5 随机读写fseek/ftell
int fseek(FILE *stream, long offset, int whence);
功能:移动文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了 文件末尾,再次写入时将增大文件尺寸。
whence:其取值如下:
SEEK_SET:从文件开头移动offset个字节
SEEK_CUR:从当前位置移动offset个字节
SEEK_END:从文件末尾移动offset个字节
返回值:
成功:0
失败:-1
long ftell(FILE *stream);
功能:获取文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
返回值:
成功:当前文件流(文件光标)的读写位置
失败:-1
void rewind(FILE *stream);
功能:把文件流(文件光标)的读写位置移动到文件开头。
参数:
stream:已经打开的文件指针
返回值:
无返回值
3.标准C库数学函数
3.1 abs/fabs
4.linux 进程函数
4.1 fork 进程创建
#include <unistd.h>
// 创建一个新的进程
// 函数调用成功之后, 进程的个数在原基础上添加一个
pid_t fork(void);
返回值:
- 失败: -1, 子进程不会被创建
- 成功: 子进程被创建, 有两个返回值
- 子进程: 返回0
- 父进程: 返回子进程的进程ID
4.2 execl和execlp函数
// 函数原型
#include <unistd.h>
extern char **environ;
// l --> list
// 一般使用execl启动磁盘上的自己编译出的可执行程序
// 启动系统自带的可执行程序也是可以的, 但是必须要指定路径
int execl(const char *path, const char *arg, .../* (char *) NULL */);
参数:
- path: 要启动的磁盘上的可执行程序(必须带路径)
- 绝对路径: 建议
- 相对路径: 程序如果改变存储位置, path有可能就找不到了(不建议)
- arg: 这是一个字符串描述(启动的进程名字), 出现下 ps aux 的 COMMOND 列
- ...: 启动的可执行程序的参数(>=0), 如果参数结束了, 末尾使用NULL(哨兵)
如果有多个参数:
"a", "u", "x", NULL
// p --> path
// 启动系统自带的可执行程序 -> 不需要指定路径, 直接写文件名即可
// 启动磁盘上的自己编译出的可执行程序 -> 指定路径
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
参数:
- file: 要启动的磁盘上的可执行程序的名字(只写名字就可以)
- 指定的这个名字在执行的时候, 会搜索环境变量 -> PATH (对应的值是n个路径)
- 搜索这些路径中有没有叫file的可执行程序
- arg: 这是一个字符串描述(启动的进程名字), 出现下 ps aux 的 COMMOND 列
- ...: 启动的可执行程序的参数(>=0), 如果参数结束了, 末尾使用NULL(哨兵)
如果有多个参数:
"a", "u", "x", NULL
// 以上两个函数共同的特点:
- 函数执行成功之后不会返回
- 函数执行失败了返回 -1
4.3 exit结束进程
// 结束进程的函数
#include <stdlib.h>
// 参数是进程退出的状态码,此函数结束时会释放进程PCB资源
void exit(int status);
#include <unistd.h>
void _exit(int status);
void funcA()
{
exit(0); // 进程退出
return ; // 返回到调用者的位置
}
int main()
{
funcA();
return 0; // 退出进程
}
4.4 wait和waitpid进程回收
#include <sys/types.h>
#include <sys/wait.h>
// 回收子进程资源
// 函数调用一次回收一个子进程
// 这是一个阻塞函数: 子进程不死, 该函数一直阻塞, 等待子进程退出, 解除阻塞回收子进程资源
// 没有子进程了就不阻塞了, 函数返回-1
pid_t wait(int *status);
参数:
- status: 传出参数, 返回进程退出时候的一些状态信息, 一般时候用不着, NULL
返回值:
成功: 回收的子进程的进程ID
失败: -1
#include <sys/types.h>
#include <sys/wait.h>
// 该函数可以设置为阻塞或非阻塞
// 可以指定回收哪一个子进程的资源
// 不管阻塞还是非阻塞, 没有子进程返回-1
pid_t waitpid(pid_t pid, int *status, int options);
参数:
- pid:
<-1: 取绝对值, 作为某个进程的组ID, 回收的是这个组中的所有子进程资源
-1: 回收任何子进程资源 ->>>>> 经常会用
0: 回收当前进程所在的进程组的子进程资源
>0: 某个进程的进程ID ->>>>>> 经常用的
- status: 传出参数, 返回进程退出时候的一些状态信息, 一般时候用不着, NULL
- options: 控制当前函数阻塞或者非阻塞
- WNOHANG: 非阻塞
- 0: 阻塞的
返回值:
失败: -1
成功: 回收的子进程的进程ID
没有做任何操作 -> 非阻塞情况下:
- 子进程还在运行, 返回0
// wait / waitpid 的第二个参数
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
参数: status
- 判断进程是不是正常退出, 并得到退出时候的状态码
main函数中的return或者exit函数
- WIFEXITED(status): 是不是正常退出
得到退出时候的状态码:
- WEXITSTATUS(status)
- 判断进程是不是被信号杀死了, 并且得到信息是被哪个信号杀死了
- 是不是被信号杀死了:
- WIFSIGNALED(status)
- 得到信息是被哪个信号杀死了:
- WTERMSIG(status)
4.5 getpgrp/getpgid/setpgid
// 得到当前进程所属的进程组的组ID
pid_t getpgrp(void);
// 得到指定进程所属的组的组ID
// 返回值: pid进程所属的组的组ID
pid_t getpgid(pid_t pid);
// 将某个进程转移到另一个进程中 / 创建新的进程组
int setpgid(pid_t pid, pid_t pgid);
参数:
- pid: 进程ID, 要操作的进程
- pgid: 进程组ID
- pgid这ID是存在的, pid对应的进程被移动到这个进程组中
- pid != pgid
- pgid这ID是不存在的, 这个进程组就被创建了
- pid == pgid
4.6 会话getsid/setsid
// 获取进程所属的会话ID
#include <unistd.h>
pid_t getsid(pid_t pid);
// 进程调用这个函数就可以变成会话
pid_t setsid(void);
4.7 守护进程
创建守护进程的步骤
/*
1. 创建一个新的子进程 - fork();
2. 杀死父进程 -> exit(0);
3. 将新的子进程提升为会话 -> setsid();
4. 修改进程的工作目录 (可选)
- 如何看进程的工作目录?
- 在哪个目录下执行进程, 工作目录就是哪个进程
- 为什么要修改呢?
- 如果进的工作目录在一个U盘里边, U盘被拔掉了 ==> 工作目录就没有了 ==> 进程不能正常工作
- 如何修改?
- int chdir(const char *path);
5. 修改掩码 (可选)
- 什么是掩码?
- 修改创建的新文件的权限, 新文件的权限 = 0777 - 掩码的值
- 修改的目的?
- 在进程中修改掩码, 创建新文件, 这个文件的权限会受掩码的影响
- 设置掩码为 0111
- 创建新文件: 肯定没有执行权限
- 如何设置掩码?
mode_t umask(mode_t mask);
6. 关闭/重定向文件描述符
- 指定的是那个文件描述符?
- 每个进程文件描述符表中的0, 1, 2默认是打开的, 对应当前终端
- 虽然进程脱离了终端, 但是还是绑定这
- 如何关闭:
close(0);
close(1);
close(2);
- 重定向:
- 在linux的/dev目录中有一个空文件: /dev/null (设备文件)
int fd = open("/dev/null", O_RDWR);
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
7. 添加子进程的业务逻辑 (在后台不停的做某件事)
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
// 1. 创建子进程
pid_t pid = fork();
if(pid > 0)
{
// 父进程
exit(0);
}
// 2. 子进程 -> 会长
setsid();
while(1)
{
sleep(100);
}
return 0;
}
5.linux进程通信
5.1 匿名管道通信pipe
#include <unistd.h>
// 创建匿名管道
// pipefd是传出参数, 对应的是管道的两端
// pipefd[0] -> 读端, pipefd[1] -> 写端
int pipe(int pipefd[2]);
返回值:
成功: 0
失败: -1
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
// cmd 取值: F_GETFL / F_SETFL
// 非阻塞的属性: O_NONBLOCK
// 获取当前fd对应的flags属性
// 设置读端非阻塞
int flag = fcntl(fd[0], F_GETFL);
// 将 O_NONBLOCK 设置 到flag属性中
flag = flag | O_NONBLOCK; // flag |= O_NONBLOCK;
// 将flag属性设置给 fd[0]
fcntl(fd[0], F_SETFL, flag);
5.2有名管道通信mkfifo
// 通过函数
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
- pathname: 要创建的管道的名字
- mode: 创建的管道文件的权限, 指定一个八进制的数
文件的最终权限: (mode & ~umask)
5.3内存映射mmap
#include <sys/mman.h>
// 创建内存映射区
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数:
- addr: 在动态库加载区的什么位置创建一块映射内存, NULL -> 委托内核指定
- length: 创建的内存映射区大小, 指定我们想要的大小即可
- 实际分配的大小是4k整数倍(>0)
- 这个参数长度不能为0
- prot: 进程对内存映射区的操作权限, 读权限是必须要指定的
- PROT_READ: 读权限
- PROT_WRITE: 写权限
- PROT_READ | PROT_WRITE: 读写权限
- flags: 实现进程间通信,必须指定 MAP_SHARED
- MAP_SHARED: 数据共享, 当映射区数据被修改, 数据才会同步到磁盘文件
- MAP_PRIVATE: 数私有, 当映射区数据被修改, 数据不会同步到磁盘文件
- fd: 磁盘文件对应的文件描述符
- 内存映射区和这个fd对应的磁盘文件进行映射
- 得到fd需要open(), 在open的时候需要指定对文件的操作权限
- 这个权限和prot参数的权限对应即可
- offset: 在映射的时候对磁盘文件进行偏移
- 这个参数必须是4k的整数倍
- 不偏移指定为0
返回值:
- 成功: 内存映射区的起始地址
- 失败: MAP_FAILED (that is, (void *) -1) )
// 释放内存映射区
int munmap(void *addr, size_t length);
参数:
- addr: 内存映射区的首地址
- length: 和 mmap的 第二个参数值相同即可
返回值:
成功: 0
失败: -1
6.linux信号函数
6.1 kill/raise/abort函数
// 发信号给对应的进程
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数:
- pid: 将信号发送给那个进程, 进程的 进程ID
- >0: pid对应是指定的进程 ====> 用的比较多
- =0: 发送给当前进程所在进程组中的所有进程
- -1: 发送给所有的进程(有权的), init进程是绝对不允许的
- <-1: pid取绝对值, 这个数表示一个进程组, 这个信号发送给这个进程组中所有进程
- sig: 要发送的信号
返回值:
成功: 0
失败: -1
#include <signal.h>
// 发送信号给当前进程
// 参数就是要发送的信号
int raise(int sig);
// 使用kill实现raise的功能
kill(getpid(), sig);
#include <stdlib.h>
// 给当前进程发送SIGABRT信号
void abort(void);
6.2 alarm/setitimer定时器函数
#include <unistd.h>
// 设置倒计时时长, 当时间倒计时 == 0的时候, 该函数会给进程发信号
// SIGALRM, 进程收到这个信号就会终止
unsigned int alarm(unsigned int seconds);
参数: seconds: 计时时长
返回值: 倒计时还剩多长时间
// 倒计时的时候终止计时
alarm(5);
.....
alarm(0);
// 算一下电脑1s可以数多少个数?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
int num = 0;
alarm(1);
while(1)
{
printf("%d\n", num++);
}
return 0;
}
# 重定向到文件
$ time ./a.out > a.txt
Alarm clock
real 0m1.001s `总时长`
user 0m0.184s `用户区代码执行的时间`
sys 0m0.740s `内核区代码执行的时间`
real = user + sys + 消耗的时长(用户区到内核区切换)
#include <sys/time.h>
// 定时周期对应的结构体
/*
luffy晚上12点睡觉, 明天早晨8点起床, 因此要定个闹钟, 但是luffy'赖床, 需要每隔5分钟再响一次
- 倒计时8个小时: it_value
- 每隔5分钟再响一次: it_interval
*/
struct itimerval {
struct timeval it_interval; /* 从第二次开始发信号的时间周期 */
struct timeval it_value; /* 定时器函数第一次发信号的倒计时时长 */
};
// 这个结构体表示一个时间段
// tv_sec + tv_usec
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
// 设置一个定时器, 而且能够实现周期性定时
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
参数:
- which: 使用什么样的计时法
- ITIMER_REAL: 自然计时法, 真正使用的时长, 使用的的 SIGALRM 信号 ==> 常用
- ITIMER_VIRTUAL: 用户区使用的总时长, 使用 SIGVTALRM 信号
- ITIMER_PROF: 使用内核使用的总时长计时, 使用信号 ITIMER_VIRTUAL
- new_value: 指定的定时规则
- old_value: 获取上一次定时的一些信息, 如果不需要, 指定为NULL
6.3 signal/sigaction信号捕捉
#include <signal.h>
typedef void (*sighandler_t)(int);
- int参数: 代表捕捉到的信号
// 注册捕捉哪个信号
// 在信号产生之前调用该函数
sighandler_t signal(int signum, sighandler_t handler);
参数:
- signum: 指定要捕捉的信号
- handler: 回调函数, 信号被捕捉到之后的处理动作
// 什么是回调?
- 1. 作为程序猿只需要实现这个函数, 不需要进行调用
- 2. 需要在程序的适当位置对函数进行注册
- 注册过程一般就是一个函数调用, 这个注册函数有一个参数是函数指针
- 将函数被调用的时机告诉框架/操作系统/内核
- 3. 框架/操作系统/内核 会检测时机是不是成熟了
- 成熟了 框架/操作系统/内核 会调用我们指定的函数
#include <signal.h>
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
- sa_handler: 函数指针, 指向信号捕捉函数, 一般时候用这个
- sa_flags:
== 0: 使用函数指针 -> sa_handler ==> 通常指定为0
== SA_SIGINFO: 使用是函数指针 -> sa_sigaction ===> 不常用
- sa_restorer: 这是一个被废弃的变量
- sa_mask:
- 当信号的回调函数被执行, 在该函数执行期间可以设置临时屏蔽某些信号
- 我们要屏蔽哪些信号只需要将这些信号设置到该集合中
- 如果不想阻塞任何信号, 需要将该变量初始化为空
- int sigemptyset(sigset_t *set);
- 在当前捕捉到的的这个信号的回调函数执行期间, 默认屏蔽当前信号
- 比如: 捕捉了SIGINT, SIGINT的处理函数被执行
- 这个处理函数执行期间, 又产生了一个SIGINT信号, 这个信号被临时阻塞
- 当处理函数处理完毕, SIGINT信号的处理函数会被再次调用
// 注册信号捕捉
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数:
- signum: 要捕捉的信号
- act: 传入参数, 设置当前捕捉的信号对应的处理动作
- oldact: 传出参数, 获得上一次设置的信号捕捉的处理动作
6.4 信号集相关函数
#include <signal.h>
// 将sigset_t中所有的标志位初始化为0
int sigemptyset(sigset_t *set);
// 将sigset_t中所有的标志位初始化为1
int sigfillset(sigset_t *set);
// 将信号 signum 添加到 set 信号集中, 也就是这个信号对应的标志位被设置为1
int sigaddset(sigset_t *set, int signum);
// 将信号 signum 从 set 信号集中删除, 也就是这个信号对应的标志位被设置为0
int sigdelset(sigset_t *set, int signum);
// 判断 signum 信号有没有被设置到信号集 set中
// 返回值: 如果信号在信号集中返回 1, 没有在返回0
int sigismember(const sigset_t *set, int signum);
// 将用户区的初始化的信号集设置到内核中, 设置或者解除阻塞
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数:
- how:
- SIG_BLOCK: 将参数 set 信号集中阻塞的信号追加到内核的阻塞信号集
- SIG_UNBLOCK: 将参数 set 信号集中的信号从内核的阻塞信号集中删除
- SIG_SETMASK: 使用参数 set 信号集中的数据覆盖内核的阻塞信号集
- set: 用户初始化的信号集, 传入参数
- oldset: 在做这次设置之前是设置, 得到了设置之前的内核中的阻塞信号集
// 查看当前内核中的未决信号集
#include <signal.h>
int sigpending(sigset_t *set);
参数:
- set: 函数调用成功, 未决信号集的状态被写入到该变量中
7.linux线程函数
7.1pthread_create创建线程
#include <pthread.h>
// pthread_t: 线程ID的类型, 无符号长整形
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数:
- thread: 传出, 通过这个参数得到了创建出的新的线程的ID
- attr: 线程属性, 一般使用默认值, 该参数指定为NULL
- start_routine: 函数指针, 指向的函数就是子线程的处理动作
- arg: 对应第三参数函数指针的参数, 回调函数被调用, 这个参数作为实参传递到start_routine中
返回值: 函数的状态
- 0: 成功了
- 失败了: 返回错误号 >0
// Compile and link with -pthread.
gcc *.c -l pthread
// 因为返回的是错误号, 因此不能使用perror直接打印错误信息 == 不能使用perror
// 使用strerror
#include <string.h>
// 参数是得到的错误号, 返回值是错误号对应的错误信息
char *strerror(int errnum);
7.2 pthread_self 获取线程ID
#include <pthread.h>
// 获取当前线程的线程ID
pthread_t pthread_self(void);
7.3 pthread_exit 线程退出
/*
有多个线程:
- 1个主线程
- 若干个子线程
线程退出的时候:
- 如果其中子线程先退出, 对主线程和其他子线程没有影响
- 主线程先退出, 运行的子线程强制被退出
- 这些线程共用虚拟地址空间, 主线程退出的时候会销毁虚拟地址空间
*/
#include <pthread.h>
// 让某一个线程退出, 退出的时候对其他线程没有任何影响
void pthread_exit(void *retval);
- 参数:
- retval: 线程退出的时候可以携带以下信息, 在这个指针指向的内存中
- 在线程回收的时候, 可以将这些信息读出来
7.4 pthread_join 回收子线程
// 在子线程退出的时候, 主线程回收子线程资源
#include <pthread.h>
// 这是一个阻塞函数, 当有子线程还活着的时候, 子线程死了解除阻塞, 回收其资源
void* pt;
pthread_join(tid, &pt);
int pthread_join(pthread_t thread, void **retval);
参数:
- thread: 要回收的线程的线程ID
- retval: 指向一级指针的地址, 传出参数, 里边的数据
- pthread_exit(void *retval), 参数指针指向的数据
- 如果对这个值不感兴趣, 直接写NULL
返回值: 函数的状态
- 0: 成功了
- 失败了: 返回错误号 >0
7.5 pthread_detach线程分离
// 在子线程被创建出之后, 设置线程分离, 主线程就不需要负责子线程的资源回收
#include <pthread.h>
// 参数: thread子线程的线程ID
int pthread_detach(pthread_t thread);
7.6 pthread_cancel 杀死线程
// 在合适的时机杀死子线程
// 在该函数被调用之后, 子线程不会马上就终止
// 合适的时机 == 在子线程的处理函数中要有一个系统调用, 并且执行到该系统调用的位置的时候
#include <pthread.h>
// 参数: 子线程的线程ID
int pthread_cancel(pthread_t thread);
// 子线程的处理函数
void* callback(void* arg)
{
int a;
.....; ======> 子线程执行到该位置, 主线程调用了 pthread_cancel
.....;
.....;
int fd = open();
}
7.7 pthread_equal 线程比较
比较线程ID是否相同
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
返回值:
相同: !=0
不同: =0
7.8 线程属性函数
// 在创建新线程的时候, 给其设置属性
// 一般时候使用默认属性, 因此该属性值指定为NULL
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
// 线程的属性类型: pthread_attr_t
// 属性的使用:
- 创建属性变量: pthread_attr_t attr;
- 对属性初始化: pthread_attr_init
- 设置具体的属性(设置线程分离): pthread_attr_setdetachstate
- 在创建新线程的时候使用这个属性: pthread_create
- 销毁属性: pthread_attr_destroy
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
// 设置线程属性(线程分离)
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
参数:
- attr: 属性变量
- detachstate: 分离状态
- PTHREAD_CREATE_DETACHED: 设置线程分离
- PTHREAD_CREATE_JOINABLE: 设置线程不分离
7.9 互斥锁操作函数
// 互斥锁的类型: pthread_mutex_t
// 初始化互斥锁
// 关键字: restrict, 修饰指针, mutex指针指向的内存只能通过mutex指针变量访问
// pthread_mutex_t *ptr = mutex; // error
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
参数:
- mutex: 初始化互斥锁变量, 并传出
- attr: 互斥锁属性, 一般使用默认的, 指定为NULL
// 销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 互斥锁加锁, 这是阻塞函数
// 阻塞的条件:
// - 这个mutex锁已经被锁上了, 其他线程调用这个函数就阻塞
// - 这个mutex锁是打开的, 线程调用这个函数可以加锁成功, 不阻塞
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 尝试加锁, 不是阻塞函数
// - 这个mutex锁已经被锁上了, 其他线程调用这个函数就不阻塞, 函数直接返回, 加锁失败
// - 这个mutex锁是打开的, 线程调用这个函数可以加锁成功, 不阻塞, 加锁成功
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
7.10 读写锁操作函数
#include <pthread.h>
// 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
参数:
- rwlock: 要初始化的读写锁
- attr:读写锁的属性, 默认属性指定=NULL
// 释放读写锁资源
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
// 加读锁, 这是一个阻塞函数
// 什么情况下阻塞?
// - 当有线程通过这把锁锁定了写, 这时候锁定读就阻塞
// - 有线程通过这把锁锁定了读, 再次加读锁 -> 不阻塞
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 尝试加读锁, 如果被写锁定了, 尝试加读锁失败 -> 不阻塞, 直接返回了
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
// 加写锁, 这是一个阻塞函数
// 什么情况下阻塞?
// - 当有线程通过这把锁锁定了写, 这时候锁定写就阻塞
// - 有线程通过这把锁锁定了读, 再次加写锁 ->阻塞
// - 如果这把锁是打开的, 加锁成功 -> 不阻塞
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
// 尝试加写锁, 加锁失败直接返回 -> 不阻塞
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
// 将读写锁解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
以上所有的返回值:
成功: 0
失败: != 0, 错误号
通过函数: char* strerror(errno);
7.11 条件变量
#include <pthread.h>
// 初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
// 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
// 阻塞线程
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
参数:
- cond: 初始化的条件变量
- mutex: 互斥锁, 用户数据同步
// 表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
// 阻塞线程, 但是不会一直阻塞, 阻塞一定时间之后就自动解除阻塞了
// 阻塞时长 == 第三个参数设置的时长
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
参数:
- cond: 初始化的条件变量
- mutex: 互斥锁, 用户数据同步
- abstime: 阻塞的时间
- 阻塞100s之后解除阻塞
- struct timespec tsp;
tsp.tv_sec = time(NULL) + 100;
// 通知阻塞在参数cond上的线程解除阻塞
// 至少唤醒一个阻塞的线程
int pthread_cond_signal(pthread_cond_t *cond);
// 通知阻塞在参数cond上的线程解除阻塞
// 唤醒全部阻塞的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
7.12 信号量函数
#include <semaphore.h>
// 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
- sem: 被初始化的信号量变量, 这个参数看做一个整形数即可
- pshared:
- 0: 线程
- 1: 进程
- value: 给第一个参数sem赋值, 通过这个变量中的值控制线程是阻塞还是不阻塞
// 销毁信号量
int sem_destroy(sem_t *sem);
// 有可能阻塞线程
// 这个函数被调用一次 sem 中的整形变量 -1
// 当sem 中的整形变量 == 0, 线程被阻塞
int sem_wait(sem_t *sem);
// 这个函数被调用一次 sem 中的整形变量 -1
// 当sem 中的整形变量 == 0, 线程不阻塞
int sem_trywait(sem_t *sem);
// 表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
// 这个函数被调用一次 sem 中的整形变量 -1
// 当sem 中的整形变量 == 0, 线程阻塞, 阻塞 abs_timeout 时长, 时间到达, 解除阻塞
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
// 这个函数被调用一次 sem 中的整形变量 +1
int sem_post(sem_t *sem);
// 得到sem变量中整形数的值, 通过 sval 参数传出
int sem_getvalue(sem_t *sem, int *sval);
8.SOCKET通信函数
8.1 大小端转换函数
#include <arpa/inet.h>
// h -> host -> 主机字节序
// to -> 转换
// n -> net -> 网络字节序
// s -> short -> 16位整形数
// l -> long -> 32位整形数
// 参数: 要转换的数据
// 返回值: 转换完成之后得到的结果
// 一般端口转换的时候使用这个函数
// 主机字节序 -> 网络字节序
uint16_t htons(uint16_t hostshort);
// 网络字节序 -> 主机字节序
uint16_t ntohs(uint16_t netshort)
// 一般对整形IP进行转换
// 主机字节序 -> 网络字节序
uint32_t htonl(uint32_t hostlong);
// 网络字节序 -> 主机字节序
uint32_t ntohl(uint32_t netlong);
#include <arpa/inet.h>
// p -> 主机字节序的点分十进制IP地址(字符串): 192.168.1.100
// n -> 网络字节序的整形IP地址
// 将本地IP字符串 -> 整形大端IP地址
int inet_pton(int af, const char *src, void *dst);
参数:
- af: 地址族协议, 使用的ipv4还是ipv6的ip地址
- ipv4: AF_INET
- ipv6: AF_INET6
- src: 要被转换的点分十进制的IP地址: 192.168.1.100这种格式
- dst: 转换得到的大端IP存储到这个指针指向的内存中
返回值:
转换成功: 0
失败: -1
// 整形大端IP地址 -> 将本地IP字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
参数:
- af: 地址族协议, 使用的ipv4还是ipv6的ip地址
- ipv4: AF_INET
- ipv6: AF_INET6
- src: 这个指针指向的内存中存储了大端的整形IP地址
- dst: 存储得到的IP地址字符串
- size: dst参数指向的内存的总大小(容量)
返回值:
成功: 返回第三个参数dst的地址
失败: NULL
// -> 将本地IP字符串
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
char *inet_ntoa(struct in_addr in);
8.2 套接字API
#include <arpa/inet.h>
// 创建套接字
int socket(int domain, int type, int protocol);
参数:
- domain: 指定地址族协议
- AF_UNIX, AF_LOCAL: 用于本地进程间通信(本地套接字)
- AF_INET: 使用IPv4
- AF_INET6: 使用IPv6
- type: 使用什么传输协议
- SOCK_STREAM: 流式传输协议
- SOCK_DGRAM: 报式传输协议
- protocol: 指定具体的协议, 这个参数一般指定为0
- 使用流式协议中的tcp协议
- 使用报式协议中的udp协议
返回值:
成功: 返回用于socket的文件描述符
失败: -1
// 将监听的套接字和本地的IP和端口绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
- sockfd: 用于监听的套接字
- addr: 在这个变量中初始化要绑定的IP和端口信息
- addrlen: 第二个参数addr对应的内存大小
返回值:
成功: 0
失败: -1
// 设置监听->给监听的套接字
int listen(int sockfd, int backlog);
参数:
- sockfd: 绑定成功的套接字
- backlog: 可以最大同时监听多少个客户端连接
- 这个值最大是128, 如果参数值超过128, 还是按128处理
返回值:
成功: 0
失败: -1
// 等待并接受客户端连接, 阻塞函数
// 开始监听之后, 没有客户端连接服务器, 这时候程序就阻塞在了accept函数上
// 当检测到了客户端连接, 这个函数解除阻塞, 和客户端建立连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
- sockfd: 用户监听的套接字
- addr: 传出参数, 得到连接的客户端的IP和端口信息
- addrlen: 传入传出参数, 记录了参数addr指针对应的内存大小
返回值:
成功: 通信的文件描述符
失败: -1
// 接收数据
ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
- 前三个参数和read一样
- flags: 套接字是一些属性, 默认使用0
// 发送数据
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
- 前三个参数和write一样
- flags: 套接字是一些属性, 默认使用0
// 连接服务器, 这个函数也是阻塞函数
// 连接完成之后, 解除阻塞
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
- sockfd: 客户端用于通信的套接字
- addr: 初始化服务器的IP和端口, 通过这个地址连接服务器
- addrlen: 参数 addr 指针指向的内存大小, sizeof(addr)
返回值:
成功: 0
失败: -1
#include <sys/socket.h>
// 设置套接字半关闭
int shutdown(int sockfd, int how);
参数:
sockfd: 要操作 的文件描述符
how:
- SHUT_RD: 关闭读
- SHUT_WR: 关闭写
- SHUT_RDWR: 关闭读写
//目标结构体清空,置为0
#include <strings.h>
void bzero(void *s, size_t n);
8.3 端口复用函数
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数:
- sockfd: 要被设置属性的文件描述符
- level: SOL_SOCKET
- optname: 设置端口复用
- SO_REUSEPORT
- SO_REUSEADDR
- optval: 要设置的属性的值
- 根据查手册 设置端口复用 该指针指向的一个int数据
- 值: 1 -> 设置端口复用
- 值: 0 -> 不设置端口复用, 默认端口不能复用
- optlen: 参数 optval 指针对应的内存大小
返回值:
设置成功: 0
失败失败: -1