文章笔记来自于【速学Linux】手把手教你学嵌入式Linux C应用编程_哔哩哔哩_bilibili
一,什么是linux应用程序
1.运行在linux操作系统用户空间的程序
2.内核程序运行在内核空间,应用程序运行在用户空间
在终端执行的命令ls,ps。。。。。。都是运行在用户空间
3.内核空间和用户空间
二,应用编程与裸机编程、驱动编程有什么区别?
三,如何编写linux应用程序
1.系统调用 【linux操作系统向应用层提供的接口,system call ,是linux 应用层进入内核空间的入口】
2.库函数:标准c库函数 【对系统调用的封装】
3.不要局限于编程语言
四,文件I/O基础
1.什么是文件I/O ? 【对文件的输入和输出操作,也就是对文件的读/写操作】
2.文件描述符
这相当于是每次打开一个文件,每个文件会分配一个文件描述符【分配一个没有被使用的最小非负整数作为文件描述符】,相当于这个文件的标签一下,所有执行 IO 操作的系统调用都是通过文件描述符来索引到对应的文件,一个进程最多可以打开 1024 个文件,当文件被关闭时,对应的文件描述符会被释放,释放之后也就成为了一个没有被使用的文件描述符了
3.打开/创建文件:open()函数
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname : 字符串类型,用于标识需要打开或创建的文件,可以包含路径(绝对路径或相对路径)信息,譬如:"./src_file" (当前目录下的 src_file 文件)、 "/home/dengtao/hello.c" 等;如果 pathname 是一个符号链接,会对其进行解引用flags : 调用 open 函数时需要提供的标志,包括文件访问模式标志以及其它文件相关标志,这些标志使用宏定义进行描述
4.写文件:write()函数
ssize_t write(int fd, const void *buf, size_t count);
fd : 文件描述符。关于文件描述符,前面已经给大家进行了简单地讲解,这里不再重述!我们需要将进行写操作的文件所对应的文件描述符传递给 write 函数。buf : 指定写入数据对应的缓冲区。count : 指定写入的字节数。返回值: 如果成功将返回写入的字节数( 0 表示未写入任何字节),如果此数字小于 count 参数,这不是错误,譬如磁盘空间已满,可能会发生这种情况;如果写入出错,则返回 -1 。读文件和写文件存在一个读写位置【读写指针】,假设初始打开的文件才开始是在0的位置,当写入100字节,指针往后移动100字节,且 读写指针是共用指针,然后就会从100字节出开始读取
ssize_t read(int fd, void *buf, size_t count);
fd : 文件描述符。与 write 函数的 fd 参数意义相同。buf : 指定用于存储读取数据的缓冲区。count : 指定需要读取的字节数。返回值: 如果读取成功将返回读取到的字节数,实际读取到的字节数可能会小于 count 参数指定的字节数,也有可能会为 0 ,譬如进行读操作时,当前文件位置偏移量已经到了文件末尾。实际读取到的字节数少于要求读取的字节数,譬如在到达文件末尾之前有 30 个字节数据,而要求读取 100 个字节,则 read 读取成功只能返回 30;而下一次再调用 read 读,它将返回 0 (文件末尾)。
6. 关闭文件: close()函数
int close(int fd);
fd : 文件描述符,需要关闭的文件所对应的文件描述符。返回值: 如果成功返回 0 ,如果失败则返回 -1 。
学习示例:
1.打开文件,写入内容
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
int fd;
int ret;
fd = open("./test.txt",O_WRONLY | O_CREAT | O_EXCL,0644);
if(-1 == fd){
printf("open error");
return 1;
}
printf("open ok\r\n");
ret = write(fd, "Hello world",11);
if(-1 == ret){
printf("write error\r\n");
close(fd);
return 1;
}
printf("write %d Bytes OK",ret);
close(fd);
return 0;
}
运行结果:
2.读取文件内容
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int fd;
char buf[125] = {0};
int ret;
fd = open("./test.txt",O_RDONLY);
if(-1 == fd){
printf("open error");
}
printf("open ok");
ret = read(fd, buf, 11);
if(-1 == ret){
printf("read error\r\n");
close(fd);
return 1;
}
printf("read %d bytes: %s",ret,buf);
close(fd);
return 0;
}
运行结果:
7.调整读写位置偏移:lseek()函数
off_t lseek(int fd, off_t offset, int whence);
fd : 文件描述符。offset : 偏移量,以字节为单位。whence : 用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):⚫ SEEK_SET :读写偏移量将指向 offset 字节位置处(从文件头部开始算⚫ SEEK_CUR :读写偏移量将指向当前位置偏移量 + offset 字节位置处, offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移;⚫ SEEK_END :读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。
五,深入探究文件I/O
1.linux系统如何进行文件管理?
静态文件:文件存放在磁盘内叫做静态文件
inode: 指向文件存放在磁盘的位置
pcb: 进程控制块,管理该进程,譬如用于记录进程的状态信息、运行特征,
2.错误处理
1.errno
2.strerror函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void)
{
int fd1;
fd1 = open("./test111.c",O_RDONLY);
if(-1 == fd1)
{
printf("%s\n", strerror(errno));
return -1;
}
return 0;
}
运行结果:
3.perror:可以加入自己的打印信息
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void)
{
int fd1;
fd1 = open("./test111.c",O_RDONLY);
if(-1 == fd1)
{
perror("open error");
return -1;
}
return 0;
}
运行结果
3.空洞文件
概念:当一个文件本身只有4k的大小,但是当前的通过lseek从文件头部偏移6000个字节,开始写入内容,导致4096-6000字节间没有内容,出现了“空洞”,也就是空洞文件
使用场景:一个文件可以从不同的地址开始写入,不再是单线程从头开始写入,多个线程同时写入,利用了多线程的优势
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
static unsigned char buf[4096];
int main(void)
{
int fd;
int ret;
fd = open("./test.txt",O_WRONLY | O_CREAT | O_EXCL,0644);
if(-1 == fd)
{
perror("open error");
return 1;
}
ret = lseek(fd, 4096, SEEK_SET);
if(-1 == ret)
{
perror("lseek error");
close(fd);
return 1;
}
ret = write(fd, buf, 4096);
if(-1 == ret)
{
printf("write error");
close(fd);
return 1;
}
close(fd);
return 0;
}
运行结果:
ls 命令:查看到的大小是文件的逻辑大小du 命令:查看到的大小是文件实际占用存储块的大小
4.O_TRUINC和O_APPEND
O_TRUINC:将文件原本的内容全部丢弃,文件大小变为 0
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void)
{
int fd;
fd = open("./test.txt",O_WRONLY | O_TRUNC);
if(-1 == fd)
{
perror("open error");
close(fd);
return 1;
}
close(fd);
return 0;
}
运行结果:
O_APPEND:当每次使用 write()函数对文件进行写操作时,都会自动把文件当前位置偏移量移动到文件末尾
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void)
{
int fd;
int ret;
fd = open("./test.txt",O_WRONLY | O_APPEND);
if(-1 == fd)
{
perror("open error");
close(fd);
return 1;
}
ret = write(fd,"hello world",11);
if(-1 == ret)
{
perror("write error");
close(fd);
return 1;
}
close(fd);
return 0;
}
运行结果:
open可以多次打开一个文件,产生多个文件描述符,也就是存在多个文件表,,但是都指向同一个文件,但是只能产生一个动态文件,以及同一个inode。对于打开的每一个fd.都要依次关闭。
5.文件描述符的复制
int dup(int oldfd); 复制文件描述符
int dup2(int oldfd, int newfd); 文件描述符的数可以自行决定
新的文件描述符和旧的指向同一个文件表,去完成任何操作
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd1, fd2;
int ret;
fd1 = open("./test_file", O_RDWR | O_CREAT | O_EXCL,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (-1 == fd1) {
perror("open error");
exit(-1);
}
/* 复制文件描述符 */
fd2 = dup2(fd1, 100);
if (-1 == fd2) {
perror("dup error");
ret = -1;
goto err1;
}
printf("fd1: %d\nfd2: %d\n", fd1, fd2);
ret = 0;
close(fd2);
err1:
/* 关闭文件 */
close(fd1);
exit(ret);
}
运行结果
6.文件共享
概念:同一个文件(譬如磁盘上的同一个文件,对应同一个 inode)被多个独立的读写体同时进行 IO 操作
常见的三种文件共享的实现方式
(1)同一个进程中多次调用open函数打开同一个文件,多个文件描述符指向不同的文件表,但是多个文件表中的inode指针指向inode节点
(2)不同的进程分别打开同一个文件
(3)同一个进程中通过dup(dup2)函数对文件描述符进行复制
7.原子操作与竞争冒险
竞争冒险:进程获得cpu的使用权完全是由操作系统决定的,先后顺序是不可预期的
原子操作:多个步骤组成的一个操作,要么不执行,要么必须执行完所有操作,不可以只执行所有步骤的一个子集
(1)O_APPEND 实现原子操作移动到文件末尾,然后读写文件(2)pread() 和 pwrite()移动到偏移位置,然后读写文件
(3) 创建一个文件O_EXCL先判断是否有文件 然后创建文件
8.截断文件
ftruncate():使用文件描述符 fd 来指定目标文件大小,目标文件必须具有可写权限
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd;
int ret;
fd = open("./test.txt", O_RDONLY | O_WRONLY);
if(-1 == fd)
{
perror("open error");
return 1;
}
ret = ftruncate(fd, 4096);
if(ret == -1)
{
perror("sss");
return 1;
}
close(fd);
return 0;
}
运行结果:
9.fcntl和ioctl函数
fcntl()函数():对文件描述符做一系列的控制操作
int fcntl(int fd, int cmd, ... /* arg */ )
cmd 命令集:⚫ 复制文件描述符( cmd=F_DUPFD 或 cmd=F_DUPFD_CLOEXEC)⚫ 获取 / 设置文件描述符标志( cmd=F_GETFD 或 cmd=F_SETFD )⚫ 获取 / 设置文件状态标志( cmd=F_GETFL 或 cmd=F_SETFL )⚫ 获取 / 设置异步 IO 所有权( cmd=F_GETOWN 或 cmd=F_SETOWN )⚫ 获取 / 设置记录锁( cmd=F_GETLK 或 cmd=F_SETLK )
ioctl()函数:是一个文件 IO 操作的杂物箱,可以处理的事情非常杂、不统一
int ioctl(int fd, unsigned long request, ...);