③【linux系统编程】李慧芹老师嵌入式Linux

linux_c系统开发学习笔记
day1

知识点汇总
要求:
1.学习的时候弃用root用户
原因:root 和 普通用户 区别很大
用普通用户 和工作的时候更加相似、往往没有权限,能学习到更多的东西。
2.重构之前写的代码
用新学到的机制来解决旧的问题
老师会提醒哪里可以重构、提升代码量

标准IO
I/O 是一切实现的基础
没有io就保存不了数据
分为 stdio sysio 即标准IO和系统调用IO
如何区分?
系统调用io是系统内核提供的,没有统一的接口
所以需要标准化,就有了标准io
解放了程序员
比如printf 不用考虑环境和编译器
所以优先使用标准io 移植性好 合并系统调用
加速读写(buffer cache)
标准io:

例如fopen分别依赖open和openfile
apue第五章
标准IO FILE类型
结构体,有什么类型?
拿来主义,先知道用法,
man操作
多看man手册

不能用指针去改常量,虽然有可能得到xbc
取决于编译器会把它放在哪儿
errno error number 是个全局变量 出错会将其值设置 如果不及时输出就会被其他error覆盖

gcc -E 编译预处理指令
私有化数据? error不再是一个整型而是一个宏
制造打开失败,用只读的方式打开一个不存在的文件!
eof 指向的是文件最后一个有效字符的下一个位置
a+打开,读的话在最前面,写的话在最后面!
文本流、二进制流
b 是对Windows系统起作用
有可能移植的时候考虑加不加b(以二进制进行操作)
man 给出的头文件有几个就要包几个
不能省略
没包头文件=编译器看不见函数原型
gcc会把所有的返回值视为整型
能够吧errno 转换为 errno msg的函数:
perror (char*)
输出的内容+ 错误信息(根据当前的errno输出错误信息)
strerror(int errnum)
在<string.h> 里面
提问:fopen打开文件,返回的指针所指向的那块内存是属于 栈?静态区?还是堆?
我觉得是栈 但是不对,因为如果是栈上的内存,函数结束后就会被释放,返回了局部变量的地址!
静态区也不对,虽然确实可以返回,但是用static只能有一块,每次都会被覆盖。
localtime 里面的指针放在静态区
从fclose也可看出,在动态申请的内存中能释放(逆操作 可以判定是在动态申请)
没有逆操作,则不一定在堆上
是资源有上限
一个进程能打开多少文件?
一个进程首先需要打开三个流
strin strout strerr
shell也打开了(应该)输出重定向 输入重定向
ulimit -a 查看 open files 限制的个数
创建文件的时候没有指定文件的权限!
如何指定?0666按位与 ~umask
~是取反 0开头是八进制数
可以用来限制进程创建的文件权限
遇到函数返回了指针
要下意识的判断 该指针指向静态区、堆还是栈?
day2 2022年7月6日
fgetc fputc 操作字符输入输出
getchar 相当于getc(stdin)
getc相当于fgetc
int getc(FILE *stream);
返回的int是转换过的
fgetc 和 getc 一模一样?不对
fgetc是函数 getc是宏
函数和宏的区别:
宏只占用编译的时间,不占用调用的时间,函数则相反
linux内核中用宏是为了节约时间
日常应用级开发还是多用函数+inline
putchar (c) = putc(c,stdout)
putc 是宏, fputc是函数
小作业:写一个mycp 要求:复制出来的dest文件要跟src文件一模一样。
写程序的建议:
先写几个常用的头文件stdio.h stdlib.h
文件关闭顺序:
最好首先关闭依赖别人的对象
然后关闭被依赖的对象
只要用到命令行,就要判断命令行的参数(相当于写注释)并给出提示信息
内存泄露:打开第一个文件成功,打开第二个文件失败了
此时第一个文件还没有关闭。
现在允许这种情况发生,以后会用到钩子函数(?没听清)
可以把一旦出错就运行的函数放在一起执行
gets(char *s)
gets 很危险,不要用,因为s可能会溢出
推荐用
fgets (char *s,int size,FILE *stream)
fgets 正常结束的情况:读到size-1个字节 (最后给s末位一个\0)和 读到一个'\n'。
例如:用fgets(buffer,5,fp)读取 abcd
需要读两次 第一次读 abcd'\0' 第二次读'\n''\0'
fgets出错时,返回一个空指针
fputs(buffer,fp);将buffer的内容输出到指定流中
size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream)
从stream中读取 size * nmemb 大小的内容到ptr指定的地方
size_t fwrite(const void *ptr, size_t size,size_t nmemb,FILE *stream)
从ptr指向的空间中读取size*nmemb大小的内容到stream中
问题:
1.错位就全错
2.返回值是成功读到或者写的对象个数,不一定是字符的个数(即nmem)
可以当做fgetc来使用
不能小瞧io操作,因为不熟悉io会导致后面的内容难以理解
printf scanf
最好用fprintf,输出重定向,比较有扩展性
sprintf 重定向到某一个字符串,比较像atoi的逆操作,但是没有一个函数叫itoa
因为sprintf没有指定缓冲区的大小,加了一个参数,就有了snprintf,但是依旧没有解决问题(最大字节数为size-1),因为后面的参数还是有可能造成缓冲区溢出。(fgets一样有问题,不一定每次都能取满)
scanf:从stdin以format格式取数据,一个一个放到...里的参数去
fsancf 和 sscanf 都是重定向
sscanf:解析一个字符串把它里面的内容按照format格式转到...指定的位置
format里面最好不写%s !!!(慎重使用)
因为你无法预测读取的内容有多长(可能溢出)这是scanf最大的缺陷之一
ftell 返回类型为long 很丑陋
因为不可能有文件指针有负数
但是为了迁就fseek(offset的参数类型long)
也就是说fseek和ftell在文件超过2g会失效
当时一张软盘16M 。。。所以。。
于是fseeko ftello 的参数变成了off_t(offset type)自己也可以使用这种技巧
off_t的大小一般来说是32位,但也不一定
这时可以利用宏可重复定义的特性,自己定义一下
#define _FILE_OFFSET_BITS 64
就可以吧off_t 转换为64位的类型
gcc指令中也可以定义,makefile 也可以定义
CFLAGS+=-D_FILE_OFFSET_BITS=64
man手册
CONFORMING TO 当前函数所遵循的协议或标准
可以看到fseeko是一个方言(不遵循c86或者c99 而是 posix)

而fseek是

那么当文件比较大,而又要求移植性比较好,就得另辟蹊径了。
fseek(FILE *fp,long offset,int whence) whence是起始位置,有SEEK_SET ,SEEK_CUR SEKK
rewind封装了fseek
相当于fseek(FILE *fp,0L,SEEK_SET);
fseek的妙用:产生空洞文件
例子:在使用迅雷等P2P下载的时候,下载的文件的大小不是从零开始的,而是一开始经过了像fseek这样的函数,创建了一个大小与下载完成后的文件一样的空洞文件,然后把该文件分块,使用多线程或者多进程异步下载!
fflush()不刷新可能有意外发生
例子:

这样无法打印出Before while()
标准输出是行缓冲模式,加上\n或者fflush才能输出
fflush不带参数时会刷新所有打开的流的缓冲区,带参数则刷新指定的流
缓冲区的作用:

修改缓冲区 setvbuf
little trick:
在vim的命令行模式下
shift+k可以直接跳到man手册上(coooool !)

问题:如何提取出完整的一行?
无论其大小
两个选项
1.使用动态内存来解决
2.getline
man getline

getline使用之前 需要宏定义一个_GNU_SOURCE
为什么要定义?为什么宏定义没有替换的值?
经过google发现,_GNU_SOURCE相当于一个开关,用来让用户配置编译环境的头文件,这个宏可以让用户打开所有feature.
/* If _GNU_SOURCE was defined by the user, turn on all the other features. */
#ifdef _GNU_SOURCE
# undef _ISOC99_SOURCE
# define _ISOC99_SOURCE 1
# undef _POSIX_SOURCE
# define _POSIX_SOURCE 1
# undef _POSIX_C_SOURCE
# define _POSIX_C_SOURCE 200112L
# undef _XOPEN_SOURCE
# define _XOPEN_SOURCE 600
# undef _XOPEN_SOURCE_EXTENDED
# define _XOPEN_SOURCE_EXTENDED 1
# undef _LARGEFILE64_SOURCE
# define _LARGEFILE64_SOURCE 1
# undef _BSD_SOURCE
# define _BSD_SOURCE 1
# undef _SVID_SOURCE
# define _SVID_SOURCE 1
# undef _ATFILE_SOURCE
# define _ATFILE_SOURCE 1
#endif
最好写到makefile 当中
练习:自己封装一个getline,实现读一行
临时文件
char* tmpnam(char *s)给一个字符地址,根据其中的内容返回一个临时文件名
在并发执行的时候容易出错,因为获取文件名和创建文件不是一气呵成的
FILE *tmpfile(void)
可以产生匿名文件,没有名字就不会冲突(w+b方式打开)
一个文件,如果没有任何的硬链接指向他,而当前他的打开文件计数已经成为零值,那么这个文件就会被销毁。
所以如果fclose匿名文件的指针,那么该文件就被释放了(如果没有释放且是守护进程就发生了泄漏)

还有其他方法创建临时文件
day3 系统调用io
文件描述符在文件io中贯穿始终(fd)
上一个标准io是FILE 指针
什么是文件描述符?
有哪些操作?
系统io的操作是支持标准io的
fopen ->open等
本章知识点汇总

inode:文件的唯一标识
文件描述符优先使用当前空闲的最小的整型值
(能够复用之前用过的下标)
文件描述符的产生:
调用open函数

open函数需要包含以上三个文件
这里给出了两种不同参数列表的open
提问:是用函数重载实现的?还是可变参数列表实现的?
应该是可变参数列表,首先c语言中不能实现函数重载。若在C++环境中提问,则可以随便传几个参数,看他报错的类型是参数类型error还是警告。
参数flags 是权限信息,由位图实现。
他必须包含O_RDONLY O_WRONLY O_RDWR中的一个
更多的选项例如
文件的创建选项(creation flags)
文件的状态选项(status flags)
可以通过按位或来进行选择

O_CREAT,有则清空无则创建
O_TRUNC,截断或者截短(truncate)
。。。有很多记不过来了
open返回值:成功返回fd失败返回-1
day4
练习:用系统io实现cp命令(重点,每次write不一定会全部写入到fd中,要检查写入的个数和buffer的长度是否一致,不一致需要重复写入)

为什么 第一个O_WRONLY和O_CREAT或运算了而后面的O_TRUNC是另外的参数?
应该是写错了
文件IO和标准IO的区别
标准io是有缓冲的,吞吐量比较大(缓冲机制合并了系统调用)
文件io是实时的,响应速度快。
如何使一个程序变快?
要区分是响应速度还是吞吐量
标准io和文件io不能混用!!
fileno可以转换FILE* fp到 fd
fdopen可以把fd转换为FILE *fp
为什么不能混用?至少pos不一样!因为缓冲区会造成影响,fp的pos移动了,但是fd可能还没来得及改动pos。反过来也是一样,从fd中读取一个cache块大小,一下子读出1024,但是fp中的pos可能才用到1个,并没有加到1024
strace 跟踪可执行文件的系统调用
io效率问题
问题:bufsize的大小有何影响?
time命令 测试程序运行时间
练习,把之前写的cp的bufsize从128一直到16M测试他的时间,观察在哪个点效率最高
不一定16M,直到段错误
可以看到,我用 truncate -s 2G test 命令创建了一个大小为2G的文件。

尝试将其复制
用时20.739秒 此时的BUFSIZE为128
将BUFSIZE改为1024之后再次复制

用时减少到了4.228s!!
有没有可能再减少一点呢?
我改成了8K的缓冲

2.56s! 依旧可以加速程序运行
128K: (和8K速度几乎一样)

似乎从8K~1M都差不多
16M会比较慢
区间应该在8K到128K
32K

对于2G大小的文件
缓冲区大概设置为32k比较合适
这个文件缓冲比的大小为2^8
不知道这个结论有没有普遍性?
后面发现没有。。。(艹)
因为最佳效率在缓冲区为block的整数倍才会发生
可以用
stat / | grep "IO Block"
来查看当前block的大小,我这边服务器的block size为4K,这就是为什么在8K的时候效率会上升,且128K与8K差不多。(因为都是4K的整数倍!)
文件共享:
面试题:写程序删除一个文件的第十行
普通思路:打开文件一次,先找第11行,复制到第十行,像数组一样依次覆盖直到文件结束
这样一次循环需要四次系统调用:seek
进阶思路:打开文件两次,一个只读一个写,
减少系统调用的次数
可以选用两个进程还是两个线程来进行这样的操作
truncate/ftruncate函数截断文件
作业:实现上述功能
程序中的重定向:dup和dup2
duplicate 重复,复制
int dup(oldfd)
close(1)和dup(fd)不是一个原子操作
也就是说,close1之后有可能其他人把1占用了
dup 的fd有可能不生成在1这个位置上,文件重定向就失败了。
这里需要用到dup2这个原子操作
int dup2(oldfd , newfd)

dup2 (fd,1);dup2把第一个oldfd的描述符复制到第二个上,如果第二个被占用了,则会被关闭后复制。
老师写的程序依然不对!
因为不能默认一个进程打开了012
而且有一个原则:不要以为自己在写main函数
你总会与别人共同工作,在你修改了标准流之后应该还原回去。
day5
同步:
sync 同步内核层面的buf和cache
解除设备挂载的时候,需要同步一下
fsync同步一个文件的buf和cache
fdatasync 只刷新数据不刷新亚数据(比如说文件的修改时间,文件的属性)
fcntl(int fd,int cmd,...) :管理文件描述符
cmd可以选:F_DUPFD 。。。
cmd不同,返回值不同
管家级别的函数
dup和dup2封装了fcntl
ioctl:设备相关的管家
一切皆文件好不好?
简化了绝大数的操作,但是有些设备不仅仅是读写,损害了一小部分的利益
ioctl_list 居多,古董级别
/dev/fd/ 当前运行进程的文件描述符目录,是虚目录。
文件系统
一、目录和文件

获取文件属性:实现一个myls,尽力去模仿ls
问题:为什么有了短格式的选项还要有长格式?
因为短格式只能使用一次,有可能冲突
格式这类运维考的多
例如创建一个 文件名为-a的文件

两种方法, -- (终止命令选项)或者指定路径
myls可做选项 a i n l (uid gid)
不要求上色
这两个文件可以找到uid和gid对应的username和groupname,从而实现-n 和 -l

stat系统调用
fstat lstat (link stat)
获取文件的属性信息,填入buf中
复习:FILE*和fd互相转换的函数
FILE *fdopen(int fd, const char *mode);
int fileno(FILE *stream);
函数原型

tags工具:vim -t查看类型的详情

off_t的位数是16?32?64?
不知道,所以不能直接用int代替
使用makefile 进行宏的重定义
例如

文件占的磁盘空间 blocksize*block数
并不等于文件的字节数
这里5G大小的文件只占用磁盘4K!

空洞文件:
cp支持空洞文件拷贝
cp拷贝的时候会检查缓冲区,如果全是空则记录长度
文件类型 dcb-lsp
文件类型:
-:普通文件 (regular file)
d:目录文件(directory)
b:块设备文件 (block device)
c:字符设备文件 (character)
l:符号链接文件(symbolic link file)
p:命名管道文件(named pipe)
s:套接字文件(socket)

文件属性的确定: 0666 & ~umask
umask为了防止产生权限过松的文件
文件权限的更改 chmod

粘住位 (t位?)
是给一个可执行文件设计的,加速其再次装载
现在因为有cache 所以一般给目录设置
比如tmp目录

文件系统:
FAT和UFS 不开源和开源
FAT :静态单链表,
轻量级,目前U盘还在用,并不难,相当于本科学期期末作业
二、系统数据文件和信息
三、进程环境