头文件包含路径
linux 操作系统分为两大空间:用户空间和内核空间
这样划分,是为了保护内核的核心组件,不被轻易访问和修改
系统调用:安全的访问内核空间
其核心是:函数API(API:用户编程接口)
所谓系统调用是指操作系统提供给用户的组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的的服务
学习linux应用编程,就是学习使用API【API需要阅读手册——参考man手册、linux c手册】
硬件中断
文件编程
文件IO:一切皆为文件(内核中虚拟文件系统(VFS));用文件系统来管理
文件分为四大类:-普通文件、d目录文件、l链接文件和c/b设备文件(三小类:p管道文件、s套接字和f堆栈文件)
都可以用C库函数和API来调用
把一切抽象成文件,用统一的方式管理设备和文件,节省开发资源;用过文件来操作硬件
文件描述符:
知道一个文件的id(文件指针),操作该id就是操作该文件;
1.宏定义 :
2.数字【0:八进制;3位8进制来表示文件的权限,r用4标识,w用2标识,x用1标识】
已经创建的文件,不能通过再creat来修改权限;
如何获取错误信息?
方法一:
errno:系统全局变量(所有应用都可以访问),用来保存错误编号(整数)
使用该变量时,要有
方法二:perror --------------------常用-----------------------
方法三:
O_WRONLY:写 O_RDONLY:读 O_RWRD:可读可写
O_CREAT:不存在就创建;【O_EXCEL: O_CREAT存在时,打开已有的文件就报错】
O_APPEND:文件读写位置移到末尾; O_TRUNC:文件长度为零(清空)
O_NONBLOCK:非阻塞的方式打开;
假设有两个file descript指向同一个文件,可以close(fd1/fd2)都可以,也可以各close一次
lseek的返回值是,lseek操作后,文件读写位置距离文件首的距离(字节数),以此可以测量文件大小:
lseek(fd3, 0, SEEK_END); // 返回值是文件大小(字节数)
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
// int fd1;
// fd1 = creat(argv[1], S_IRWXU | S_IRWXG | S_IRWXO);
#if 0
// 第一种方法
if (errno == EISDIR)
{
printf("is a directory\n");
}
// 第二种方法:
perror("error is:");
// 第三种方法:
printf("%s\n", strerror(errno));
#endif
// printf("%d\n",fd1);
// int fd2=open(argv[1],O_RDWR);
// printf("%d\n",fd2);
// printf("%d\n",close(fd1));
// printf("%d\n",close(fd2));
int fd3 = open(argv[1], O_RDWR | O_CREAT, S_IRWXG | S_IRWXO | S_IRWXU);
char buffer[1024];
gets(buffer);
int w_num;
printf("%d\n", w_num = write(fd3, buffer, strlen(buffer)));
if (w_num == -1)
{
printf("write error\n");
exit(-1);
}
//lseek(fd3, 0, SEEK_SET);
//lseek(fd3, 0, SEEK_END); // 返回值是文件大小(字节数)
lseek(fd3, -w_num, SEEK_CUR);//将文件读写位置移到开头
int r_num;
r_num = read(fd3, buffer, strlen(buffer));
buffer[r_num] = '\0';
printf("read num:%d; read content:%s\n", r_num, buffer);
return 0;
}
struct stat file_a1;
//1.
stat("a.txt",&file_a1);
printf("stat.size=%ld\n",file_a1.st_size);//输出a.txt的大小;
//2.
struct stat file_a2;
fstat(fd3,&file_a2);
printf("fstat.size=%ld\n",file_a2.st_size);//输出a.txt的大小;
//3.
struct stat file_a3;
lstat("a.txt",&file_a3);
printf("lstat.size=%ld\n",file_a3.st_size);//输出a.txt的大小;
以上函数实现:读一行,写一行的read_line 函数,并以此实现文件拷贝(需要考虑到换行符)
#include
#include
#include
#include
#include
#include
#include
#include
// 按行读取
int read_line(int fd1,int fd2)
{
char src[200] = {0};
char des[200] = {0};
int index_s = 0;
int index_d = 0;
int r_num = 0;
int count = 0;
while ((r_num = read(fd1, src, sizeof(src))) > 0)
{
for (int i = 0; i < r_num; ++i)
{
des[index_d++] = src[index_s++];
if (des[index_d - 1] == '\n')
{
index_d = 0;
write(fd2,des,strlen(des));
memset(des, 0, sizeof(des));
}
}
index_s = 0;
}
if (strlen(des) != 0)
{
write(fd2,des,strlen(des));
}
}
int main(int argc, char **argv)
{
if (argc != 3)
{
printf("input error!\n");
exit(-1);
}
int fd1 = open(argv[1], O_RDWR);
if (fd1 == -1)
{
perror("open1 :");
}
int fd2 = open(argv[2], O_RDWR|O_CREAT,0777);
if (fd2 == -1)
{
perror("open2 :");
}
read_line(fd1,fd2);
return 0;
}
作业:统计文件中的单词数:
#include
#include
#include
#include
#include
#include
#include
#include
// 统计字符串数组内个数:
int count_words(char *src)
{
int index = 0;
int count = 0;
int flag = 0;
while (index <= strlen(src))
{
if (('A' <= src[index] && src[index] <= 'Z') || ('a' <= src[index] && src[index] <= 'z'))
{
flag = 1;
}
else if (src[index] == ' ')
{
if (flag == 1)
{
flag = 0;
count++;
}
}
index++;
}
if (flag == 1)
{
count++;
}
return count;
}
// 按行读取
int read_line(int fd1)
{
char src[200] = {0};
char des[200] = {0};
int index_s = 0;
int index_d = 0;
int r_num = 0;
int count = 0;
while ((r_num = read(fd1, src, sizeof(src))) > 0)
{
for (int i = 0; i < r_num; ++i)
{
des[index_d++] = src[index_s++];
if (des[index_d - 1] == '\n')
{
index_d = 0;
count = count + count_words(des);
memset(des, 0, sizeof(des));
}
}
index_s = 0;
}
if (strlen(des) != 0)
{
count = count + count_words(des);
}
printf("words number:%d\n", count);
}
int main(int argc, char **argv)
{
if (argc != 2)
{
printf("input error!\n");
exit(-1);
}
int fd = open(argv[1], O_RDWR);
if (fd == -1)
{
perror("open :");
}
read_line(fd);
return 0;
}
一个程序一旦运行,则打开三个文件【/dev/下的stdin-0 stdout-1 stderr-2】
文件指针fp:stdin ,stdout, stderr
stdout是行缓冲的,而stderr是无缓冲
这意味着当你向stdout写入数据时,如果没有遇到换行符('\n')或者没有显式调用fflush函数,数据将会暂存在输出缓冲区中。
而当你向stderr写入数据时,它会立即被刷新到输出设备上,无需等待。这样做是为了确保重要的错误消息能够尽快显示在终端上。
有缓冲库函数(标准IO) —————— 无缓冲系统调用(初级IO)
对普通文件的操作,使用库函数fread,更方便;
对于特殊文件,尤其是对于设备文件,库函数没有封装相应的API,则只能选择系统调用
【文件映射 系统编程 通信 】
库函数内部封装的还是系统调用,系统调用大量数据存入缓冲区中,减少访问内核-硬件的次数,减少中断次数
库函数可以跨平台使用,C语言内定义了宏,可以确认当前Linux/win系统
库函数 :fgets fputs ...... ......
类型:全缓冲 行缓冲 无缓冲
#include
#include
int main(int argc, char **argv)
{
// setbuf(stdout,NULL); //修改为无缓冲
// setvbuf(stdout, NULL, _IONBF, 0); // 改为无缓冲
char *ptr = (char *)malloc(200); // 设置全缓冲,缓冲区大小为200字节
setvbuf(stdout, ptr, _IOFBF, 200);
printf("hello wolrd");
while (1);
return 0;
}
fread fwrite是有缓冲区的! 全缓冲or行缓冲?
char *src = "hello world";
FILE *fp = fopen(argv[1], "a+");
setvbuf(fp, NULL, _IONBF, 0);//注释此行,则无法写入,说明fwrite有缓冲区
fwrite(src, 1, strlen(src), fp);
while (1);
return 0;
提升读写效率的一种方法就是适当的选择大的缓冲区,以减少IO操作,或者:
在标准I/O库中,`fwrite` 函数通常会将数据存储在缓冲区中,而不会立即将其写入文件。这是为了提高性能,在一次 `fwrite` 调用中可以一次性写入多个数据。当缓冲区满了、文件关闭或者显式地调用了 `fflush` 函数时,缓冲区的数据才会被写入文件。
`fflush` 函数被用来刷新(清空)缓冲区,将缓冲中的数据立即写入到文件中。
参数 `stream` 是要刷新的文件流指针。当 `fflush` 函数被调用时,它会将缓冲区中的内容写入文件,并清空缓冲区。这样可以确保数据被立即写入文件,而不需要等待缓冲区满或者文件关闭。
在示例代码中,将文件流 `fp` 设置为行缓冲模式后,`fwrite` 函数将字符串 `"hello world"` 写入缓冲区。然后,使用 `fflush(fp)` 显式地刷新缓冲区,将数据写入文件。这样可以确保数据被写入文件,而不需要等待缓冲区满或者程序结束。
请注意,`fflush` 函数调用时会导致I/O操作,因此在频繁执行 `fflush` 的情况下,可能会影响程序的性能。因此,最好使用缓冲机制并在适当的时机使用 `fflush` 来确保数据写入文件。
int ftruncate(int fd, off_t length);
导致由fd引用的常规文件被截断为精确长度字节的大小。
如果先前的文件大于此大小,则会丢失额外的数据。如果先前的文件较短,则对其进行扩展,并且扩展部分读取为空字节('\0')。
使用ftruncate(),文件必须打开才能写入;使用truncate(),文件必须是可写的。
使用以上三个函数,以文件映射方式实现文件拷贝:
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
// 检查命令行参数
if (argc != 3)
{
printf("input error!");
exit(-1);
}
// 打开要读取的文件
int fd1 = open(argv[1], O_RDWR);
if (fd1 == -1)
{
perror("fd1 error:");
exit(-1);
}
// 打开要写入的文件
int fd2 = open(argv[2], O_RDWR | O_CREAT, 0777);
if (fd2 == -1)
{
perror("fd2 error :");
exit(-1);
}
// 确认要读取文件的大小,通过fstat
struct stat file1;
fstat(fd1, &file1);
// 映射要读取的文件,以及判断映射是否成功
char *src = mmap(NULL, file1.st_size, PROT_EXEC | PROT_WRITE | PROT_READ, MAP_SHARED, fd1, 0);
if (src == MAP_FAILED)
{
perror("map1 error :");
exit(-1);
}
// 将即将打开(新建)的文件,扩容,防止后续写入失败
ftruncate(fd2, file1.st_size);
// 映射要写入的文件,以及判断映射是否成功
char *des = mmap(NULL, file1.st_size, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED, fd2, 0);
if (des == MAP_FAILED)
{
perror("map2 error :");
exit(-1);
}
// 直接对两个映射的地址执行操作
strncpy(des, src, file1.st_size);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
//定义初始化一个fd
int fd1=open("a.txt",O_RDWR);
if(fd1==-1)
{
perror("fd1:");
exit(-1);
}
//转换为fp
FILE*fp1=fdopen(fd1,"a+");
if(fp1==NULL)
{
perror("fdope error:");
exit(-1);
}
fwrite("fdopen is ok,hha",16,1,fp1);
//转换为fd
int fd2=fileno(fp1);
if(fd2<=2)
{
perror("fileno error");
exit(-1);
}
//移动读写位置到文件首
int lseek_num=lseek(fd2,0,SEEK_SET);
if(lseek_num==-1)
{
perror("lseek error");
exit(-1);
}
//读取尝试
char buffer[1024];
memset(buffer,0,sizeof(buffer));
if(read(fd2,buffer,25)==-1)
{
perror("read error:");
exit(-1);
}
buffer[25]='\0';
printf("%s\n",buffer);
return 0;
}
dup和dup2
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd1 = open(argv[1], O_RDWR);
if (fd1 == -1)
{
perror("fd1 error");
exit(-1);
}
int fd2 = open(argv[2], O_RDWR | O_CREAT, S_IRWXG | S_IRWXU | S_IRWXO);
if (fd2 == -1)
{
perror("fd2 error");
exit(-1);
}
// 关闭 0:stdin文件,然后将0重定向为fp1所指文件
close(0);
dup2(fd1, 0);
char buffer[1024] = {0};
// 获取重定向后的fp1指向的文件大小,再读出来到buffer
struct stat file1;
fstat(0, &file1);
read(0, buffer, file1.st_size);
// scanf("%s", buffer); //会不能读取空格和回车
// 关闭1:stdout文件,然后将1重定向为fp2所指文件
close(1);
dup2(fd2, 1);
// 将buffer内的内容输出到fp2所指向的文件
printf("%s\n", buffer);
close(fd1);
close(fd2);
return 0;
}
应用于:网络读取重定向
int flag = fcntl(fd1, F_GETFD); // 获取fd1的flags
flag = flag | O_APPEND; // flag加上O_APPEND
fcntl(fd1, F_SETFL, flag); // 重新设置flags
fcntl如果不显式的先close(1);那么就无法重定向!!!
普通文件无阻塞——读到数据就成功返回,没读到数据返回0,总之不会堵塞,所以一般认为非阻塞比较好,效率更高
大多数套接字 、设备文件会出现阻塞读取——没有数据就会阻塞,节省了CPU资源,但是程序运行效率低
如何设置成非阻塞呢?
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
// 标准输入stdin也是阻塞读取————【没有读到数据就会一直停着】
char buffer[1024];
int flags = fcntl(0, F_GETFL);
flags = flags | O_NONBLOCK; // 加上非阻塞的打开权限
fcntl(1, F_SETFL, flags);
read(0, buffer, sizeof(buffer) - 1);
buffer[1023] = '\0';
printf("buffer is=%s\n", buffer);
// 读取鼠标坐标(阻塞)
int mouse = open(argv[1], O_RDWR); //
if (mouse == -1)
{
perror("mouse error");
exit(-1);
}
int mou_loc;
read(mouse, &mou_loc, sizeof(mou_loc));
printf("%d\n", mou_loc);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd = open("/dev/input/mouse0", O_RDWR | O_CREAT);
if (fd == -1)
{
perror("fd open error");
exit(-1);
}
fd_set set1;
FD_SET(0, &set1);
FD_SET(fd, &set1);
fd_set allset;
struct timeval time1;
time1.tv_sec = 3;
time1.tv_usec = 0;
// while (1)
// {
allset = set1; //如果是循环判断,则需要此行
int ret = select(fd + 1, &allset, NULL, NULL, &time1);
//返回值: -1:出错 0:没有检测到/超时 n:变化的文件描述符个数
if (FD_ISSET(0, &allset) > 0)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
read(0, buffer, sizeof(buffer) - 1);
printf("%s\n",buffer);
}
if (FD_ISSET(fd, &allset) > 0) //检测fd是不是在allset内,
{
int mou_loc;
read(fd, &mou_loc, sizeof(mou_loc));
printf("mouse location=%d\n", mou_loc);
}
// }
printf("select is over\n");
close(fd);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd = open("/dev/input/mouse0", O_RDWR | O_CREAT);
if (fd == -1)
{
perror("fd open error");
exit(-1);
}
int all_fd[1024]; // 定义一个文件描述符数组,全部初始化为-1————以用于监听时的轮询
for (int i = 0; i < 1024; ++i)
{
all_fd[i] = -1;
}
fd_set set1; // 文件描述符组 添加 stdin:0描述符
FD_SET(0, &set1);
for (int i = 0; i < 1024; ++i)
{
if (all_fd[i] == -1)
{
all_fd[i] = 0;
break;
}
}
FD_SET(fd, &set1); // 文件描述符组 添加 鼠标:fd描述符
for (int i = 0; i < 1024; ++i)
{
if (all_fd[i] == -1)
{
all_fd[i] = fd;
break;
}
}
fd_set allset;
while (1) // 循环 以轮询文件描述组内,哪个文件描述符可读写变化了
{
allset = set1;
int ret = select(fd + 1, &allset, NULL, NULL, NULL); // null 一直等待
if (ret < 0) //<0 说明没有变化
{
perror("select error");
exit(-1);
}
if (ret > 0) //>0 说明有文件变化了
{
/********************** 轮询的核心代码 *******************/
for (int i = 0; i < 1024; ++i)
{
if (all_fd[i] != -1)
{
if (FD_ISSET(all_fd[i], &allset) > 0)
{
if (0 == all_fd[i])
{
char buffer[1024];
memset(buffer, 0, 1024);
read(0, buffer, sizeof(buffer) - 1);
buffer[1023] = '\0';
printf("%s\n", buffer);
}
if (fd == all_fd[i])
{
int location;
read(fd, &location, sizeof(int));
printf("%d\n", location);
}
if (0 == --ret)
{
break;
}
}
}
}
}
}
printf("select is over\n");
close(fd);
return 0;
}
缺点:
1. 范围受限,fd_set()是一个数组,静态分配空间,固定大小是1024
2. 轮询 :全盘轮询,效率最低
优点:
1. 跨平台,win32下也可以实现
轮询(Polling)和监听(Listening)是两种不同的输入/输出(I/O)处理机制。
1. 轮询(Polling):
- 概念:在轮询机制中,程序会不断地主动查询(轮询)是否有新的I/O事件发生,然后采取相应的操作。
- 工作原理:当程序需要等待某个I/O事件时,它会不断地查询该事件是否已经就绪,如果还没就绪就继续查询直至事件就绪。
- 特点:简单易实现,但是可能会造成CPU资源的浪费,因为程序需要不断地查询事件状态,如果没有事件发生就会浪费CPU时间。
2. 监听(Listening):
- 概念:在监听机制下,程序会将对I/O事件的管理交给操作系统,通过注册回调函数(或事件处理函数)来处理事件。
- 工作原理:程序将自己注册到操作系统的事件管理机制中,然后操作系统在相应的事件发生时,会调用注册的回调函数来处理事件。
- 特点:由操作系统管理事件,程序只需等待事件发生和处理就好,避免了不断轮询造成的资源浪费。
是一种I/O处理技术,它将多个I/O流集中到一个线程中处理,并且能实时监测多个I/O事件的发生情况。在传统的单路I/O处理中,每个I/O流在一个线程中独占一个处理线程,而多路I/O复用可以通过轮询或监听等机制,同时处理多个I/O事件。
多路I/O复用的特点是,采用了非阻塞I/O的方式,可以在一个线程中同时监听多个I/O事件,当有事件发生时,触发相应的处理程序,实现并发处理多个I/O操作。这种技术在服务器应用中非常有用,可以提高系统的吞吐量和响应速度。
常见的多路I/O复用模型有基于轮询(如select、poll)和基于事件监听(如epoll)两种。这些技术能够有效地减少资源的占用,提高系统的性能。
函数原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
其中:
1. struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
2. nfds:类型为nfds_t,是fds数组中元素的个数
3.
timeout > 0:表示超时时间,poll函数会等待指定的毫秒数,直到有事件发生或超时。
timeout == 0:表示立即返回,不等待事件发生。
timeout < 0:表示无限等待,poll函数会一直等待直到有事件发生。
返回值:
> 0:表示有一个或多个文件描述符就绪,返回值为就绪的文件描述符个数。
0:表示超时,没有文件描述符就绪。
-1:表示出错,可以通过errno变量获取具体的错误码。
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd1 = open("/dev/input/mouse0", O_RDWR | O_CREAT, 0777);
if (fd1 == -1)
{
perror("fd1 error");
exit(-1);
}
struct pollfd fd[2]; //1.struct结构体内有:int fd; short int events; short int revents
fd[0].fd = 0;
fd[0].events = POLLIN;
fd[1].fd = fd1;
fd[1].events = POLLIN;
while (1)
{
int ret = poll(fd, 2, -1);
if (ret > 0)
{
for (int i = 0; i < 2; ++i)
{
if (fd[i].events == fd[i].revents)
{
if (fd[i].fd == 0)
{
char buffer[64];
memset(buffer, 0, sizeof(buffer));
read(0, buffer, sizeof(buffer));
printf("buffer=%s\n", buffer);
}
else if (fd[i].fd == fd1)
{
int location;
read(fd1, &location, sizeof(int));
printf("location=%d\n", location);
}
if (--ret == 0)
{
break;
}
fd[i].revents=0;
}
}
}
}
return 0;
}
后续网络编程会涉及到