置顶
- 这个笔记是本人在有一定基础的情况下做的,所以有很多基础的就不记了,比如fork和vfork的区别,子进程怎么判断等
第一章:Unix系统编程概述
零. 相关命令和操作
- bc
- more
一. 理解系统编程
- 处理器 Processor
- 输入输出 IO
- 进程管理 Process Management
- 内存 Memory
- 设备 Device
- 计时器 Timers
- 进程间通信 Interprocess Communication
- 网络 Networking
二、学习方法
- 分析程序–它是做什么的
分析现有程序,了解其功能和实现原理 - 学习系统调用–它是如何实现的
- 编程实现–自己能不能编写一个
第二章:用户、文件操作与联机帮助:编写who
零. 相关命令和操作
- man,who,cp,login
- open(),read(),write(),creat(),lseek(),close()
- perror
1. 编写who指令
- 知识准备:
-
用户登录信息存放在文件 /var/run/utmp 文件中
-
/usr/include/utmp.h头文件有 utmp结构体,专门用来存储用户信息
- 编写步骤:
-
- 读取/var/run/utmp文件,把数据读到utmp结构体中
不过我查看这个文件加密了,,,这个怎么读。。。估计是书比较老,低版本的Linux能读?
- 读取/var/run/utmp文件,把数据读到utmp结构体中
-
- 再按照自己想要的格式打印出来
-
- 循环直到读完文件数据
- 总结
- 注意man的使用
2. 编写cp
- cp f1 f2
把f1的内容复制给f2,f2存在的话覆盖,不存在的话建立(用create就能满足这个要求了) - 就是文件的读写一顿操作
3. 缓冲的使用
- 每次read/write,都要进行内核态用户态切换,为了提高读写速度,可以一次多读点,存缓冲区里(buf)
- 内核缓冲区
- 数据并不是调用write后就立刻写到磁盘上,而是先存进内核缓冲区,积累到一定数量再写。
- 读数据也是先从磁盘读到缓冲区
- 这样能提高磁盘IO效率,但是若突然断电,内核还来不及把内核缓冲区的数据写到磁盘上,就会导致更新的数据丢失
4. 终端注销
- 还是读utmp文件,修改相应记录的状态,这个加密文件,,,貌似改不了啊
5. 错误处理
- <errno.h>中有指定错误类型的宏,可以根据这个获得错误信息
extern int error; //根据error的值判断错误类型
- 但是perror更方便,直接自动判断错误信息。
6. 总结
- 系统调用总结
- open(filename,how)
- create(filename,model)
- read(fd,buf,size)
- write(fd,buf,size)
- lseek(fd,distance,base)
- close(fd)
- 文件描述符,一个整形类型
- 缓存
- error错误标示
7. 课后思考
- lseek如果设置偏移量大于文件长度
- 会自动填充中间的空洞,但是空洞那部分不一定占用磁盘,要看操作系统的实现
- sync(),fsync(fd),fdatasync(fd)显式把数据从缓冲区写到磁盘
第三章:目录与文件属性:编写ls
零. 相关命令和操作
- ls
- opendir(),readdir(),closedir(),seekdir()
- stat()
- chmod(),chown(),utime()
- rename()
一、ls实现
- 简单的ls
- 就是读取目录并显示出来
- ls -l
- 这个需要文件信息,用stat函数获取。
- 用getgrgid根据gid获得组的信息,从而获得组名,用户名也是这样获取
二、三个特殊的位
- SUID
- 给程序额外的权限,例如修改密码会修改/etc/passwd,而这个文件有权限限制,就可以给程序一个特殊的权限,也就是SUID,使得这个程序的所有者变为root,这样就可以通过passwd程序修改密码了
- SGID
- 设置程序运行时所属组,获得组用户的权限
- sticky位
- 对于文件来说,指把程序放到交换空间,加快装载速度,不过现在不重要,用虚拟存储技术取代,用页交换
- 对目录而言,sticky使得目录里的文件只能被创建者删除,这对于临时文件夹是很有用的,比如/tmp
三、设置和修改文件属性
- 创建时可以指定model,create(filename,model),如0777,0代表八进制
- umask(022)设置掩码,创建只是请求,掩码是关闭某些权限
- 改变权限chmod(filename,model)
- 改变所有者和组 chown(filename,uid,gid)
- 修改时间 utime(filename,tt)
- 修改文件名 rename(old,new)
- 不同文件类型的文件创建方式不一样
第四章:文件系统:编写pwd
零. 相关命令和操作
- pwd
- mkdir(),rmdir(),chdir()
- link(),unlink(),rename(),symlink()
一、几种常用文件操作
-
- mkdir/rmdir
-
- link/unlink
-
- rename
-
- chdir
二、实现pwd
-
- 根据"."读取当前目录名
-
- 根据"…"跳到上一级目录,读取上一级目录名
-
- 直到根目录,根目录的".“和”…"都指向自己
- 注意:自己实现的pwd不能到 /目录,只能到"家"目录,因为这个家目录是分区后的文件系统的根,分区后,每个区有一个自己的文件系统树
三、装载点
-
- 如上所说,每个磁盘或分区有自己的文件系统树,但对用户来说感受不到,感觉像一个大树,就是把一个文件系统挂载到另一个文件系统的某个目录上,这个目录作为指针指向另一个目录的根
-
- Unix允许不同类别的文件系统挂载
第五章:连接控制:学习stty
零. 相关命令和操作
- stty,write
- fcntl,ioctl
- tcsetattr,tcgetattr
一、一切皆文件
- 设备也是文件,对文件的操作能用在设备文件上
- 终端也是文件
tty //查看当前终端名字
who > /dev/pts/1 //把信息输入到这个终端
- 设备文件的i-节点存储的是指向内核子程序的指针,而不是文件的大小和存储列表,内核中传输设备数据的子程序被称为设备驱动程序
- 在/dev/pts/1这个例子中,在终端进行数据传输的代码是在 设备-进程表 中编号为136的子程序,该子程序接受一个参数,参数为1。
136 和 1 被成为设备的主设备号和从设备号,主设备号确认处理该设备的子程序,从设备号作为参数传到子程序
二、磁盘链接的属性
-
- 实际上就是对磁盘文件的属性控制
- 获取设置
- 修改设置
- 存储设置
- fcntl(fd,cmd)
//一个设置文件为自动添加模式的例子 s=fcntl(fd,F_GETFL); //get flags s|=O_APPEND; //set APPEND bit result=fcntl(fd,F_SETFL,s) //set flags if(result==-1){ perror("..."); } write(fd,buf,1); //在结尾写入 //为什么不用lseek定位到结尾,再写入? 答:因为若有两个程序同时要写入,两个都调用了lseek时定位到同一位置,那么后写入的就会覆盖前一个写入的。 而设置标志位后这个动作就会变为原子操作。
- 也可以在open的时候指定文件操作
-
- 磁盘链接文件通过open,fcntl控制链接属性
三、终端链接的属性
-
- 处理进程和外部设备数据流的内核子程序被称为终端驱动程序或tty驱动程序。驱动程序包含很多控制设备操作的设置。进程可以读、修改和重置这些驱动控制标志。
-
- stty命令,读取和修改终端驱动程序的设置
四、编写终端驱动程序
-
- 驱动程序的操作
- 输入
- 输出
- 控制
- 本地
-
- 编写步骤
- 从驱动程序获得属性
- 修改索要修改的属性
- 将修改过的属性送回驱动程序
- 使用tcgetattr/tcsetattr
-
- 其他设备编程
- ioctl() 每个设备文件都支持系统调用ioctl
-
- 获取鼠标信息,cat /etc/input/mouse1 显示鼠标的操作信息,但是全是乱码。。。
第六章:为用户编程:终端控制和信号
零. 相关命令和操作
- fcntl,signal
一、终端模式
-
- 规范模式
也成为cooked模式,驱动程序输入的字符保存在缓冲区,可以进行退格等操作。
- 规范模式
-
- 非规范模式
缓冲和编辑功能关闭,退格作为一个输入符号,不能再编辑已经输入过的内容
- 非规范模式
-
- 规范模式要按下回车键程序才读取输入的内容,而非规范输入实时读取,在写需要输入 y/n的程序时很有用
二、信号
-
- 信号的三个来源:用户,内核,进程
-
- 自己的程序最好写个进程杀死信号的处理函数,把修改的设置恢复为默认的
-
- SIGSTOP和SIGKILL不能被捕捉和忽略,Ctrl-C是SIGINT信号
第七章:事件驱动编程:编写一个视频游戏
零. 相关命令和操作
- alarm,setitimer,getitimer
- kill,pause
- sigaction,sigprocmask
- fcntl,aio_read
一、curses入门
-
- curses是一个基于字符的图形界面库
-
- reflesh() 刷新界面
curses内部有虚拟和实际屏幕,虚拟屏幕来对内容进行改动。
refresh()比较真实屏幕和虚拟屏幕的差异,然后只改动不一样的地方。
- reflesh() 刷新界面
-
- 这种只传输改变的内容而不是影响本身的技术被用在视频流中。
二、时钟编程
-
- 计时器
- sleep()
- alarm()
-
- 间隔计时器
- getitimer/setitimer 函数
- itimerval/timeval 结构
三、信号处理
-
- 早期的信号处理机制–捕鼠器模型
- 默认:signal(SIGALRM,SIG_DEF);
- 忽略:signal(SIGALRM,SIG_IGN);
- 调用函数:signal(SIGALRM,handler);
-
- 处理多个信号
- signal会有问题,只能捕捉一次,使用完就失效了,还得在handler中再设置一次signal,像一个捕鼠器。但是若还没设置的时候就又出现了这个信号呢?早期的信号处理有这个Bug
- 总之signsl有很多问题,然后就有了sigaction
struct siaaction{ void (*sa_handler)(); void (*sa_sigaction)(int,siginfo_t*,void*); sigset_t sa_mask; int sa_flags; } //sa_handler是用老信号处理,如SIG_IGN等 //通过sa_mask设置是使用老的还是新的
四、防止数据损毁
-
- 临界区:若修改某个数据结构的代码在运行期间被打断将导致数据的不完整或摧毁,则称这段代码为临界区。要设法保护这段代码。
-
- 阻塞信号
- 在信号处理者那里阻塞信号:在处理一个信号的时候阻塞另一个信号,要设置sa_mask位,注意是阻塞而不是忽略
- 一个进程的阻塞信号:在任何时候一个进程都有一些信号被阻塞,这个信号集就被称为信号挡板(signal mask),通过sigprocmask可以修改这个阻塞信号集。
- 用sigsetops构造信号集
sigemptyset(sigset_t* setp);//清空setp sigfillset(sigset_t* setp);//将所有信号加到setp sigaddset(sigset_t* setp,int signum);//将signum信号加到setp sigdelset(sigset_t* setp,int signum);//将signum信号从setp删除
- 例子:暂时的阻塞用户信号
sigset_t sigs,prevsigs; sigemptyset(&sigs); sigaddset($sigs,SIGINT); sigaddset($sigs,SIGQUIT); sigprocmask(SIG_BLOCK,&sigs,&prevsigs); //dosomething sigprocmask(SIG_BLOCK,&prevsigs,NULL);
-
- 向另一个进程发送信号 kill
五、异步IO
-
- 使用O_ASYNC
- 用fcntl 对文件操作符设置O_ASYNC属性
- 当有键盘输入时,内核向进程发送SIGIO信号
-
- 使用aio_read
- aiocb //an aio control buf 结构体
- aio_read(&kbcbuf);
第八章:进程和程序:编写命令解释器sh
零. 相关命令和操作
- fork,exec,wait,exit
- sh,ps
一、进程
-
- 查看进程
ps -la //list all ps aux //查看所有进程,进程所属用户
-
- shell主循环
while(! end_of_input){ get command; execute command; wait for command finish; }
-
- exec**命令 一个进程调用另一个进程
-
- fork(),vfork()创建进程
-
- wait(pid) 等待子进程结束
-
- 退出exit(),atexit(),向父进程发送SIGCHLD
第九章:可编程的Shell,shell编程和环境:编写自己的shell
零. 相关命令和操作
- exit,getenv
- env
一、shell脚本
-
- 元素:变量,IO,控制语句,环境
-
- 讲真的,这个自写shell解释器是有点东西的
-
- 环境变量,全局/etc/profile,个人.bashrc
第十章:IO重定向和管道
零. 相关命令和操作
- dup,dup2
- pipe
一、标准文件描述符
-
- 标准输入:stdin 0
-
- 标准输出:stdout 1
-
- 标准错误输出:stderr 2
-
- 命令行里面的重定向,是把标准输出文件描述符1 重定向到自己指定的文件
二、重定向
-
- close-open
-
- open-close-dup-close
-
- open-dup2-close //dup2会自己关闭老的文件描述符
三、管道
-
- pipe(array),父子进程传送,匿名管道
-
- 管道非文件,类似文件却不同
1. 从管道读数据
(1)管道无数据时阻塞
(2)当所有写者关闭写端,读管道的调用返回0
(3)多个读者会出问题,因为数据读一次就没了,不像文件一直存在
2. 向管道写数据
(1)若管道空间不够,写入阻塞
(2)写入必须保证一个最小块的大小,如512字节
(3)若无读者存在,则写失败,返回-1
第十一章:连接到近端或远端的进程:服务器与Socket
零. 相关命令和操作
- fdopen,popen,socket,bind,listen,accept,connect
一、三种传输数据的方式
-
- 文件 open-write-read
-
- 管道 pipe-fork-read-write
-
- socket socket-bind-listen-accept
socket-connect
- socket socket-bind-listen-accept
二、bc:Unix中使用的计算器
-
- bc只处理输入,计算是由另一个dc程序实现的,他俩要进行通信
-
- bc的思想
- 客户/服务器模型
- 双向通信
- 永久性服务,dc只有一个,bc可以有很多个
bc/dc成为协程,当一个程序完成自己的工作后把控制权交给另一个程序
-
- 编写bc
- pipe
- fork
- dup
- exec
三、看似文件
-
- fdopen 让文件描述符像文件一样使用
以文件描述符作为参数而不是文件名,返回FILE*类型
- fdopen 让文件描述符像文件一样使用
-
- popen 让进程看似文件
- 参数为程序,返回FILE*
- 要用pclose()关闭,其中调用了wait()等待进程关闭
-
- popen 函数实现
- 用管道
- 用fdopen返回读管道文件
四、访问数据
-
- 文件读写
-
- 函数:应用程序API
-
- 从进程获取数据,可以用不同的语言编写
五、telnet
- telnet ip port
- 我自己的经验…可以用于测试端口是否可用,或者自己写程序的测试
六、时间服务器
-
- 处理连接,把时间信息发送过去
-
- socket-bind-listen-accept-传数据-close
-
- 可以用 telnet ip port 获得结果,也可以自己写客户端
-
- 客户端:socket-connect-read/write-close
七、远程的ls命令
-
- 登录远程服务器用ls命令
-
- 本地用远程的ls命令,需要写服务器
-
- 可在远程写个接受shell命令的服务器,调用popen从进程中读取数据返回到客户端,但是注意rm等命令不能随意用
八、软件精灵/守护进程
-
- 如httpd,syslogd等,这里的d是精灵(daemon)的意思,也叫守护进程
第十二章:连接和协议:编写Web服务器
一、服务器的三个核心步骤
-
- 服务器设立服务
-
- 客户端连接到服务器
-
- 服务器和客户处理事物
二、服务端和客户端的构造
-
- make_server_socket(port)
- 封装socket,bind,listen
-
- connect_to_server(hostname,port)
- 封装socket,connect
-
- 子进程结束发送,SIGCHLD信号,可以自己捕捉处理,避免僵尸进程的产生,也可以用wait,但是wait有问题,比如有多个子进程同时结束,wait来不及同时处理从而产生僵尸进程,可以用waitpid代替
三、Web的三种基本用户操作
-
- 请求目录,列举目录信息
-
- 文件请求,返回文件内容
-
- cgi文件,运行程序
-
- 返回错误消息
四、设计Web服务器
-
- 建立服务器
-
- 接收请求
-
- 读取请求
-
- 处理请求
-
- 发送应答
五、HTTP协议
-
- 客户发送请求
GET filename HTTP/version
可选参数
空行
-
- 服务器发送应答
HTTP/version status-code status-msg
附加信息
空行
内容
第十三章:基于数据报的编程:编写许可证服务器
零、相关命令和操作
- socket
- sendto,recvfrom
一、许可证服务系统
-
- 用户想使用程序
-
- 程序向许可证服务器请求是否可用
-
- 若可用,获得许可证,从而使用
-
- 不可用,提示
-
- 若是本地服务器,票据格式可以是进程pid+信息
二、数据报,UDP
TCP | UDP |
---|---|
流 | 数据报 |
分片/重组 | 否 |
排序 | 否 |
可靠地 | 可能未到达 |
连接的 | 多个发送者 |
有连接操作,较慢 | 无握手,较快 |
三、许可证服务器改进
-
- 客户端崩溃怎么办
- 造成的问题:没有释放票据,一直占用着,其他客户也不能用
- 解决办法:服务器端调度收回票据,隔一段时间问客户还用不用,不用的话就收回
- 注意:在处理请求的时候最好关闭调度,也就是忽略alarm信号,防止破坏数据的一致性
-
- 服务器崩溃
- 造成的问题:签出列表丢失,失去进程持有票据的记录
- 解决办法:1. 票据验证,客户周期性的向服务器发送票据副本
2. 协议中增加验证
四、分布式许可证服务器
会出现的问题
-
- 因为票据标示是进程pid,在不同的主机上可能有相同的pid,发生矛盾,可以扩展票据表项的格式,增加主机标识
-
- 回收票据,是通过kill(pid,0)回收的,而不同主机服务器不能给客户端发送信号,可以每个客户端都运行服务器实例。但是又会有新问题。kill(pid,0)是退出信号
-
- 主机崩溃,若其中一台崩溃,会发生什么?主服务器还在运行的话,如何回收票据?客户端如何验证票据呢?如果主服务器崩溃,谁来分发票据?有如下解决办法:
- 客户端服务器和中央服务器通信
- 每个客户都和中央服务器通信
- 客户服务器和客户服务器通信,没有中央服务器
五、Unix域socket
-
- 文件名作为socket地址
-
- 就是本地socket通信
六、总结
socket | PF_INET | PF_UNIX |
---|---|---|
SOCK_STREAM | 连接的,跨机器 | 连接的,本地 |
SOCK_DGRAM | 数据报,跨机器 | 数据报,本地 |
第十四章:线程机制:并发函数的使用
零、相关命令和操作
- pthread_create,pthread_join
- pthread_mutex_lock,pthread_mutex_unlock
- pthread_cond_wait,pthread_cond_signal
一、线程
-
- pthread_create 绑定线程函数
-
- pthread_join 等待线程结束
-
- pthread_mutex_lock/unlock 互斥量访问资源,比如多个线程需要修改一个变量,就在修改前后加解锁,不过加锁后速度降低,也可以线程单独开一个变量,处理完返回,看情况。
二、线程和进程
-
- 线程是共享进程的内存,所以要考虑很多问题。如下几个问题
-
- 共享的数据空间
- 一个线程中malloc,而另一个线程free
-
- 共享的文件描述符
- 若一个文件关闭了某个文件描述符,则对于其他的线程来说也不能用这个文件描述符了
-
- fork,exec,exit,signals
- 某线程fork,则只有该线程在新进程中运行,其他线程不会
- 某线程exit,则其他线程全部退出
- 某线程exec,则其他线程全部退出,新的程序取代当前程序
- 对于signal,进程可以接受任意信号,对于线程呢?这个可以看其他书学习
三、线程间互通消息
-
- 使用条件变量
- pthread_cond_signal(cond),通过条件变量发送消息,若没有wait,什么都不发生,若有多个wait,只响应一个
- pthread_cond_wait(cond,mutex),等待其他线程发出signal条件变量消息,此函数总是和互斥锁一起使用,改函数解开锁,并挂起等待,若没有被锁,则执行结果不确定
四、多线程Web服务器
-
- 把fork换成pthread_create
-
- 独立线程
- 一般线程需要pthread_join来等待线程结束回收资源,然而有些线程不用等待返回,但还得释放资源,可以在create时声明,称之为独立线程
- 3.Web服务器不需要等待处理请求的线程返回,所以可以设置为独立线程
五、总结
-
- 可以通过线程可以实现复杂的动画
-
- 通信方式
- 进程通信通过:管道,文件,socket,信号
- 线程:锁,信号量,文件,信号
第十五章:进程间通信(IPC)
零、相关命令和操作
- select,poll
- mkfifo
- shmget,shmat,shmctl,shmdt
- semget,semctl,semop
- talk,lpr
一、从多个数据源读取数据
-
- talk,直接read/write,设置read非阻塞,但是这样极其占用资源,因为调用read要进行内核切换。
-
- select系统调用
- 获得算需要的文件描述符列表
- 将此列表传给select
- select挂起直到任何一个文件文件描述符有数据到达
- select设置一个变量中的若干位,用来通知你哪一个文件描述符已经有输入的数据
-
- poll和select类似
二、通信的选择
-
- 进程间通信的三种方式
- 文件
- 命名管道 mkfifo/unlink/open/write/read,和文件不同的是,从管道读取数据后,数据就没了。
- 共享内存
- 共享内存段不依赖进程的存在而存在
- 共享内存段有自己的名字,称为关键字(key),是个整型
- 共享内存段有自己的拥有者和权限位
- 进程可以连接到某共享内存段,并且获得指向此段的指针
- 操作
-
- 得到共享内存段,shmget(…)
-
- 进程连接共享内存,shmat(…)
-
- 读写交互,通用的对指针的读写
-
- 特点
-
- 多个客户,可以多个客户读取共享内存段
-
- 避免竞态条件,使用信号量
-
-
- 各通信方式的比较
- 速度
- 通过文件或命名管道需要更多的操作。
- 但是共享内存机制同样也包括了对磁盘的读写,因为虚拟内存系统允许用户空间(内存)中的段交换到磁盘上
- 连接和无连接
- 无连接:共享内存,文件,数据报socket
- 连接:fifo,流socket
- 范围
- 父子进程通信:无名管道
- 本机进程通信:共享内存,命名管道,本地socket
- 远程主机通信:文件,ip socket
- 访问限制
- ip socket没有访问权限限制
- 其他基本都有文件系统权限限制
- 竞态条件
- 问题:一个读,另一个写,会有问题
- 解决:锁和信号量
三、进程间的分工合作
-
- 文件锁
- 读数据锁:其他人可以读,不能写
- 写数据锁:其他人不能操作
- 通过fcntl设置
- 2.信号量
- 创建信号量集:semget
- 将所有信号量置0:semctl
- 执行操作:semop
- 删除信号量:semctl
四、打印池
-
- 多个写者,一个读者,例如打印机
-
- 客户/服务器模型
- 客户只把要打印的数据发送到服务器
- 服务器对要打印的数据进行处理,然后放到打印队列中