欢迎光临散文网 会员登陆 & 注册

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

2022-07-05 22:24 作者:狱雷Guru  | 我要投稿

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盘还在用,并不难,相当于本科学期期末作业







二、系统数据文件和信息

三、进程环境








③【linux系统编程】李慧芹老师嵌入式Linux的评论 (共 条)

分享到微博请遵守国家法律