• UNIX高级环境编程1


    UNIX高级环境编程1

    故宫角楼是很多摄影爱好者常去的地方,夕阳余辉下的故宫角楼平静而安详。

    故宫角楼

    首先,了解一下进程的基本概念,进程在内存中布局和内容。

    此外,还需要知道运行时是如何为动态数据结构(如链表和二叉树)分配额外内存的。

    一 进程

    1 进程和程序

    进程:是一个可执行程序的实例。

    程序:包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程。包含如下信息:

    1. 二进制格式标识:如最常见的ELF格式。
    2. 机器语言指令:对程序算法进行编码。
    3. 程序入口地址:标识程序开始执行时的起始指令位置。
    4. 数据:程序文件包含的变量初始值和程序使用的字面常量值,如字符串。
    5. 符号表和重定位表:描述程序中函数和变量的位置及名称。
    6. 共享库和动态链接信息:程序文件中所包含的一些字段,列出了程序运行时需要使用的共享库,以及加载共享库的动态链接器的路径名。
    7. 其他信息。

    进程的再定义:进程是由内核定义的抽象的实体,并为该实体分配用以执行程序的各项系统资源。

    从内核的角度看,进程由用户内存空间和一系列内核数据结构组成,其中用户内存空间包含了程序代码及代码所使用的变量,而内核数据结构则用于维护进程状态信息。

    2 典型的进程内存布局

    NewImage

    每个进程所分配的内存由很多部分组成,通常称之为“段(segment)”。如上图所示:

    1. 文本段:包含进程运行的程序机器语言指令。文本段具有只读属性,因此多个进程可同时运行同一程序,共享文本段。
    2. 初始化数据段:包含显式初始化的全局变量和静态变量。当程序加载到内存时,从可执行文件中读取这些变量的值。
    3. 未初始化数据段(BSS段,block started by symbol):包含了未进行显式初始化的全局变量和静态变量。程序启动之前,系统将本段内所有内存初始化为0.所以又叫做零初始化数据段。
    4. 栈(stack):动态增长和收缩的段,由栈帧(stack frame)组成。系统会为每个当前调用的函数分配一个栈帧。栈帧中存储了函数的局部变量、实参和返回值。
    5. 堆(heap):在运行时为变量动态进行内存分配的一块区域。堆顶端成为程序中断(program break)
    将经过初始化的全局变量和静态变量与未经过初始化的全局变量和静态变量分开存放,其主要原因在于程序在磁盘上存储时,没有必要为未经过初始化的变量分配存储空间。相反,可执行文件只需记录未初始化数据段的位置及所需要大小,直到运行时再由程序加载器来分配这一空间。

    需要注意一点时,该内存布局的讨论是在虚拟内存中的,并不是物理内存中的布局。

    在后面会专门讨论虚拟内存的一些细节。

    二 内存分配

    1 在堆上分配内存

    堆:一段长度可变的连续虚拟内存,始于进程的未初始化数据段末尾,随着内存的分配和释放而增减。将堆的当前内存顶部边界称为“程序中断(program break)”

    program break是一个非常重要的概念,因为分配和释放内存的实际动作就是改变进程的program break位置。

    program break的起始位置(堆的大小为0)位于未初始化数据段末尾之后。

    细节:在分配新的内存后,program break位置升高,程序可以访问新分配区域内的任何内存地址,而此时物理内存页尚未分配。内存会在进程首次试图访问这些虚拟内存地址时自动分配新的物理内存页。

    函数malloc和free

    malloc函数声明

    1
    2
    #include
    void *malloc(size_t size);

    作用:在堆上分配参数size字节大小的内存。

    返回值:成功返回指向新分配内存起始地址的指针,失败返回NULL

    free函数声明 

    1
    2
    #include
    void free(void *ptr);

    
作用:释放ptr参数所指向的内存块,该参数应该是之前由malloc或者其他内存分配函数之一所返回的地址。

    需要注意的是:一般情况下,free并不降低program break的位置,而是将这块内存增加到空闲内存列表中,供后续的malloc函数循环使用。因为:

    • 被释放的内存块通常位于堆的中间,而非堆的顶部,因而降低program break是不可能的。
    • 它最大限度地减少了内核调用调整program break系统调用的次数。
    • 通常程序会持有分配的内存或者反复释放和重新分配,而不是释放所有内存再运行一段时间。

    仅当堆顶空闲内存“足够”大的时候,free函数的glibc实现会调用sbrk()来降低program break的地址,至于“足够”与否则取决于malloc函数包行为的控制参数(128KB为典型值)。这减少了必须对sbrk()发起的调用次数。

    malloc和free的实现

    malloc()的实现

    1. 扫描之前由free()所释放的空闲内存块列表,以求找到尺寸大于或者等于要求的一块内存
    2. 如果这一内存块的尺寸正好与要求相当,就把它直接返回给调用者。
    3. 如果是一块较大的内存,那么将对其进行分割,在将一块大小相当的内存返回给调用者的同时,把较小的那块空闲内存块保留在空闲列表。
    4. 如果在空闲内存列表中找不到足够大的空闲内存块,那么malloc会调用sbrk()以分配更多的内存,并且malloc会分配出比所需字节数更多的内存,将超出的部分置于空闲内存列表中。

    free()的实现

    首先先了解两点:malloc返回的内存块和空闲列表中的内存块的结构

    为了知道每一个内存块的大小,当malloc分配内存块时,会额外分配几个字节来存放记录这块内存大小的整数值。该整数位于内存块的起始处,而实际返回给调用者的内存地址恰好位于这一长度记录字节之后。如下图所示:

    NewImage

    为了管理空闲内存列表,free()会使用内存块本身的空间来存放链表指针,将自身添加到列表中。如下图所示:

    NewImage

    所以,在频繁地分配和释放内存之后,堆中的链表可能会变成下图的样子,空闲链表中的空闲内存会和已分配的在用内存混杂在一起。

    NewImage

    三 编程需要注意的事项

    通过对内存相关知识更多的了解,在平时编程的时候,应更清楚为什么我们需要遵守下面的规则。

    1. 分配一块内存后,不要改变这块内存范围外的任何内容。
    2. 释放同一块已分配内存超过一次是错误的。当两次释放同一块内存时,常见的后果是导致不可预知的行为。
    3. 若非经由malloc函数包中函数所返回的指针,绝不能在调用free()函数使用。
    4. 如果需要反复分配内存,那么应当确保释放所有已使用完毕的内存,不然将导致内存泄露。
     
    虽然在我们平时的工作当中,可能涉及不到这么底层的原理,但是通过对这些基本原理的了解,可以让我们更加清除,我们写代码究竟在写些什么 :)

    参考资料:

    《Linux/Unix系统编程手册(上册)》 第6章,第7章 

     
    摘要: 故宫角楼是很多摄影爱好者常去的地方,夕阳余辉下的故宫角楼平静而安详。首先,了解一下进程的基本概念,进程在内存中布局和内容。此外,还需要知道运行时是如何为动态数据结构(如链表和二叉树)分配额外内存的。一 进程1 进程和程序进程:是一个可执行程序的实例。程序:包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程。包含如下信息:二进制格式标识:如最常见的ELF格式。机器语言指令:对程序算法进行...阅读全文
    posted @ 2016-04-21 21:31 suzhou 阅读(176) | 评论 (0) 编辑
     
    摘要: 春天来了,除了工作学习,大家也要注意锻炼身体,多出去运动运动。上周末在元大都遗址公园海棠花溪拍的海棠花。进入正题。O_DIRECT和O_SYNC是系统调用open的flag参数。通过指定open的flag参数,以特定的文件描述符打开某一文件。这两个flag会对写盘的性能有很大的影响,因此对这两个flag做一些详细的了解。先看一个open函数的使用例子./* Open new or existi...阅读全文
    posted @ 2016-04-12 10:47 suzhou 阅读(137) | 评论 (0) 编辑
     
    摘要: 信号就是软中断。信号提供了异步处理事件的一种方式。例如,用户在终端按下结束进程键,使一个进程提前终止。1 信号的概念每一个信号都有一个名字,它们的名字都以SIG打头。例如,每当进程调用了abort函数时,都会产生一个SIGABRT信号。每一个信号对应一个正整数,定义在头文件中。没有信号对应整数0,kill函数使用信号编号0表示一种特殊情况,所以信号编号0又叫做空信号(null signal)。下...阅读全文
    posted @ 2015-05-08 18:49 suzhou 阅读(597) | 评论 (2) 编辑
     
    摘要: 在前面的章节我们了解到,进程之间是有关联的:每个进程都有一个父进程;子进程退出时,父进程可以感知并且获取子进程的退出状态。本章我们将了解:进程组的更多细节;sessions的内容;login shell和我们从login shell启动的进程之间的关系。一 终端登录(Terminal Logins...阅读全文
    posted @ 2015-04-02 21:22 suzhou 阅读(349) | 评论 (1) 编辑
     
    摘要: 1 进程快照(Process Accounting)当一个进程终止时,内核会为该进程保存一些数据,包括命令的小部分二进制数据、CPU time、启动时间、用户Id和组Id。这样的过程称为process accounting,本篇译为进程快照。函数acct可以打开或关闭进程快照功能。负责记录快照的数据...阅读全文
    posted @ 2015-04-01 10:52 suzhou 阅读(343) | 评论 (0) 编辑
     
    摘要: 本章包含内容有:创建新进程程序执行(program execution)进程终止(process termination)进程的各种ID1 进程标识符(Process Identifiers)每个进程都有一个唯一的标识符,进程ID(process ID)。进程的ID是可重用的,如果一个进程被终止,那么它的进程ID会被系统回收,但是会延迟使用,防止该进程ID标识的新进程被误认为是以前的进程。三个特...阅读全文
    posted @ 2015-03-18 22:45 suzhou 阅读(592) | 评论 (2) 编辑
     
    摘要: 在学习进程控制相关知识之前,我们需要了解一个单进程的运行环境。本章我们将了解一下的内容:程序运行时,main函数是如何被调用的;命令行参数是如何被传入到程序中的;一个典型的内存布局是怎样的;如何分配内存;程序如何使用环境变量;程序终止的各种方式;跳转(longjmp和setjmp)函数的工作方式,以及如何和栈交互;进程的资源限制1 main函数main函数声明:int main (int arg...阅读全文
    posted @ 2015-03-06 20:22 suzhou 阅读(362) | 评论 (2) 编辑
     
    摘要: 1 二进制IO(Binary IO)在前一篇我们了解了逐字符读写和逐行读写函数。如果我们在读写二进制文件,希望以此读写整个文件内容,这两个函数虽然可以实现,但是明显会很麻烦且多次循环明显效率很低。为了应对这种场景,标准IO库提供了fread和fwrite函数。函数声明:#include size_t fread(void *restrict ptr, size_t size, size_t nob...阅读全文
    posted @ 2015-03-02 16:53 suzhou 阅读(506) | 评论 (0) 编辑
     
    摘要: 标准IO函数库隐藏了buffer大小和分配的细节,使得我们可以不用关心预分配的内存大小是否正确的问题。虽然这使得这个函数库很容易用,但是如果我们对函数的原理不熟悉的话,也容易遇到很多问题。1 流和FILE实体(Streams and FILE Objects)前面的章节中,IO集中在文件描述符,每一个打开的文件都对应一个文件描述符,通过文件描述符对文件进行操作。现在使用了标准IO库,讨论的重点集...阅读全文
    posted @ 2015-02-28 10:48 suzhou 阅读(517) | 评论 (0) 编辑
     
    摘要: 1 File Times每个文件会维护三个时间字段,每个字段代表的时间都不同。如下表所示:字段说明:st_mtim(the modification time)记录了文件内容最后一次被修改的时间。st_ctim(the changed-status time)记录了文件的i-node最后一次被修改的时间,如修改文件权限位,修改文件所有者ID,修改关联到该文件的link数目。i-node中的信息和...阅读全文
    posted @ 2015-02-27 12:52 suzhou 阅读(392) | 评论 (0) 编辑
     
    摘要: 本篇主要介绍文件和文件系统中常用的一些函数,文件系统的组织结构和硬链接、符号链接。通过对这些知识的了解,可以对Linux文件系统有更为全面的了解。1 umask函数之前我们已经了解了每个文件与权限相关的9个位(bit),我们现在来了解一下当每个进程创建文件时默认会设置该文件的文件权限(the file mode creation mask)。umask函数设置该进程默认创建文件的权限掩码(the...阅读全文
    posted @ 2015-02-26 20:35 suzhou 阅读(505) | 评论 (0) 编辑
     
    摘要: 在前面的两篇,我们了解了IO操作的一些基本操作函数,包括open、read和write。 在本篇我们来学习一下文件系统的其他特性和一个文件的属性,涉及的函数功能包括:查看文件的所有属性;改变文件所有者;改变文件权限;操作文件夹。 我们还会了解一些文件系统相关的数据结构和符号链接(symbol...阅读全文
    posted @ 2015-02-17 22:52 suzhou 阅读(477) | 评论 (2) 编辑
     
    摘要: 引言:本篇通过对open函数的讨论,引入原子操作,多进程通信(共享文件描述符)和内核相关的数据结构。还会讨论集中常见的文件IO控制函数,包括:dup和dup2sync,fsync和fdatasyncfcntlioctl/dev/fd一、文件共享这里所说的文件共享主要指的是进程间共享打开的文件。这一节...阅读全文
    posted @ 2015-02-15 22:55 suzhou 阅读(497) | 评论 (0) 编辑
     
    摘要: 引言: Unix系统中主要的文件操作包括:openreadwritelseekclose unbuffered IO和standard I/O相对应,后面的章节我们会讨论这两者的区别。 在讨论open函数的时候,会引入原子操作,多进程通信(共享文件描述符)和内核相关的数据结构。一,文件描述符 ...阅读全文
    posted @ 2015-02-13 12:05 suzhou 阅读(635) | 评论 (0) 编辑
     
    摘要: 写的比较简略,主要是记录一下,以后配置别的机器的时候看一下,仅供参考。1 安装命令依赖包sudo apt-get install git-core git-gui git-doc2 设置SSH检查是否有~/.ssh文件夹cd ~/.ssh生成新的SSHssh-keygen -t rsa -C "your email"4 把SSH公钥放到github上,把id_rsa.pub复制进去5 测试ssh -T git@github.com可能是系统不一样,这次跳出来这种界面,不管了,输入刚才设置的密码。阅读全文
    posted @ 2014-04-08 09:12 suzhou 阅读(24) | 评论 (0) 编辑
     
    摘要: 最近163的源出问题了,又要换一次源。报错如下:查更新源的命令查了好多次,这次还是记下来吧,估计以后还会用到很多次。常规来说,是要先备份的,不过感觉备份也没什么用,所以就直接跳过吧。。1 打开文件 sudo gedit /etc/apt/sources.list2 添加源地址选一两个就好了,我一般是...阅读全文
    posted @ 2013-06-27 00:16 suzhou 阅读(32) | 评论 (0) 编辑
     
    摘要: 1 Ubuntu12.04内核升级1 准备条件:安装有Unbuntu12.04的机器或者虚拟机下载最新的稳定版Linux内核源码:下载地址是http://www.kernel.org/,现在最新的稳定版本是3.9.4root权限2 解压文件到/usr/src文件夹中因为我是从本地系统直接复制到虚拟机里的这个文件夹中,所以会出现权限问题。要先拷贝到别的权限较低的文件夹中然后在终端进入root权限进行移动。移动文件到指定文件夹解压文件:解压命令:xz -d linux-3.9.4.tar.xztar -xvf linux-3.9.4.tar第一条命令大概执行40秒到一分钟第二条命令的执行过程:..阅读全文
    posted @ 2013-06-02 21:05 suzhou 阅读(43) | 评论 (0) 编辑
    分类: 后台开发Linux
  • 相关阅读:
    bzoj 3339 莫队
    E. XOR and Favorite Number
    HDU 2222 AC自动机
    SPOJ 694 不同子串个数
    Codeforces Round #428 (Div. 2)
    HDU 6103
    《贪婪的动态规划》
    《浅谈图论模型的建立与应用》
    bzoj 2194 快速傅里叶之二
    java中高级面试题整理及参考答案
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/5437585.html
Copyright © 2020-2023  润新知