linux C 系统开发
本文最后更新于 2023年6月24日 中午
linux_c 系统开发学习笔记 -------k3ppf0r(qcw)
目前需要实现基于客户机和服务器 CS 模型的网络音频 广播/点播 系统
本音频系统可以广泛用于在语言教室和公共广播等多种场所,该软件分为 CS,服务器运行在 PC 机器上,客户机可运行在 PC 或嵌入式,服务器以多播的方式向局域网中所有客户机发送数据,客户机可以根据自己的选择来决定要接收的数据。
服务器 =》 路由器/交换机 =》 客户机 1,客户机 2,
类似 arp 协议?
参考书目:
UNIX 环境高级编程
UNIX 网络编程
TCP/IP 详解
深入理解计算机系统
守护进程(系统日志)
S
多线程多进程开发(fork)
main data list
——–socket–>
C
父进程 –>(多进程实现及关系,进程间通信(管道,共享内存 ,消息队列)) 子进程(解析播放)
lib
引入数据库或者使用当前文件系统
解析文件系统存储
流量控制
开始学不要谈效率问题 只要易读性,拿来主义
IO: 是实现一切的基础
stdio 标准 IO
sysio 系统调用 IO,文件 IO,无缓存 IO
man 2 系统调用
man 7 查看机制
标准 IO
由标准 C 库(第三方库)提供的接口函数(通过封装操作系统提供的系统 IO,再给用户使用)
man 3
FILE 类型贯穿始终(结构体)
fopen(); 内部 malloc(sizeof(FILE))
fclose(); 内部 free()
fgetc();
fputc();
fputs();
fgets();
// 逐字节
fread();
fwrite();
const char *系列
是漏洞最多的地方;溢出在于%s;格式化字符串漏洞在于 format;
printf();
fprintf();
scanf();
sscanf(args…,format,str) => 镜像函数 <= sprintf(str,format,args…) [itoa 等应用]
文件位置指针
重定位: fseek(fp,0L,SEEK_END);fseeko();
用于制造空洞文件
告诉我: ftell(fp);ftello();
倒回: rewind(fp);
fflush();
默认行缓冲模式,不加换行符,就会等着缓冲区满了输出。
写文件是全缓冲模式
getline();
getdelim();
临时文件 考虑到问题:1 如何不冲突 2 及时销毁
tmpnam();
FILE *tmpfile(void); => 给你一个匿名文件
系统层面 IO:
操作系统直接提供的接口函数
文件描述符 fd 是在文件 IO 中贯穿始终的类型
1 |
|
(学习方法: 猜 FILE 内容 然后去验证)
FILE
| … |
| POS |
| FD |
文件描述符
就是一个数组下标,int 类型,通过整形数,找到文件结构体,再找到实际的文件
会优先使用当前最小的下标
|0|
|1|
|2|
打开同一个文件
|3|-> struct1|…|pos|count|…| -> inode1
|4|-> struct2|…|pos|count|…| -> inode1
一个结构体被引用到两个 fd
|5|-> struct3|…|pos|count(记录被引用次数)|…| -> inode2
|6|-> struct3
1 |
|
将文件 IO 与标准 IO(FILE )的区别
举例: 传达室老大爷跑邮局
标准 IO 是有缓存的,适用于吞吐量大,使用简单
文件 IO,无缓存 IO, 响应速度快,user_to_kernel
面试:如何使一个程序变快? 使用文件 IO
提醒: 标准 IO 与文件 IO 不可混用
要转换: fileno() fdopen()
IO 的效率问题
1 |
|
文件共享问题:多个任务共同操作一个文件或者协同完成任务
写程序删除一个文件的第十行
1、覆盖写
1-> open r -> fd1 -> lseek 11
1-> open r+ -> fd2 -> lseek 10
while()
{
1-> fd1 -> read
2-> fd2 -> write
}
2、truncate/ftruncate
原子操作
不可分割的最小操作
作用: 解决竞争和冲突
如 tmpnam()
程序中的重定向:dup,dup2
对于 fd 拷贝一个副本,到当前可用的最小的下标去。只操作了文件描述符数组,内存中的文件指针不变。
close(1);
dup(fd); // fd to 1
close(fd);
但如上方法有缺陷,不是原子操作
优化:
dup2(fd,1);
close(fd);
开发会注意到问题:
1 不要有内存泄漏
2 不要有写越界,溢出
3 不要当自己在写 main 函数,当自己在写一个小模块
同步问题 sync fsync fdatasync 中间层开发,介于底层和应用层
void sync(void);
将内核 buffer cache 全局催促
fcntl();
文件描述符所变的魔术,都来源于它
ioctl();
设备相关的管家
一切皆文件
光鲜亮丽下的祖安
/dev/fd/目录
虚目录,显示的是当前进程的文件描述符信息
ls -l /dev/fd/
/*************************************/
文件系统:
类 ls 的实现,myls -l -a -i -n
一、目录和文件
1.获取文件属性
创建-开头的文件 指定路径;– 截断
stat,fstat,lstat
(尤其小心使用数字常量,默认给的是 int)
(size 是一个属性而已,inode 才是唯一标识,block 是真实大小块)
2.文件访问权限
st_mode 是一个 bitmap,表示文件类型,文件访问权限,特殊权限位
3.umask
0666 & ~umask
防止产生权限过松的文件4.文件权限的更改/管理 chmod,fchmod
chmod(path,mode);
fchmod(fd,mode);5.粘住位
t 位chmod 1777 /tmp
tmp 目录6.文件系统 FAT UFS(类似 ext2)
文件系统 : 文件或数据的存储和管理
FAT 16/32 :是一个静态(数组)单链表 Windows 不开源
{
int next[n];
char data[n][size];
}
UFS
柱面族/块族
desc
inode 位图(0/1 没用/用)
块位图
inode 节点(内部小格子一个 4K)
块
inode 结构体数组
{
stat
亚数据
无关,隐藏
15 指针 12 + 1 级间接块指针 2 (256)、3 (256 个 2 级指针)
}
缺陷: inode 节点满了,但是块还是大量空白
文件名在目录项,目录文件里
7.硬链接 符号链接
硬链接不能给目录、分区建立;反之符号链接可以
ln file file_link 硬链接是目录项的同义词,两个指针指向同一个数据块
ln -s file file_link
link();
unlink(); 弄出匿名文件
man 3 rm
remove();
man 2 mv
rename();
8.utime
可以更改文件最后读的时间和最后修改的时间
9.目录的创建和销毁
mkdir();
rmdir();
10.更改工作路径
man 2 cd
chdir(path);fchdir(fd);
可以突破 chroot();
getcwd(buf,BUFSIZE);//pwd
11.分析目录,读取目录
1 |
|
或者 API:
1 |
|
二、 系统数据文件和信息
/etc/passwd
/etc/group
getgrgid();
getgrgrnam();
/etc/shadow
格式: $加密方法$杂质串盐$hash
getspnam();
crypt();
getpass();
时间戳
time();
gmtime();
localtime();
mktime();
strfttime();
加解密:
hash 混淆,不可逆,如果原串相同,所得串也相同,防备撞库
x % 5 = 2 => x=?
安全? 攻击成本大于收益
加密-解密
三、进程环境
1、main 函数
int main(int argc,char **argv)
2、进程的终止
正常终止:
从 main 函数返回
调用 exit()
调用_exit() 或 _Exit() 程序被 pwn 了 避免故障扩大
最后一个线程从其启动例程返回
最后一个线程调用 pthread_exit
异常终止:
调用 abort
接到一个信号,并终止
最后一个线程对其取消请求做出响应。
atexit(); 注册exit()的hook钩子函数(函数正文执行之前执行)
一个一个拿,一个一个放
应用(方便程序员的): if(fd100 < 0){
close(fd1);
close(fd2);
...
close(fd99);
perror();
exit(1);
}
3、命令行参数的分析
getopt();
getopt_long();
4、环境变量
key=valueexport
env
environ
getenv();
putenv();
5、C 程序的存储空间布局
32 位操作系统虚拟内存空间:
| 4G kernel |
| 3G 0xc0000000 |
| argv env |
| 栈 | 向下生长
| /// lib |
| heap | 向上生长
| bss |
|已初始化数据段 data|
| text 0x08048000|
| 0 |
pmap 查看 lib 映射
6、库
- 动态库
- 静态库
- 手工装载库
动态库的相关库函数
1 |
|
7、函数跳转
|c|
|b|
|a|
|main|
1 |
|
1 |
|
8、资源的获取以及控制
大部分 API 使用被封装在 ulimit 命令中
1 |
|
进程
进程基本知识
1、进程标识符 pid
类型 pid_t
1 |
|
S 可中断的睡眠态
Z+ 僵尸进程,子进程结束,等待父进程收集,父进程还没有结束
孤儿进程
进程号是顺次向下使用
1 |
|
2、进程的产生 fork() vfork()(废弃)
1 |
|
注意理解关键字:duplicating 拷贝,克隆,一摸一样
父子进程的区别:fork()返回值不一样,pid 不同,ppid 也不同,未决信号和文件锁不继承,资源利用量清零。
init 进程:1 号,是所有进程的祖先进程
调度器的调度策略来决定哪一个进程先运行
在调用 fork() 前一定要刷新缓冲区流(文件全缓冲 终端行缓冲),这是因为全缓冲下父进程中 fork 前的输出也会被子进程复用。
子进程在循环中干完了规定的活后一定要退出,否则会继续 fork! 。
1、fork()增加了读时共享,写时复制(Copy-On-Write,COW)的机制
写时复制可以避免拷贝大量根本就不会使用的数据(地址空间包含的数据多达数十兆)。
2、父子进程共用同一个文件描述符表(相当于公共设施的存在) ,这里也可以用来实现父子进程通信。
3、进程的消亡及释放资源
wait 防止父进程已经结束,子进程还没结束,使得成为孤儿进程
1 |
|
管理子进程,进程分配任务
1、分块法
2、交叉分配 √
3、池类算法
上游将任务送入任务池,下游抢任务,谁完成了就继续,涉及竞争冲突
4、exec 函数族
few => 三剑客
1 |
|
内部命令和外部命令
1 |
|
70 行实现 mysh 执行外部命令
(命令分隔符都不能使用)
5、用户权限及组权限(u+s,g+s)
内核为每个进程维护的三个 UID 值(和三个 GID 值,是对应关系,略),这三个 UID 分别是:
RUID:(Real UID,实际用户 ID),我们当前以哪个用户登录,我们运行程序产生进程的 RUID 就是这个用户的 UID。
EUID:(Effective UID,有效用户 ID),指当前进程实际以哪个 UID 来运行。一般情况下 EUID 等于 RUID;但如果进程对应的可执行文件具有 SUID 权限(也就是 rws 的 s),那么进程的 EUID 是该文件属主的 UID,鉴权看的就是这个 ID。
SUID:(Saved Set-user-ID,保存的设置用户 ID),EUID 的一个副本,与 SUID 权限有关。
文件和目录权限除了普通权限 rwx 外,还有三个特殊权限:
SUID:在属主的 x 位以 s 标识,全称 SetUID
SGID:在属组的 x 位以 s 标识,全称 SetGID
STIKCY:黏着位
exec 来鉴定权限, 设置 RUID EUID SUID , 执行文件进程鉴定权限 EUID (SUID)后,再继续执行
1 |
|
init 进程(0,0,0) –fork,exec–> getty 进程(0,0,0) –exec–> login 进程(0,0,0) –checkpass–T–fork,exec–>shell 进程(1001,1001,1001)
1 |
|
编写具有 u+s 的程序需要:
1 |
|
6、解释器文件
脚本文件
1 |
|
shell 看到了#!,就会把解释器装载起来,再用你的解释器解释这个文件,此时会把第一句话当成注释。
linux 世界讲究机制,而非策略
7、system();
是 few 的简单封装。调用 shell 去执行命令。
1 |
|
8、进程会计
1 |
|
9、进程时间
time 的实现
1 |
|
10、守护进程 Daemon
会话 session 会话是有一个或者多个进程组组成的集合
通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话
终端 (标准:只能输入和输出)tty
规范化 多线程并发
守护进程标志:
1 |
|
1 |
|
单实例守护进程: 锁文件 /var/run/name.pid
守护进程开机启动脚本文件:
1 |
|
11、系统日志
1 |
|
syslogd 服务
1 |
|
并发
同步:按你所设想的步骤执行。
异步:例子:俄罗斯方块,事件什么时候到来不知道,什么时候出结果也不知道。
异步事件的处理:查询法(频繁)、通知法(稀疏)
一、信号
1、信号是什么
信号是软件层面的中断。
信号的响应依赖于中断
中断是硬件层面的模仿
2、signal();
signal 会打断阻塞的系统调用
1 |
|
1 |
|
3、信号的不可靠
信号的行为不可靠,有可能第一次执行还没结果,第二次就开始了。原因是信号的执行现场不是我们构建的。
4、可重入函数
所有的系统调用都是可重入的,一部分库函数也是可重入的,如:memcpy,rand_r
不可重入的函数不能用在信号处理中。
函数不可重入的条件
函数内使用了静态的数据。
函数内使用了 malloc 或者 free 函数
函数内调用了标准的 I/O 函数
5、信号的响应过程
内核只有一种概念:线程
信号从收到到响应有一个不可避免延迟:
(位图)
mask:11111111111111111(是否响应)
pending(来信号):0000000000001(置 1)
响应时:
(位图)
mask:1->0
pending:1->0
响应过程:
程序被打断,拿着中断现场进入内核,写入信号相关 mask 等,当从 kernel 态回到 user 态时,会做这样的行为 mask & pending ,从而发现信号,执行信号注册的行为,行为执行完后回到内核,写入信号相关 mask 等,回到被打断的现场。
1 |
|
Linux 系统中的内核态本质是内核,是一种特殊的软件程序,用于控制计算机的硬件资源,例如协调 CPU 资源,分配内存资源,并且提供稳定的环境供应用程序运行。0-4G 范围的虚拟空间地址都可以操作,尤其是对 3-4G 范围的高位虚拟空间地址必须由内核态去操作。
用户态提供应用程序运行的空间,为了使应用程序访问到内核管理的资源,例如 CPU,内存,I/O 等。用户态只能受限的访问内存,且不允许访问外设(硬盘、网卡等);内核态 CPU 可以访问内存所有数据,包括外设,且可以将自己从一个程序切换到另一个程序。
用户态切换到内核态的三种方式:
发生系统调用时:(主动)这是处于用户态的进程主动请求切换到内核态的一种方式。用户态的进程通过系统调用申请使用操作系统提供的系统调用服务例程来处理任务。而系统调用的机制,其核心仍是使用了操作系统为用户特别开发的一个中断机制来实现的,即软中断。
产生异常时:(被动)当 CPU 执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行的进程切换到处理此异常的内核相关的程序中,也就是转到了内核态,如缺页异常。
外设产生中断时:(被动)当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作的完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
6、信号常用函数
1 |
|
7、信号集
1 |
|
8、信号屏蔽字/peding 集的处理
1 |
|
9、扩展
1 |
|
10、实时信号
取决于你用哪个信号,而不是哪套函数
1 |
|
二、多线程实现
1、线程的概念
一个正在运行的函数
posix 线程是一套标准,而不是实现
openmp 线程
线程标识:
1 |
|
ps ax -L 查看
2、线程的创建、终止、栈清理、取消选项
线程的调度取决于调度器策略。
1 |
|
线程终止:1、线程从启动例程返回,返回值就是线程的退出码
2、线程可以被同一进程中的其它线程取消
3、线程调用 pthread_exit()函数
1 |
|
线程取消:有两种状态,允许和不允许
允许取消又分为:异步 cancel,推迟 cancel->推迟至 cancel 点取消
cancel 点:POSIX 定义的 cancel 点,都是可能引发阻塞的系统调用
1 |
|
线程分离:
1 |
|
条件竞争:十字路口
用地址传参会导致竞争。
3、线程同步
互斥量
1 |
|
条件变量:
先发通知再解锁
1 |
|
信号量:
读写锁:互斥量和信号量的综合
r 读共享锁,w 写互斥锁(如果正在读,会加上预写锁)
4、线程属性,同步属性
1 |
|
线程同步的属性:互斥量属性
1 |
|
条件变量属性:
1 |
|
读写锁属性:
5、重入
多线程中的 IO
线程与信号
1 |
|
重新做一套语言 天然支持并发
openmp 标准:
1 |
|
高级 IO
非阻塞 IO – 阻塞 IO
有限状态机编程
1、非阻塞 IO
简单流程:程序的自然流程(把大象放冰箱里)是结构化的
复杂流程:自然流程不是结构化的(网络协议、口令两次输入)而是一个复杂的流程图。
IO 密集型任务,大部分在空闲
2、IO 多路转接
监视任务
1 |
|
poll 在 user 态建立了一个结构体数组,epoll 则是通过系统调用,kernel 来维护这个结构体数组
1 |
|
3、其他读写函数
1 |
|
4、存储映射 IO
1 |
|
可以用来父子进程来共享内存
1 |
|
5、文件锁
1 |
|
微信支付
支付宝支付
“如果你觉得这个博客对你有帮助,欢迎打赏!”