Outline for Linux高编教程.docx
- 文档编号:10966498
- 上传时间:2023-02-24
- 格式:DOCX
- 页数:53
- 大小:123.36KB
Outline for Linux高编教程.docx
《Outline for Linux高编教程.docx》由会员分享,可在线阅读,更多相关《Outline for Linux高编教程.docx(53页珍藏版)》请在冰豆网上搜索。
OutlineforLinux高编教程
文件编号
日期
OutlineForLinux
作者:
吴万港
完成日期:
2013-5-11
签收人:
签收日期:
修订页
编号
修订内容简述
修订日期
修订后版本号
修订人
审核人
批准人
1
创建
2013-5-11
V1.0.0
吴万港
2
加入第二讲
2011-7-19
V1.0.1
吴万港
目录
1第一讲I/O编程文件编程4
1.1概述4
1.2Glibc简介4
1.2.1Glibc的功能实现4
1.2.2Glibc的内容4
1.2.3Glibc规格5
1.3标准I/O库6
1.3.1引言6
1.3.2流和FILE对象6
1.3.3标准输入、输出、标准出错6
1.3.4缓冲7
2第二讲多线程7
2.1线程概念7
2.2进程和线程的区别8
2.3线程标识9
2.4创建线程9
2.5线程终止10
2.6线程同步12
2.6.1互斥量13
2.6.2死锁14
2.6.3读写锁15
2.6.4条件变量16
2.7线程控制18
2.7.1线程属性18
2.7.1.1pthread_attr_t结构18
2.7.1.2分离状态19
2.7.1.3线程的继承性20
2.7.1.4线程的作用域22
2.7.1.5线程堆栈的地址22
2.7.1.6线程堆栈的大小23
2.7.1.7警戒缓冲区23
2.7.1.8总结24
2.7.2线程同步属性24
2.7.2.1互斥量属性24
3附录A24
3.1打印线程ID24
3.2获取线程退出状态26
3.3使用互斥量保护数据结构29
3.4死锁问题31
3.5创建分离状态的线程32
1
第一讲I/O编程文件编程
1.1概述
I/O即Input/Ouput的缩写,就是我们平时所说的输入和输出。
在Linux系统中,我们最熟悉的输入和输出就是设备就是屏幕和键盘。
那么这些输入输出设备在linux操作系统中是如何表示的?
还有没有其他的可以表示为I/O的设备?
1.2Glibc简介
glibc是GNU发布的libc库,是linux系统中最底层的api,几乎其他任何运行库都依赖于glibc。
1.2.1Glibc的功能实现
1.string,字符串处理
2.signal,信号处理
3.dlfcn,管理共享库的动态加载
4.direct,文件目录操作
5.elf,共享库的动态加载器,也即interpreter
6.iconv,不同字符集的编码转换
7.inet,socket接口的实现
8.intl,国际化,也即gettext的实现
9.io
10.linuxthreads
11.locale,本地化
12.login,虚拟终端设备的管理,及系统的安全访问
13.malloc,动态内存的分配与管理
14.nis
15.stdlib,其它基本功能
1.2.2Glibc的内容
以glibc-2.2为例,档案主要包括:
1.分享式库函数:
这是glibc的主体,分布于/lib与/usr/lib两个目录中,包括标准c函数库、libm数学函数库、libcrypt加密与编码函数库、libdb资料库函数库、libthread多线程函数库、libnss网络服务函数库等等。
这些函数库都是以.so文件结尾。
/lib/ld*.so是应用程序与函数库连结的工具。
有的用于应用程序编译时将程序与函数库内的函数相连结,在只支援静态链接的系统中,此连结方式就是直接将所需的函数从函数库中抽出与程序的可执行部分相连,而在支援可分享函数库的系统中,在程序编译时期的连结只是在执行中纪录了那些函数是存在那个函数库中,等该程序开始执行时,则由另一个负责动态连结的ld*.so将所需的函数库连结好。
要使用这些函数库,在用用程序中,必须首先将函数库对应的头见文件include到应用程序中,这些都文件全部以.h作为结尾,全部在/usr/include目录下,在这些头文件中实现各种函数、变量、数据类型等的定义。
一般可以采用man函数的形式获取系统中对于此函数使用说明。
这些说明文件一般存放在/usr/man或/usr/share/man等目录下面。
2.时区资料库
主要分布在/usr/share/zoneinfo目录下,含有世界各地时区与格林威治时间的转换资料。
1.2.3Glibc规格
3.ISOC
ISOC是InternationalStandardfortheCprogramminglanguage的缩写,此标准明定了C语言的语法,标准C函式库应具备那些标头档、巨集定义、函式与物件....等等,几乎在任何平台上的C语言(包括非UNIX平台)都支援此标准。
4.POSIX
POSIX是PortableOperatingSystemInterfaceforComputerEnvironments的缩写,它是ISOC的延伸,明定了一个可移植的作业系统所应具备种种条件,其范围不只有系统函数库而已,还同时包括一些标准的工具程序、系统核心应有的特色、以及在C函数库中某些与作业系统相关的低阶控制支援(如系统呼叫窗口)等等。
由于glibc是完全按照POSIX的标准制作的,同时搭配了符合POSIX标准的Linux核心,故在此环境下开发的程式可以做到完全符合POSIX的规格。
5.BerkeleyUnix
BerkeleyUnix泛称柏克莱大学所开发的UNIX系列作业系统,包括4.2BSD、4.3BSD、4.4BSD以及早期的SunOS。
这些系统的C函式库中有许多杰出的设计,但却没有在上述两个标准中,包括select()函式、sockets....等等,这些在glibc中都有支援。
6.SVID
SVID是SystemVInterfaceDescription的缩写,它是一份描述AT&TUNIXSystemV系统规格的文件,它是POSIX标准的延伸。
Glibc?
作了大部分的SVID规格要求,其中较重要的包括了行程之间的通?
标准以及分享式内存(sharedmemory),至于其他的部分则较不常使用。
?
作SVID主要的目的是希望可以做到与UNIXSystemV的相容与程式的可移植性
7.XPG
XPG是X/OpenPortabilityGuide的缩写,是由X/OpenCompany,Ltd.所发表,同时X/Open还拥有UNIX商标的版权。
而这份规格不但是POSIX标准的扩充,同时也明定了一个UNIX作业系统所应符合的要求。
其中包括了iconv()字集转换介面,以及部分BSD与SVID的特色。
1.3标准I/O库
1.3.1引言
本章说明标准的I/O库。
由于是标准的文件操作I/O库函数,因此不仅仅在linux,而且在很多操作系统上都实现了该库,因此它由ISOC标准说明。
1.3.2流和FILE对象
在Unix/Linux中,文件对象都是用文件描述符来表示的,当打开一个文件时,返回一个文件描述符,然后所有针对此文件描述符的操作都是针对对应文件的操作。
而对于标准的I/O库,它们的操作则是围绕流(stream)进行的。
当用标准I/O库打开或创建一个文件时,我们已经使得流和一个文件相关联,此后针对此流的操作都是对对应文件的操作。
在标准I/O库中,打开或创建流后用FILE对象来表示。
1.3.3标准输入、输出、标准出错
在Unix/Linux中任何进程都定义了三个流,即输入、输出、标准出错。
任何进程都可以自由的使用它们,且Unix/Linux已经为我们定义了表示这三个流的宏:
⏹STDIN_FILENO
⏹STDOUT_FILENO
⏹STDERR_FILENO
用户应用程序中预定义的文件指针为stdin、stdout、stderr,这三个文件指针同样被定义在
1.3.4缓冲
标准I/O库提供缓冲的目的是尽可能的减少读写的次数,因此它对每个I/O流自动的进行缓冲管理,从而避免应用程序需要考虑的这点所带来的麻烦。
不幸的是,标准I/O库最令人迷惑的也是它的缓冲。
标准I/O库提供三种类型的缓冲:
⏹全缓冲,在填满标准缓冲区后才进行实际的I/O操作。
对于驻留在磁盘上的文件通常是由标准i/o库实施全缓冲的。
⏹行缓冲,当输入和输出中遇到换行符时,标准i/o库执行i/o操作。
遇到一个终端时,通常采用行缓存。
⏹不缓冲。
2第二讲多线程
2.1线程概念
进程是Linux/Unix操作系统中最小的执行单元,任何进程都有最少一个线程,而且每个进程有且只有一个主线程,在进程执行过程中,主线程若退出,则意味着进程退出。
典型的Unix/Linux进程可以看作只有一个控制线程:
一个进程在同一时刻只做一件事情。
有了多线程以后,在程序设计时可以在一个进程中加入多个线程,让每个线程执行不同的任务,这样,一个进程就可以同时执行更多的任务了。
有些人将多线程和多CPU联系起来,即使在单CPU的处理器上运行进程,也能带来一些好处。
处理器的数量并不影响程序结构,因此不管处理器个数,程序可以通过使用多线程得以提高程序的执行效率。
而且,即使多线程程序在串行化任务时不得不阻塞运行,但是还有其他一些线程可以执行,所以多线程程序在单处理器上运行仍然能够改善响应时间和吞吐量。
多线程主要可以带来以下好处:
1.通过为每个任务分配单独的线程,能够简化处理异步事件的代码。
每个线程在进行事件处理时可以采用同步变成模式,同步面呈模式要比异步编程模式简单的多。
2.多个进程间必须使用操作系统提供的复杂机制才能实现内存和文件描述的共享,而在多个线程间可以相互访问相同的存储地址空间和文件描述符。
3.有些较为大型的人物可以被分解成多个较小的任务,从而改善程序的吞吐量。
在只有一个控制线程的情况下,单个进程需要完成多个任务时,实际上需要把这些任务串行化;在多个线程的情况下,相互独立的任务处理就可以交叉进行,只需要为每个任务分配一个单独的线程,当然只有在处理过程互不依赖的情况下,多个任务才可以同时进行处理。
4.交互的程序同样可以通过使用多线程实现响应时间的改善,多线程可以把程序中处理用户输入输出的部分与其他部分分开。
多线程带来的编程挑战:
1.以往串行化的编程方式被改变,程序的执行编程并行化,不管是在单CPU还是多CPU结构上。
2.由于程序串行化执行,以为这多个线程可以在同一时间执行多个任务,如果多个任务共享某存储内存,则必须注意在多个线程间存储空间中资源保护和竞争的问题,这些都将给代码编写带来新的挑战。
3.由于多个线程间共同访问某存储空间,容易引起线程死锁等问题。
2.2进程和线程的区别
线程的划分尺度小于进程,使得多线程程序的并发性搞。
另外,进程在执行过程中拥有独立的内存单元,而线程并没有自己独立的内存空间,而是和其所属的进程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。
但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。
但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。
这就是进程和线程的重要区别。
进程(Process)是最初定义在Unix等多用户、多任务操作系统环境下用于表示应用程序在内存环境中基本执行单元的概念。
以Unix操作系统为例,进程是Unix操作系统环境中的基本成分、是系统资源分配的基本单位。
Unix操作系统中完成的几乎所有用户管理和资源分配等工作都是通过操作系统对应用程序进程的控制来实现的。
2.3线程标识
操作系统对进程的管理是通过进程ID来实现的,线程也和进程一样,每个线程也有一个线程ID,进程ID在操作系统中是唯一的,但是线程不同,线程ID只在其所属的进程中有效。
进程ID是用pid_t数据类型来表示的,是一个非负整数。
而线程ID使用pthread_t数据类型表示,在程序运行时被看成是一个结构体,因此不能作为整数来处理。
使用结构体来表示pthread_t数据类型的后果就是不能用一种可移植的方式来打印数据类型的值,因为有时候打印线程的ID是非常有用的。
线程可以通过以下函数获取自身的pthread_t值。
#include
pthread_tpthread_self(void);
返回值:
调用线程的线程ID
有时候需要比较两个线程的ID,检查是否是同一个线程,或者指定的工作是否交给响应的线程执行。
#include
intpthread_equal(pthread_ttid1,pthread_ttid2);
返回值:
若相等返回非0值,否则返回0
2.4创建线程
在传统的Unix/Linux进程模型中,每个进程只有一个控制线程。
在POSIX线程环境下,程序开始运行时,它也是以单进程中的单个控制线程启动的,在创建多个线程以前,程序的行为和进程并没有什么区别。
新创建一个线程可以使用下列函数:
#include
intpthread_create(pthread_t*restricttidp,
constpthread_attr_t*restrictattr,
void*(*start_rtn)(void),
void*restrictarg);
返回值:
若成功返回0,否则返回错误编号
当pthread_create函数成功返回时,由tidp指向的内存单元被设置为新创建的线程的线程ID。
attr参数用于定制各种不同的线程属性,如果希望线程以默认属性创建,则直接设置为NULL。
指点将在后文介绍,目前将其设置为NULL。
start_rtn指针为一个函数指针,新创建的线程从start_rtn指向的函数处开始执行。
arg为start_rtn指向的函数的参数,此函数有且只有一个void*类型的参数,如果要给start_rtn传递多个参数,则必须将多个参数放在一个结构体中,然后将此结构体传递给start_rtn函数。
注意pthread_create函数创建的线程,操作系统并不能保证哪个线程会优先于优先运行,这个知识点属于线程优先级和操作系统调度线程的问题,这里不做讲解。
参见实例13.1打印线程ID
2.5线程终止
如果进程中任何一个线程调用了exit,_Exit或者_exit三个函数中的任何一个,那么整个进程就会终止。
与此类似,如果信号的动作是终止进程,那么,把该型号发送到线程同样也会终止程序。
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下:
⏹线程从自己的例程函数中返回,返回值就是线程的退出代码。
⏹线程可以被同一进程中其他线程取消。
⏹线程可以调用pthread_exit。
#include
voidpthread_exit(void*rval_ptr);
rval_ptr是一个void*类型的指针,用来返回线程返回的值。
进程中其他线程还可以通过以下方式等待某个线程退出:
#include
intpthread_join(pthread_tthread,void**rval_ptr);
返回值:
成功则返回0,否则返回错误编码
调用pthread_join函数的线程一直阻塞,等到线程调用pthread_exit、从线程例程函数返回或者被取消。
如果线程从例程函数中返回,rval_ptr就是其返回值;若线程调用了pthread_exit函数,则其返回值被存放在rval_ptr指针中;如果线程是被取消的,其返回值为PTHREAD_CANCELED。
如果对线程的返回值不感兴趣,则将rval_ptr设置为NULL。
线程可以通过以下函数由一个线程取消另外一个线程:
#include
intpthread_cancel(pthread_ttid);
返回值:
成功则返回0,否则返回错误编码
在默认情况下,pthread_cancel函数会使得由tid标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit函数,但是,线程可以选择忽略取消方式或者控制取消方式,使得pthread_cancel仅仅只是提交了一个请求。
线程可以安排在线程函数退出时需要调用的函数,这与进程可以用atexit函数安排进程退出时调用的函数类似。
这种机制被称为线程清理处理程序(threadcleanuphandler)。
线程可以建立多个线程处理函数,处理程序被记录在栈中,就是说,他们的执行顺序与他们被注册的顺序是相反的。
#include
voidpthtread_cleanup_push(void(*rtn)(void*),void*arg);
voidpthread_cleanup_pop(intexecute);
当线程调用以下动作时调用清理函数,调用参数为arg,清理函数rtn的调用顺序是由pthread_cleanup_push决定的。
⏹调用pthread_exit时
⏹响应取消请求时
⏹非0参数调用pthread_cleanup_pop时
注意,如果线程是从例程函数返回的,不管是否为线程注册了线程清理函数,这些清理函数仍然不会被调用。
参见实例2获取线程退出状态
2.6线程同步
当做个线程共享相同的内存时,需要确保每个线程看到一致的数据视图。
如果每个线程使用的变量都是其他线程不会读取或修改的,那么就不存在一致性的问题了。
同样的,如果变量是只读的,多个线程同时读取该变量也不会有一致性的问题。
但是,当某个线程可以修改变量,而其他线程也可以读取或者修改此变量的时候,就需要对线程进行同步,以确保他们在访问变量的存储存储内容是不会访问到无效的数值。
为了解决多个线程间数据一致性的问题,在多线程环境下,不得不使用锁,确保在同一时间内只有一个线程能够访问该变量。
图1非同步线程对同一个数据的增量操作
图2两个线程同步内存访问
如果两个线程试图在同一时间对同一个变量进行增量操作而不进行同步的话,如图1所示,就有可能出现不一致。
变量可能比原来增加了1,也可能比原来增加了2。
如果修改操作如图2所示的那样,是原子性操作的话,那么i的内容肯定是7,不会出现不一致的情况。
这就是同步的作用。
在Unix/Linux操作系统中,线程间同步机制有如下几种机制:
⏹互斥量
⏹读写锁
⏹条件变量
⏹同步信号
2.6.1互斥量
互斥锁(Mutex)从本质上就是一把锁,在访问共享资源前对资源进行加锁,访问完成之后释放锁。
对互斥量进行加锁,任何试图再次对互斥量加锁的线程都会被阻塞,知道加锁的线程释放该锁。
如果有多个线程阻塞在此互斥量上,那么一旦加锁的线程释放该锁,则所有在该互斥量上阻塞的线程都变成可运行状态,第一个变为可运行状态的线程可以对该互斥量枷锁,即获取该锁,其他线程看到互斥量被重新锁住,则重新进入阻塞状态。
这种情况下,每次只有一个线程可以获取该锁,但是到底是哪个线程获取该锁,则不一定,完全由操作系统自行决定。
如果允许其中某个线程在没有得到锁的情况下也能访问资源,那么即使其他所有的线程在使用共享资源前都获取了锁,也会出现数据不一致的情况。
互斥量用pthread_mutext_t数据类型来表示,在使用互斥量前,必须先对其进行初始化,如下所示:
#include
intpthread_mutex_init(pthread_mutex_t*mutex,
constpthread_mutexattr_t*attr);
返回值:
成功为0,否则返回错误编码
互斥量被初始化之后,在不需要使用时,需要将其销毁,如下所示:
#include
intpthread_mutex_destroy(pthread_mutex_t*mutex);
返回值:
成功为0,否则返回错误编码
对互斥量进行加锁,使用完成之后释放锁,其系统函数如下所示:
#include
intpthread_mutex_lock(pthread_mutex_t*mutex);
intpthread_mutex_trylock(pthread_mutex_t*mutex);
intpthread_mutex_unlock(pthread_mutex_t*mutex);
返回值:
成功返回0,否则返回错误编码
如果线程在获取锁时不希望被阻塞,而是希望如果当前互斥量已经被上了锁了,则返回表示已经上了锁的错误码,如果互斥量还没有被上锁,则获取该锁,这种情况下,我们可以调用pthread_mutex_trylock函数。
如果被调用的互斥量已经被上锁,此时该函数返回EBUSY的错误码。
参见例程3使用互斥量保护数据结构
2.6.2死锁
如果线程试图对一个互斥量加锁两次,那么它自身就会进入死锁状态,在使用互斥量时,还有很多其他情况下会产生死锁。
例如,程序中使用多个互斥量,如果线程T1一直占用着互斥量A,并且试图锁住第二个互斥量B时处于阻塞状态,但是拥有互斥量B的线程T2也在试图获取互斥量A,这是就发生了死锁。
因为两个线程都在相互请求另一个线程拥有的资源,使得这两个线程都无法获取资源,继续往前运行,发生了死锁。
图3死锁图示
如何避免死锁呢?
如果涉及了太多了互斥量,我们可以使用函数pthread_mutex_trylock接口函数避免死锁。
参见例程4死锁问题
2.6.3读写锁
如果先获取互斥量锁的线程只是读取共享内存中的内容,另外的线程也是获取共享内存的内容,使用互斥量锁的情况下,其他线程也是处于阻塞等待状态,因此互斥量是一种独占锁。
读写锁是一种共享锁,有更高的并行性,因为如果所有的线程都是获取共享内存的内容的话,那么就可以使用读写锁来提高其并行性,如果有一个线程希望能够修改共享内存的内容,则读写锁,则先获取读写锁的线程会阻塞直到读或者写操作完成,此时读写锁表现的和互斥量是一样的行为。
读写锁非常适合于对数据读此书远远大于写次数的情况。
和互斥量一样,读写锁进行操作之前也需要初始化,不再使用时,则需要销毁。
如下所示:
#include
intpthread_rwlock_init(pthread_rwlock_
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Outline for Linux高编教程 Linux 教程