最近发现只有算法基础,对底层的东西可以说是很不了解,所以看了一遍用来补下基础,惭愧啊…
1.消除不必要的内存引用 P354
比如对函数外部引用的变量累加,可以改成使用局部变量,就不会每次都从内存读取和写入了
2.C语言链接代码时候有强符号和弱符号区别 P471
强符号不能重复定义,而弱符号则可以,可能会导致弱符号链接到了强符号或者任意一个其他同名的弱符号。
3.同一种操作系统信号只会收到一次,不同信号之间可能会有并发问题 p527
4.虚拟内存 P560
每个进程都有自己的地址空间,申请内存时内核会和实际内存的虚拟地址映射(按照页面由CPU翻译成物理地址,缓存在内存和高速缓冲区),并且由内核保护了内存,
5. 文件 P635
每个进程都有自己的文件描述符(0开始),系统有一份打开文件表(所有进程共享),还是一份实际的vnode(所有进程共享),当一个进程打开同一个文件两次时会生成两个文件描述符,但是都指向同一个文件打开表中的一项,只是它的引用计数+1,当引用数位0时系统会回收文件打开表中的这项。
uinx I/O 函数(比如read(), write,open,close等)每次调用都是一次系统调用,所以可以用带缓存的函数减少系统调用,网络IO则看情况。
6.多路复用 P684
select()
只有一个函数,提供一个fdset,每一位检查一个文件描述符,每次有任意一个文件描述符从内核复制完数据时都会取消阻塞,且在每次调用时都要把文件描述符表赋值到内核,以及从内核传递回来,还需要遍历全部文件描述符来确定那些文件描述符准备完了,并且只支持1024个文件描述符。
epoll(书中没说)
就避免了select的上述三个问题,提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。
每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
epoll通过回调函数以及维护红黑树,并且插入就绪list链表,使得每次唤醒不再需要遍历全部注册文件描述符。所以也就没有了1024个的限制。
7.信号量 P698
互斥锁和信号量
信号量P(s) 和V(s)
P(s) -1,为0时挂起阻塞
V(s) +1,为了重启任意一个P(s)
互斥锁是只有0-1值的信号量,但是信号量还可以是很大数值的,可以用来实现消息队列,同步消费者和生产者(调度共享资源P705)。
读写锁
读优先:
用一个readcnt统计当前在临界区的读者数量,并且一个互斥量mex保护修改,并且有另外一个互斥量w保护不被写者并发。
当第一个进入临界区的读者锁住w,最后一个出临界区的读者才解锁w,保证所有读者完成后才触发写者,极端情况可能会导致写者永远无法被触发。
8.rand等需要依赖上一次结果的函数的线程安全问题 P716
rand()需要依赖上一次的结果来生成下一次值,这类函数的线程安全需要靠修改函数使得在一个线程内保存需要的上下文,我认为这里作者的意思主要是想要获得一段固定的随机数序列的时候,标准库做不到每个线程内都获得相同的序列,只能修改函数使得可以传入上次结果,这样每个线程维护自己的上一次结果就能获得相同的序列了。不然直接加锁只能做到全局序列整体递增,但在每个线程都需要完全随机时变得有问题(每个线程抢占到全局序列的一个子集,失去了随机分布性,如果能保证整个进程内只有你调用rand并且加锁这样似乎能做到全局随机?但是你能保证别的库就没有调用过?)。