志在指尖
用双手敲打未来

linux系统编程(linux系统编程手册pdf)

linux系统编程

Linux体系一般有4个首要部分:
内核、shell、文件体系和应用程序。内核、shell和文件体系一同形成了基本的操作体系结构,它们使得用户能够运转程序、办理文件并运用体系。部分层次结构如图1-1所示。
1.linux内核
Linux内核是世界上最大的开源项目之一,内核是与计算机硬件接口的易替换软件的最低等级。它担任将一切以“用户形式”运转的应用程序连接到物理硬件,并答应称为服务器的进程运用进程间通讯(IPC)彼此获取信息。
内核是操作体系的中心,具有许多最基本功用,它担任办理体系的进程、内存、设备驱动程序、文件和网络体系,决定着体系的功用和稳定性。
Linux内核由如下几部分组成:内存办理、进程办理、设备驱动程序、文件体系和网络办理等。如图:
体系调用接口:SCI层供给了某些机制履行从用户空间到内核的函数调用。这个接口依赖于体系结构,甚至在相同的处理器宗族内也是如此。SCI实践上是一个十分有用的函数调用多路复用和多路分解服务。在./linux/kernel中您能够找到SCI的完结,并在./linux/arch中找到依赖于体系结构的部分。
1.内存办理
对任何一台计算机而言,其内存以及其它资源都是有限的。为了让有限的物理内存满意应用程序对内存的大需求量,Linux采用了称为“虚拟内存”的内存办理方法。Linux将内存划分为简单处理的“内存页”(关于大部分体系结构来说都是4KB)。Linux包括了办理可用内存的方法,以及物理和虚拟映射所运用的硬件机制。
不过内存办理要办理的可不止4KB缓冲区。Linux供给了对4KB缓冲区的笼统,例如slab分配器。这种内存办理形式运用4KB缓冲区为基数,然后从中分配结构,并跟踪内存页运用状况,比如哪些内存页是满的,哪些页面没有完全运用,哪些页面为空。这样就答应该形式根据体系需要来动态调整内存运用。
为了支撑多个用户运用内存,有时会出现可用内存被耗费光的状况。由于这个原因,页面能够移出内存并放入磁盘中。这个进程称为交流,由于页面会被从内存交流到硬盘上。内存办理的源代码能够在./linux/mm中找到。
2.进程办理
进程实践是某特定应用程序的一个运转实体。在Linux体系中,能够一起运转多个进程,Linux经过在短的时刻距离内轮流运转这些进程而完结“多使命”。这一短的时刻距离称为“时刻片”,让进程轮流运转的办法称为“进程调度”,完结调度的程序称为调度程序。
进程调度控制进程对CPU的拜访。当需要挑选下一个进程运转时,由调度程序挑选最值得运转的进程。可运转进程实践上是仅等待CPU资源的进程,假如某个进程在等待其它资源,则该进程是不可运转进程。Linux运用了比较简单的根据优先级的进程调度算法挑选新的进程。
经过多使命机制,每个进程可认为只要自己独占计算机,然后简化程序的编写。每个进程有自己独自的地址空间,而且只能由这一进程拜访,这样,操作体系避免了进程之间的相互干扰以及“坏”程序对体系或许形成的危害。为了完结某特定使命,有时需要归纳两个程序的功用,例如一个程序输出文本,而另一个程序对文本进行排序。为此,操作体系还供给进程间的通讯机制来帮助完结这样的使命。Linux中常见的进程间通讯机制有信号、管道、同享内存、信号量和套接字等。
内核经过SCI供给了一个应用程序编程接口(API)来创建一个新进程(fork、exec或PortableOperatingSystemInterface[POSⅨ]函数),中止进程(kill、exit),并在它们之间进行通讯和同步(signal或许POSⅨ机制)。
3.文件体系
和DOS等操作体系不同,Linux操作体系中独自的文件体系并不是由驱动器号或驱动器称号(如A:或C:等)来标识的。相反,和UNIX操作体系一样,Linux操作体系将独立的文件体系组合成了一个层次化的树形结构,而且由一个独自的实体代表这一文件体系。Linux将新的文件体系经过一个称为“挂装”或“挂上”的操作将其挂装到某个目录上,然后让不同的文件体系结合成为一个全体。Linux操作体系的一个重要特色是它支撑许多不同类型的文件体系。Linux中最普遍运用的文件体系是Ext2,它也是Linux土生土长的文件体系。但Linux也能够支撑FAT、VFAT、FAT32、MINIX等不同类型的文件体系,然后能够方便地和其它操作体系交流数据。由于Linux支撑许多不同的文件体系,而且将它们组织成了一个一致的虚拟文件体系.
虚拟文件体系(VirtualFileSystem,VFS):躲藏了各种硬件的详细细节,把文件体系操作和不同文件体系的详细完结细节分离了开来,为一切的设备供给了一致的接口,VFS供给了多达数十种不同的文件体系。虚拟文件体系能够分为逻辑文件体系和设备驱动程序。逻辑文件体系指Linux所支撑的文件体系,如ext2,fat等,设备驱动程序指为每一种硬件控制器所编写的设备驱动程序模块。
虚拟文件体系(VFS)是Linux内核中十分有用的一个方面,由于它为文件体系供给了一个通用的接口笼统。VFS在SCI和内核所支撑的文件体系之间供给了一个交流层。即VFS在用户和文件体系之间供给了一个交流层。
VFS在用户和文件体系之间供给了一个交流层:
在VFS上面,是对比如open、close、read和write之类的函数的一个通用API笼统。在VFS下面是文件体系笼统,它定义了上层函数的完结方法。它们是给定文件体系(超过50个)的插件。文件体系的源代码能够在./linux/fs中找到。
文件体系层之下是缓冲区缓存,它为文件体系层供给了一个通用函数集(与详细文件体系无关)。这个缓存层经过将数据保存一段时刻(或许随即预先读取数据以便在需要是就可用)优化了对物理设备的拜访。缓冲区缓存之下是设备驱动程序,它完结了特定物理设备的接口。
因此,用户和进程不需要知道文件地点的文件体系类型,而只需要象运用Ext2文件体系中的文件一样运用它们。
4.设备驱动程序
设备驱动程序是Linux内核的首要部分。和操作体系的其它部分相似,设备驱动程序运转在高特权级的处理器环境中,然后能够直接对硬件进行操作,但正由于如此,任何一个设备驱动程序的错误都或许导致操作体系的溃散。设备驱动程序实践控制操作体系和硬件设备之间的交互。设备驱动程序供给一组操作体系可理解的笼统接口完结和操作体系之间的交互,而与硬件相关的详细操作细节由设备驱动程序完结。一般而言,设备驱动程序和设备
的控制芯片有关,例如,假如计算机硬盘是SCSI硬盘,则需要运用SCSI驱动程序,而不是IDE驱动程序。
5.网络接口(NET)
供给了对各种网络规范的存取和各种网络硬件的支撑。网络接口可分为网络协议和网络驱动程序。网络协议部分担任完结每一种或许的网络传输协议。众所周知,TCP/IP协议是Internet的规范协议,一起也是事实上的工业规范。Linux的网络完结支撑BSD套接字,支撑全部的TCP/IP协议。Linux内核的网络部分由BSD套接字、网络协议层和网络设备驱动程序组成。
网络设备驱动程序担任与硬件设备通讯,每一种或许的硬件设备都有相应的设备驱动程序。
2.linuxshell
shell是体系的用户界面,供给了用户与内核进行交互操作的一种接口。它接收用户输入的指令并把它送入内核去履行,是一个指令解释器。别的,shell编程言语具有一般编程言语的许多特色,用这种编程言语编写的shell程序与其他应用程序具有同样的效果。
现在首要有下列版别的shell。
1.BourneShell:是贝尔实验室开发的。
2.BASH:是GNU的BourneAgainShell,是GNU操作体系上默许的shell,大部分linux的发行套件运用的都是这种shell。
3.KornShell:是对BourneSHell的开展,在大部分内容上与BourneShell兼容。
4.CShell:是SUN公司Shell的BSD版别。
3.linux文件体系
各操作体系运用的文件体系并不相同,例如,Windows98曾经的微软操作体系运用FAT(FAT16)文件体系,Windows2000今后的版别运用NTFS文件体系,而Linux的正统文件体系是Ext2。
在CentOS6.3体系中,默许的文件体系是Ext4,它是Ext3(Ext2)文件体系的升级版,在功用、伸缩性和可靠性方面进行了很多改进,变化能够说是天翻地覆的,比如:
向下兼容Ext3;
最大1EB文件体系和16TB文件;
无限数量子目录;
Extents接连数据块概念;
多块分配、推迟分配、耐久预分配;
快速FSCK、日志校验、无日志形式、在线碎片整理、inode增强、默许启用barrier等;
Linux支撑的常见文件体系
Linux体系能够支撑的文件体系十分多,除Linux默许文件体系Ext2、Ext3和Ext4之外,还能支撑fat16、fat32、NTFS(需要从头编译内核)等Windows文件体系。也便是说,Linux能够经过挂载的方法运用Windows文件体系中的数据。Linux所能够支撑的文件体系在”/usr/src/kemels/当时体系版别/fs”目录中(需要在安装时挑选),该目录中的每个子目录都是一个能够辨认的文件体系。咱们介绍较为常见的Linux支撑的文件体系,如表1所示。
文件体系描述
ExtLinux中最早的文件体系,由于在功用和兼容性上具有许多缺陷,现在已经很少运用
Ext2是Ext文件体系的升级版别,RedHatLinux7.2版别曾经的体系默许都是Ext2文件体系。于1993年发布,支撑最大16TB的分区和最大2TB的文件(1TB=1024GB=1024x1024KB)
Ext3是Ext2文件体系的升级版别,最大的区别便是带日志功用,以便在体系忽然中止时进步文件体系的可靠性。支撑最大16TB的分区和最大2TB的文件
Ext4是Ext3文件体系的升级版。Ext4在功用、伸缩性和可靠性方面进行了很多改进。Ext4的变化能够说是天翻地覆的,比如向下兼容Ext3、最大1EB文件体系和16TB文件、无限数量子目录、Extents接连数据块概念、多块分配、推迟分配、耐久预分配、快速FSCK、日志校验、无日志形式、在线碎片整理、inode增强、默许启用barrier等。它是CentOS6.3的默许文件体系
swapswap是Linux中用于交流分区的文件体系(相似于Windows中的虚拟内存),当内存不够用时,运用交流分区暂时替代内存。一般巨细为内存的2倍,但是不要超过2GB。它是Linux的必需分区
NFSNFS是网络文件体系(NetworkFileSystem)的缩写,是用来完结不同主机之间文件同享的一种网络服务,本地主机能够经过挂载的方法运用远程同享的资源
iso9660光盘的规范文件体系。Linux要想运用光盘,必须支撑iso9660文件体系
fat便是Windows下的fatl6文件体系,在Linux中辨认为fat
vfat便是Windows下的fat32文件体系,在Linux中辨认为vfat。支撑最大32GB的分区和最大4GB的文件
NTFS便是Windows下的NTFS文件体系,不过Linux默许是不能辨认NTFS文件体系的,假如需要辨认,则需要从头编译内核才能支撑。它比fat32文件体系更加安全,速度更快,支撑最大2TB的分区和最大64GB的文件
ufsSun公司的操作体系Solaris和SunOS所采用的文件体系
procLinux中根据内存的虚拟文件体系,用来办理内存存储目录/proc
sysfs和proc—样,也是根据内存的虚拟文件体系,用来办理内存存储目录/sysfs
tmpfs也是一种根据内存的虚拟文件体系,不过也能够运用swap交流分区
文件体系详解:https://blog.csdn.net/hguisu/article/details/7401963
4.用户态和内核态
应用程序是无法直接拜访硬件资源的,需要经过经过内核SCI层供给的接口来拜访硬件资源。
Linux体系将自身划分为两部分,一部分为中心软件,便是kernel,也称作内核空间,另一部分为一般应用程序,这部分称为用户空间。
区别用户空间和内核空间的意图是为确保体系安全。在CPU的一切指令中,有一些指令是十分风险的,假如错用,将导致整个体系溃散。比如:清内存、设置时钟等。由于假如应用程序和内核在同一个维护等级,那么应用程序就有或许有意或许不小心进入了内核空间,破坏了内核空间的代码和数据,体系溃散就家常便饭。所以CPU将指令分为特权指令和非特权指令,关于那些风险的指令,只答应操作体系及其相关模块运用,一般的应用程序只能运用那些不会形成灾祸的指令。Intel的CPU将特权等级分为4个等级:RING0,RING1,RING2,RING3,内核空间等级为“RING0”,用户空间等级为RING3。
linux的内核是一个有机的全体。每一个用户进程运转时都好像有一份内核的复制,每当用户进程运用体系调用时,都自动地将运转形式从用户级转为内核级,此刻进程在内核的地址空间中运转。
当应用程序进程履行体系调用而陷入内核代码中履行时,咱们就称进程处于内核运转态(或简称为内核态)。此刻处理器处于特权级最高的(RING0级)内核代码中履行。当进程处于内核态时,履行的内核代码会运用当时进程的内核栈。每个进程都有自己的内核栈。当进程在履行用户自己的代码时,则称其处于用户运转态(用户态)。即此刻处理器在特权级最低的(RING3级)用户代码中运转。当正在履行用户程序而忽然被中止程序中止时,此刻用户程序也能够象征性地称为处于进程的内核态。由于中止处理程序将运用当时进程的内核栈。这与处于内核态的进程的状况有些相似。
内核态与用户态是操作体系的两种运转等级,跟intelcpu没有必然的联络,如上所说到的intelcpu供给Ring0-Ring3四种等级的运转形式,Ring0等级最高,Ring3最低。Linux运用了Ring3等级运转用户态,Ring0作为内核态,没有运用Ring1和Ring2。linux

linux系统编程手册pdf

Linux下的多线程编程
1引言
线程(thread)技能早在60年代就被提出,但真正应用多线程到操作体系中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支撑线程的概念,可是在一个进程(process)中只答应有一个线程,这样多线程就意味着多进程。现在,多线程技能现已被许多操作体系所支撑,包含Windows/NT,当然,也包含Linux。
为什么有了进程的概念后,还要再引进线程呢?运用多线程到底有哪些长处?什么的体系应该选用多线程?咱们首先有必要答复这些问题。
运用多线程的理由之一是和进程相比,它是一种十分”节俭”的多使命操作办法。咱们知道,在Linux体系下,发动一个新的进程有必要分配给它独立的地址空间,树立众多的数据表来维护它的代码段、仓库段和数据段,这是一种”昂贵”的多使命工作办法。而运转于一个进程中的多个线程,它们彼此之间运用相同的地址空间,同享大部分数据,发动一个线程所花费的空间远远小于发动一个进程所花费的空间,而且,线程间彼此切换所需的时刻也远远小于进程间切换所需求的时刻。据统计,总的说来桓鼋痰目笤际且桓鱿叱炭?0倍左右,当然,在具体的体系上,这个数据或许会有较大的区别。
运用多线程的理由之二是线程间方便的通讯机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能经过通讯的办法进行,这种办法不只费时,而且很不方便。线程则不然,因为同一进程下的线程之间同享数据空间,所以一个线程的数据能够直接为其它线程所用,这不只方便,而且方便。当然,数据的同享也带来其他一些问题,有的变量不能一同被两个线程所修正,有的子程序中声明为static的数据更有或许给多线程程序带来灾难性的冲击,这些正是编写多线程程序时最需求留意的地方。
除了以上所说的长处外,不好进程比较,多线程程序作为一种多使命、并发的工作办法,当然有以下的长处:
1)提高应用程序呼应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个体系都会等候这个操作,此刻程序不会呼应键盘、鼠标、菜单的操作,而运用多线程技能,将耗时长的操作(timeconsuming)置于一个新的线程,能够避免这种为难的状况。
2)使多CPU体系更加有用。操作体系会确保当线程数不大于CPU数目时,不同的线程运转于不同的CPU上。
3)改善程序结构。一个既长又复杂的进程能够考虑分为多个线程,成为几个独立或半独立的运转部分,这样的程序会利于了解和修正。
下面咱们先来尝试编写一个简略的多线程程序。
2简略的多线程编程
Linux体系下的多线程遵从POSIX线程接口,称为pthread。编写Linux下的多线程程序,需求运用头文件pthread.h,衔接时需求运用库libpthread.a。顺便说一下,Linux下pthread的完成是经过体系调用clone()来完成的。clone()是Linux所特有的体系调用,它的运用办法类似fork,关于clone()的详细状况,有爱好的读者能够去查看有关文档阐明。下面咱们展示一个最简略的多线程程序example1.c。
/*example.c*/
#include<stdio.h>
#include<pthread.h>
voidthread(void){
inti;
for(i=0;i<3;i++)
printf(“Thisisapthread.\n”);
}
intmain(void){
pthread_tid;
inti,ret;
ret=pthread_create(&id,NULL,(void*)thread,NULL);
if(ret!=0){
printf(“Createpthreaderror!\n”);
exit(1);
}
for(i=0;i<3;i++)
printf(“Thisisthemainprocess.\n”);
pthread_join(id,NULL);
return(0);
}
咱们编译此程序:
gccexample1.c-lpthread-oexample1
运转example1,咱们得到如下成果:
Thisisthemainprocess.
Thisisapthread.
Thisisthemainprocess.
Thisisthemainprocess.
Thisisapthread.
Thisisapthread.
再次运转,咱们或许得到如下成果:
Thisisapthread.
Thisisthemainprocess.
Thisisapthread.
Thisisthemainprocess.
Thisisapthread.
Thisisthemainprocess.
前后两次成果不相同,这是两个线程抢夺CPU资源的成果。上面的示例中,咱们运用到了两个函数,pthread_create和pthread_join,并声明晰一个pthread_t型的变量。
pthread_t在头文件/usr/include/bits/pthreadtypes.h中界说:
typedefunsignedlongintpthread_t;
它是一个线程的标识符。函数pthread_create用来创立一个线程,它的原型为:
externintpthread_create__P((pthread_t*__thread,__constpthread_attr_t*__attr,
void(__start_routine)(void*),void*__arg));
第一个参数为指向线程标识符的指针,第二个参数用来设置线程特点,第三个参数是线程运转函数的起始地址,最终一个参数是运转函数的参数。这儿,咱们的函数thread不需求参数,所以最终一个参数设为空指针。第二个参数咱们也设为空指针,这样将生成默许特点的线程。对线程特点的设定和修正咱们将在下一节阐述。当创立线程成功时,函数回来0,若不为0则阐明创立线程失利,常见的过错回来代码为EAGAIN和EINVAL。前者表示体系约束创立新的线程,例如线程数目过多了;后者表示第二个参数代表的线程特点值不合法。创立线程成功后,新创立的线程则运转参数三和参数四确认的函数,原来的线程则继续运转下一行代码。
函数pthread_join用来等候一个线程的完毕。函数原型为:
externintpthread_join__P((pthread_t__th,void**__thread_return));
第一个参数为被等候的线程标识符,第二个参数为一个用户界说的指针,它能够用来存储被等候线程的回来值。这个函数是一个线程堵塞的函数,调用它的函数将一向等候到被等候的线程完毕中止,当函数回来时,被等候线程的资源被收回。一个线程的完毕有两种途径,一种是象咱们上面的比如相同,函数完毕了,调用它的线程也就完毕了;另一种办法是经过函数pthread_exit来完成。它的函数原型为:
externvoidpthread_exit__P((void*__retval))attribute((noreturn));
仅有的参数是函数的回来代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。最终要阐明的是,一个线程不能被多个线程等候,不然第一个接收到信号的线程成功回来,其余调用pthread_join的线程则回来过错代码ESRCH。
在这一节里,咱们编写了一个最简略的线程,并掌握了最常用的三个函数pthread_create,pthread_join和pthread_exit。下面,咱们来了解线程的一些常用特点以及如何设置这些特点。
3修正线程的特点
在上一节的比如里,咱们用pthread_create函数创立了一个线程,在这个线程中,咱们运用了默许参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,运用默许特点就够了,但咱们仍是有必要来了解一下线程的有关特点。
特点结构为pthread_attr_t,它相同在头文件/usr/include/pthread.h中界说,喜爱追根问底的人能够自己去查看。特点值不能直接设置,须运用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数有必要在pthread_create函数之前调用。特点目标主要包含是否绑定、是否别离、仓库地址、仓库巨细、优先级。默许的特点为非绑定、非别离、缺省1M的仓库、与父进程相同等级的优先级。
关于线程的绑定,牵涉到别的一个概念:轻进程(LWP:LightWeightProcess)。轻进程能够了解为内核线程,它坐落用户层和体系层之间。体系对线程资源的分配、对线程的操控是经过轻进程来完成的,一个轻进程能够操控一个或多个线程。默许状况下,发动多少轻进程、哪些轻进程来操控哪些线程是由体系来操控的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的”绑”在一个轻进程之上。被绑定的线程具有较高的呼应速度,这是因为CPU时刻片的调度是面向轻进程的,绑定的线程能够确保在需求的时分它总有一个轻进程可用。经过设置被绑定的轻进程的优先级和调度级能够使得绑定的线程满意比如实时反应之类的要求。
设置线程绑定状况的函数为pthread_attr_setscope,它有两个参数,第一个是指向特点结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创立了一个绑定的线程。
#include
pthread_attr_tattr;
pthread_ttid;
/*初始化特点值,均设为默许值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr,PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid,&attr,(void*)my_function,NULL);
线程的别离状况决议一个线程以什么样的办法来中止自己。在上面的比如中,咱们选用了线程的默许特点,即为非别离状况,这种状况下,原有的线程等候创立的线程完毕。只要当pthread_join()函数回来时,创立的线程才算中止,才干开释自己占用的体系资源。而别离线程不是这姿态的,它没有被其他的线程所等候,自己运转完毕了,线程也就中止了,马上开释体系资源。程序员应该依据自己的需求,挑选恰当的别离状况。设置线程别离状况的函数为pthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(别离线程)和PTHREAD_CREATE_JOINABLE(非别离线程)。这儿要留意的一点是,假如设置一个线程为别离线程,而这个线程运转又十分快,它很或许在pthread_create函数回来之前就中止了,它中止今后就或许将线程号和体系资源移交给其他的线程运用,这样调用pthread_create的线程就得到了过错的线程号。要避免这种状况能够采纳一定的同步措施,最简略的办法之一是能够在被创立的线程里调用pthread_cond_timewait函数,让这个线程等候一会儿,留出足够的时刻让函数pthread_create回来。设置一段等候时刻,是在多线程编程里常用的办法。可是留意不要运用比如wait()之类的函数,它们是使整个进程睡觉,并不能处理线程同步的问题。
别的一个或许常用的特点是线程的优先级,它寄存在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行寄存,一般说来,咱们总是先取优先级,对取得的值修正后再寄存回去。下面即是一段简略的比如。
#include
#include
pthread_attr_tattr;
pthread_ttid;
sched_paramparam;
intnewprio=20;
pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr,?m);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr,?m);
pthread_create(&tid,&attr,(void*)myfunction,myarg);
4线程的数据处理
和进程相比,线程的最大长处之一是数据的同享性,各个进程同享父进程处沿用的数据段,能够方便的取得、修正数据。但这也给多线程编程带来了许多问题。咱们有必要当心有多个不同的进程拜访相同的变量。许多函数是不行重入的,即一同不能运转一个函数的多个拷贝(除非运用不同的数据段)。在函数中声明的静态变量常常带来问题,函数的回来值也会有问题。因为假如回来的是函数内部静态声明的空间的地址,则在一个线程调用该函数得到地址后运用该地址指向的数据时,别的线程或许调用此函数并修正了这一段数据。在进程中同享的变量有必要用关键字volatile来界说,这是为了避免编译器在优化时(如gcc中运用-OX参数)改动它们的运用办法。为了维护变量,咱们有必要运用信号量、互斥等办法来确保咱们对变量的正确运用。下面,咱们就逐渐介绍处理线程数据时的有关知识。
4.1线程数据
在单线程的程序里,有两种根本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD:Thread-SpecificData)。它和全局变量很象,在线程内部,各个函数能够象运用全局变量相同调用它,但它对线程外部的其它线程是不行见的。这种数据的必要性是清楚明晰的。例如咱们常见的变量errno,它回来规范的出错信息。它明显不能是一个局部变量,几乎每个函数都应该能够调用它;但它又不能是一个全局变量,不然在A线程里输出的很或许是B线程的出错信息。要完成比如此类的变量,咱们就有必要运用线程数据。咱们为每个线程数据创立一个键,它和这个键相相关,在各个线程里,都运用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表相同的数据内容。
和线程数据相关的函数主要有4个:创立一个键;为一个键指定线程数据;从一个键读取线程数据;删去键。
创立键的函数原型为:
externintpthread_key_create__P((pthread_key_t__key,
void(__destr_function)(void)));
第一个参数为指向一个键值的指针,第二个参数指明晰一个destructor函数,假如这个参数不为空,那么当每个线程完毕时,体系将调用这个函数来开释绑定在这个键上的内存块。这个函数常和函数pthread_once((pthread_once_tonce_control,void(*initroutine)(void)))一同运用,为了让这个键只被创立一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它履行这个函数,今后的调用将被它疏忽。
在下面的比如中,咱们创立一个键,并将它和某个数据相相关。咱们要界说一个函数createWindow,这个函数界说一个图形窗口(数据类型为Fl_Window*,这是图形界面开发工具FLTK中的数据类型)。因为各个线程都会调用这个函数,所以咱们运用线程数据。
/*声明一个键*/
pthread_key_tmyWinKey;
/*函数createWindow*/
voidcreateWindow(void){
Fl_Window*win;
staticpthread_once_tonce=PTHREAD_ONCE_INIT;
/*调用函数createMyKey,创立键*/
pthread_once(&once,createMyKey);
/*win指向一个新树立的窗口*/
win=newFl_Window(0,0,100,100,”MyWindow”);
/*对此窗口作一些或许的设置工作,如巨细、位置、称号等*/
setWindow(win);
/*将窗口指针值绑定在键myWinKey上*/
pthread_setpecific(myWinKey,win);
}
/*函数createMyKey,创立一个键,并指定了destructor*/
voidcreateMyKey(void){
pthread_keycreate(&myWinKey,freeWinKey);
}
/*函数freeWinKey,开释空间*/
voidfreeWinKey(Fl_Window*win){
deletewin;
}
这样,在不同的线程中调用函数createMyWin,都能够得到在线程内部均可见的窗口变量,这个变量经过函数pthread_getspecific得到。在上面的比如中,咱们现已运用了函数pthread_setspecific来将线程数据和一个键绑定在一同。这两个函数的原型如下:
externintpthread_setspecific__P((pthread_key_t__key,__constvoid*__pointer));
externvoid*pthread_getspecific__P((pthread_key_t__key));
这两个函数的参数意义和运用办法是清楚明晰的。要留意的是,用pthread_setspecific为一个键指定新的线程数据时,有必要自己开释原有的线程数据以收回空间。这个进程函数pthread_key_delete用来删去一个键,这个键占用的内存将被开释,但相同要留意的是,它只开释键占用的内存,并不开释该键相关的线程数据所占用的内存资源,而且它也不会触发函数pthread_key_create中界说的destructor函数。线程数据的开释有必要在开释键之前完成。
4.2互斥锁
互斥锁用来确保一段时刻内只要一个线程在履行一段代码。必要性清楚明晰:假设各个线程向同一个文件次序写入数据,最终得到的成果一定是灾难性的。
咱们先看下面一段代码。这是一个读/写程序,它们共用一个缓冲区,而且咱们假定一个缓冲区只能保存一条信息。即缓冲区只要两个状况:有信息或没有信息。
voidreader_function(void);
voidwriter_function(void);
charbuffer;
intbuffer_has_item=0;
pthread_mutex_tmutex;
structtimespecdelay;
voidmain(void){
pthread_treader;
/*界说延迟时刻*/
delay.tv_sec=2;
delay.tv_nec=0;
/*用默许特点初始化一个互斥锁目标*/
pthread_mutex_init(&mutex,NULL);
pthread_create(&reader,pthread_attr_default,(void*)&reader_function),NULL);
writer_function();
}
voidwriter_function(void){
while(1){
/*确定互斥锁*/
pthread_mutex_lock(&mutex);
if(buffer_has_item==0){
buffer=make_new_item();
buffer_has_item=1;
}
/*翻开互斥锁*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
voidreader_function(void){
while(1){
pthread_mutex_lock(&mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
这儿声明晰互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其间包含一个体系分配的特点目标。函数pthread_mutex_init用来生成一个互斥锁。NULL参数标明运用默许特点。假如需求声明特定特点的互斥锁,须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁特点。前一个函数设置特点pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的比如中,咱们运用的是默许特点PTHREAD_PROCESS_PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD_MUTEX_DEFAULT。它们分别界说了不同的上所、解锁机制,一般状况下,选用最终一个默许特点。
pthread_mutex_lock声明开端用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock中止,均被上锁,即同一时刻只能被一个线程调用履行。当一个线程履行到pthread_mutex_lock处时,假如该锁此刻被另一个线程运用,那此线程被堵塞,即程序将等候到另一个线程开释此互斥锁。在上面的比如中,咱们运用了pthread_delay_np函数,让线程睡觉一段时刻,就是为了避免一个线程始终占据此函数。
上面的比如十分简略,就不再介绍了,需求提出的是在运用互斥锁的进程中很有或许会呈现死锁:两个线程企图一同占用两个资源,并按不同的次第确定相应的互斥锁,例如两个线程都需求确定互斥锁1和互斥锁2,a线程先确定互斥锁1,b线程先确定互斥锁2,这时就呈现了死锁。此刻咱们能够运用函数pthread_mutex_trylock,它是函数pthread_mutex_lock的非堵塞版别,当它发现死锁不行避免时,它会回来相应的信息,程序员能够针对死锁做出相应的处理。别的不同的互斥锁类型对死锁的处理不相同,但最主要的仍是要程序员自己在程序设计留意这一点。
4.3条件变量
前一节中咱们讲述了如何运用互斥锁来完成线程间数据的同享和通讯,互斥锁一个明显的缺点是它只要两种状况:确定和非确定。而条件变量经过答应线程堵塞和等候另一个线程发送信号的办法弥补了互斥锁的缺乏,它常和互斥锁一同运用。运用时,条件变量被用来堵塞一个线程,当条件不满意时,线程往往解开相应的互斥锁并等候条件发生变化。一旦其它的某个线程改动了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量堵塞的线程。这些线程将重新确定互斥锁并重新测验条件是否满意。一般说来,条件变量被用来进行线承间的同步。
条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:
externintpthread_cond_init__P((pthread_cond_t*__cond,__constpthread_condattr_t*cond_attr));
其间cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的特点结构,和互斥锁相同咱们能够用它来设置条件变量是进程内可用仍是进程间可用,默许值是PTHREADPROCESS_PRIVATE,即此条件变量被同一进程内的各个线程运用。留意初始化条件变量只要未被运用时才干重新初始化或被开释。开释一个条件变量的函数为pthread_conddestroy(pthread_cond_tcond)。
函数pthread_cond_wait()使线程堵塞在一个条件变量上。它的函数原型为:
externintpthread_cond_wait__P((pthread_cond_t*__cond,
pthread_mutex_t*__mutex));
线程解开mutex指向的锁并被条件变量cond堵塞。线程能够被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,可是要留意的是,条件变量只是起堵塞和唤醒线程的效果,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点咱们从后边的比如中能够看到。线程被唤醒后,它将重新查看判断条件是否满意,假如还不满意,一般说来线程应该仍堵塞在这儿,被等候被下一次唤醒。这个进程一般用while语句完成。
另一个用来堵塞线程的函数是pthread_cond_timedwait(),它的原型为:
externintpthread_cond_timedwait__P((pthread_cond_t*__cond,
pthread_mutex_t*__mutex,__conststructtimespec*__abstime));
它比函数pthread_cond_wait()多了一个时刻参数,经历abstime段时刻后,即使条件变量不满意,堵塞也被免除。
函数pthread_cond_signal()的原型为:
externintpthread_cond_signal__P((pthread_cond_t*__cond));
它用来开释被堵塞在条件变量cond上的一个线程。多个线程堵塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决议的。要留意的是,有必要用维护条件变量的互斥锁来维护这个函数,不然条件满意信号又或许在测验条件和调用pthread_cond_wait函数之间被宣布,从而形成无约束的等候。下面是运用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简略的比如。
pthread_mutex_tcount_lock;
pthread_cond_tcount_nonzero;
unsignedcount;
decrement_count(){
pthread_mutex_lock(&count_lock);
while(count==0)
pthread_cond_wait(&count_nonzero,&count_lock);
count=count-1;
pthread_mutex_unlock(&count_lock);
}
increment_count(){
pthread_mutex_lock(&count_lock);
if(count==0)
pthread_cond_signal(&count_nonzero);
count=count+1;
pthread_mutex_unlock(&count_lock);
}
count值为0时,decrement函数在pthread_cond_wait处被堵塞,并翻开互斥锁count_lock。此刻,当调用到函数increment_count时,pthread_cond_signal()函数改动条件变量,告知decrement_count()中止堵塞。读者能够试着让两个线程分别运转这两个函数,看看会呈现什么样的成果。
函数pthread_cond_broadcast(pthread_cond_t*cond)用来唤醒一切被堵塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,所以有必要当心运用这个函数。
4.4信号量
信号量本质上是一个非负的整数计数器,它被用来操控对公共资源的拜访。当公共资源添加时,调用函数sem_post()添加信号量。只要当信号量值大于0时,才干运用公共资源,运用后,函数sem_wait()削减信号量。函数sem_trywait()和函数pthread_mutex_trylock()起相同的效果,它是函数sem_wait()的非堵塞版别。下面咱们逐一介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中界说。
信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:
externintsem_init__P((sem_t*__sem,int__pshared,unsignedint__value));
sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间同享,不然只能为当时进程的一切线程同享;value给出了信号量的初始值。
函数sem_post(sem_t*sem)用来添加信号量的值。当有线程堵塞在这个信号量上时,调用这个函数会使其间的一个线程不在堵塞,挑选机制相同是由线程的调度策略决议的。
函数sem_wait(sem_t*sem)被用来堵塞当时线程直到信号量sem的值大于0,免除堵塞后将sem的值减一,标明公共资源经运用后削减。函数sem_trywait(sem_t*sem)是函数sem_wait()的非堵塞版别,它直接将信号量sem的值减一。
函数sem_destroy(sem_t*sem)用来开释信号量sem。
下面咱们来看一个运用信号量的比如。在这个比如中,一共有4个线程,其间两个线程担任从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。
/*Filesem.c*/
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
#defineMAXSTACK100
intstack[MAXSTACK][2];
intsize=0;
sem_tsem;
/*从文件1.dat读取数据,每读一次,信号量加一*/
voidReadData1(void){
FILE*fp=fopen(“1.dat”,”r”);
while(!feof(fp)){
fscanf(fp,”%d%d”,&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*从文件2.dat读取数据*/
voidReadData2(void){
FILE*fp=fopen(“2.dat”,”r”);
while(!feof(fp)){
fscanf(fp,”%d%d”,&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*堵塞等候缓冲区有数据,读取数据后,开释空间,继续等候*/
voidHandleData1(void){
while(1){
sem_wait(&sem);
printf(“Plus:%d+%d=%d\n”,stack[size][0],stack[size][1],
stack[size][0]+stack[size][1]);
–size;
}
}
voidHandleData2(void){
while(1){
sem_wait(&sem);
printf(“Multiply:%d*%d=%d\n”,stack[size][0],stack[size][1],
stack[size][0]*stack[size][1]);
–size;
}
}
intmain(void){
pthread_tt1,t2,t3,t4;
sem_init(&sem,0,0);
pthread_create(&t1,NULL,(void*)HandleData1,NULL);
pthread_create(&t2,NULL,(void*)HandleData2,NULL);
pthread_create(&t3,NULL,(void*)ReadData1,NULL);
pthread_create(&t4,NULL,(void*)ReadData2,NULL);
/*避免程序过早退出,让它在此无限期等候*/
pthread_join(t1,NULL);
在Linux下,咱们用指令gcc-lpthreadsem.c-osem生成可履行文件sem。咱们事先编辑好数据文件1.dat和2.dat,假设它们的内容分别为12345678910和-1-2-3-4-5-6-7-8-9-10,咱们运转sem,得到如下的成果:
Multiply:-1*-2=2
Plus:-1±2=-3
Multiply:910=90
Plus:-9±10=-19
Multiply:-7-8=56
Plus:-5±6=-11
Multiply:-3*-4=12
Plus:9+10=19
Plus:7+8=15
Plus:5+6=11
从中咱们能够看出各个线程间的竞争联系。而数值并未按咱们原先的次序显现出来这是因为size这个数值被各个线程任意修正的原因。这也往往是多线程编程要留意的问题。
5小结
多线程编程是一个很有意思也很有用的技能,运用多线程技能的网络蚂蚁是现在最常用的下载工具之一,运用多线程技能的grep比单线程的grep要快上几倍,类似的比如还有许多。期望大家能用多线程技能写出高效实用的好程序来。
6、信号(signal)是一种软件中止,它供给了一种处理异步事情的办法,也是进程间惟一的异步通讯办法。在Linux体系中,依据POSIX规范扩展今后的信号机制,不只能够用来通知某种程序发生了什么事情,还能够给进程传递数据。
6.1、信号的来源
信号的来源能够有许多种试,依照发生条件的不同能够分为硬件和软件两种。
6.1.1、硬件办法
当用户在终端上按下某键时,将发生信号。如按下组合键后将发生一个SIGINT信号。
硬件异常发生信号:除数据、无效的存储拜访等。这些事情一般由硬件(如:CPU)检测到,并将其通知给Linux操作体系内核,然后内核生成相应的信号,并把信号发送给该事情发生时正在进行的程序。
6.1.2、软件办法
用户在终端下调用kill指令向进程发送使命信号。
进程调用kill或sigqueue函数发送信号。
当检测到某种软件条件现已具备时宣布信号,如由alarm或settimer设置的定时器超不时将生成SIGALRM信号。
6.2、信号的品种
在Shell下输入kill–l可显现Linux体系支撑的全部依靠,信号列表如下:
SIGHUP2)SIGINT3)SIGQUIT4)SIGILL
SIGTRAP6)SIGABRT7)SIGBUS8)SIGFPE
SIGKILL10)SIGUSR111)SIGSEGV12)SIGUSR2
SIGPIPE14)SIGALRM15)SIGTERM16)SIGSTKFLT
SIGCHLD18)SIGCONT19)SIGSTOP20)SIGTSTP
SIGTTIN22)SIGTTOU23)SIGURG24)SIGXCPU
SIGXFSZ26)SIGVTALRM27)SIGPROF28)SIGWINCH
SIGIO30)SIGPWR31)SIGSYS34)SIGRTMIN
SIGRTMIN+136)SIGRTMIN+237)SIGRTMIN+338)SIGRTMIN+4
SIGRTMIN+540)SIGRTMIN+641)SIGRTMIN+742)SIGRTMIN+8
SIGRTMIN+944)SIGRTMIN+1045)SIGRTMIN+1146)SIGRTMIN+12
SIGRTMIN+1348)SIGRTMIN+1449)SIGRTMIN+1550)SIGRTMAX-14
SIGRTMAX-1352)SIGRTMAX-1253)SIGRTMAX-1154)SIGRTMAX-10
SIGRTMAX-956)SIGRTMAX-857)SIGRTMAX-758)SIGRTMAX-6
SIGRTMAX-560)SIGRTMAX-461)SIGRTMAX-362)SIGRTMAX-2
SIGRTMAX-164)SIGRTMAX
信号的值界说在signal.h中,在Linux中没有16和32这两个信号。上面信号的意义如下:
(1)SIGHUP:当用户退出Shell时,由该Shell启的发一切进程都退接收到这个信号,默许动作为中止进程。
(2)SIGINT:用户按下组合键时,用户端时向正在运转中的由该终端发动的程序宣布此信号。默许动作为中止进程。
(3)SIGQUIT:当用户按下组合键时发生该信号,用户终端向正在运转中的由该终端发动的程序宣布此信号。默许动作为中止进程并发生core文件。
(4)SIGILL:CPU检测到某进程履行了不合法指令。默许动作为中止进程并发生core文件。
(5)SIGTRAP:该信号由断点指令或其他trap指令发生。默许动作为中止进程并发生core文件。
(6)SIGABRT:调用abort函数时发生该信号。默许动作为中止进程并发生core文件。
(7)SIGBUS:不合法拜访内存地址,包含内存地址对齐(alignment)出错,默许动作为中止进程并发生core文件。
(8)SIGFPE:在发生致命的算术过错时发生。不只包含浮点运转过错,还包含溢出及除数为0等一切的算术过错。默许动作为中止进程并发生core文件。
(9)SIGKILL:无条件中止进程。本信号不能被疏忽、处理和堵塞。默许动作为中止进程。它向体系管理员供给了一种能够杀死任何进程的办法。
(10)SIGUSR1:用户界说的信号,即程序能够在程序中界说并运用该信号。默许动作为中止进程。
(11)SIGSEGV:指示进程进行了无效的内存拜访。默许动作为中止进程并运用该信号。默许动作为中止进程。
(12)SIGUSR2:这是别的一个用户界说信号,程序员能够在程序中界说并运用该信号。默许动作为中止进程。
(13)SIGPIPE:Brokenpipe:向一个没有读端的管道写数据。默许动作为中止进程。
(14)SIGALRM:定时器超时,超时的时刻由体系调用alarm设置。默许动作为中止进程。
(15)SIGTERM:程序完毕(terminate)信号,与SIGKILL不同的是,该信号能够被堵塞和处理。一般用来要求程序正常退出。履行Shell指令kill时,缺少发生这个信号。默许动作为中止进程。
(16)SIGCHLD:子程序完毕时,父进程会收到这个信号。默许动作为疏忽该信号。
(17)SIGCONT:让一个暂停的进程继续履行。
(18)SIGSTOP:中止(stopped)进程的履行。留意它和SIGTERM以及SIGINT的区别:该进程还未完毕,只是暂停履行。本信号不能被疏忽、处理和堵塞。默许作为暂停进程。
(19)SIGTSTP:中止进程的动作,但该信号能够被处理和疏忽。按下组合键时宣布该信号。默许动作为暂停进程。
(20)SIGTTIN:当后台进程要从用户终端读数据时,该终端中的一切进程会收到SIGTTIN信号。默许动作为暂停进程。
(21)SIGTTOU:该信号类似于SIGTIN,在后台进程要向终端输出数据时发生。默许动作为暂停进程。
(22)SIGURG:套接字(socket)上有紧迫数据时,向当时正在运转的进程宣布此信号,报告有紧迫数据到达。默许动作为疏忽该信号。
(23)SIGXCPU:进程履行时刻超越了分配给该进程的CPU时刻,体系发生该信号并发送给该进程。默许动作为中止进程。
(24)SIGXFSZ:超越文件最大长度的约束。默许动作为yl中止进程并发生core文件。
(25)SIGVTALRM:虚拟时钟超不时发生该信号。类似于SIGALRM,可是它只计算该进程占有用的CPU时刻。默许动作为中止进程。
(26)SIGPROF:类似于SIGVTALRM,它不只包含该进程占用的CPU时刻还抱括履行体系调用的时刻。默许动作为中止进程。
(27)SIGWINCH:窗口巨细改动时宣布。默许动作为疏忽该信号。
(28)SIGIO:此信号向进程指示宣布一个异步IO事情。默许动作为疏忽。
(29)SIGPWR:关机。默许动作为中止进程。
(30)SIGRTMIN~SIGRTMAX:Linux的实时信号,它没有固定的意义(或许说能够由用户自由运用)。留意,Linux线程机制运用了前3个实时信号。一切的实时信号的默许动作都是中止进程。
6.2.1、牢靠信号与不行靠信号
在Linux体系中,信号的牢靠性是指信号是否会丢掉,或许说该信号是否支撑扫除。SIGHUP(1)~SIGSYS(31)之间的信号都是继承自UNIX体系是不行靠信号。Linux体系依据POSIX规范界说了SIGRTMIN(33)~SIGRTMAX(64)之间的信号,它们都是牢靠信号,也称为实时信号。
当导致发生信号的事情发生时,内核就发生一个信号。信号发生后,内核一般会在进程表中设置某种形的标志。当内核设置了这个标志,咱们就说内核向一个进程投递了一个信号。信号发生(generate)和投递(delivery)之间的时刻间隔,称主信号未决(pending)。
进程能够调用sigpending将信号设为堵塞,假如为进程发生一个堵塞信号,而对信号的动作是捕捉该信号(即不疏忽信号),则内核将为该进程的此信号保持为未决状况,直到该进程对此信号免除堵塞或许对此信号的呼应更改为疏忽。假如在进程免除对某个信号的堵塞之前,这种信号发生了屡次,那么假如信号被投递屡次(即信号在未决信号队列里边排队),则称之为牢靠信号;只被投递一次的信号称为不行靠信号。
6.2.2、信号的优先级
信号实质上是软中止,中止有优先级,信号也有优先级。假如一个进程有多个未决信号,则关于同一个未决的实时信号,内核将依照发送的次序来投递信号。假如存在多个未决信号,则值(或许说编号)越小的越先被投递。假如即存在不行靠信号,又存在牢靠信号(实时信号),尽管POSIX对这一状况没有明确规定,但Linux体系和大多数遵从POSIX规范的操作体系相同,将优先投递不行靠信号。
6.3、进程对信号的呼应
当信号发生时,用户能够要求进程以下列3种办法之一对信号做出呼应。
6.3.1、捕捉信号:关于要捕捉的信号,能够为其指定信号处理函数,信号发生时该函数自动被调用,在该函数内部完成对该信号的处理。
6.3.2、疏忽信号:大多数信号都可运用这种办法进行处理,可是SIGKILL和SIGSTOP这两个信号不能被疏忽,一同这两个信号也不能被捕获和堵塞。此外,假如疏忽某某些由硬件异常发生的信号(如不合法存储拜访或除以0),则进程的行为是不行猜测的。
6.3.3、依照体系默许办法处理。大部分信号的默许操作是中止进程,且一切的实时信号的默许动作都是中止进程。
6.4、各种信号的默许处理状况
程序不行捕获、堵塞或疏忽的信号有:SIGKILL,SIGSTOP
不能恢复至默许动作的信号有:SIGILL,SIGTRAP
默许会导致进程流产的信号有:SIGABRT、SIGBUS、SIGFPE、SIGILL、SIGIOT、SIGQUIT、SIGSEGV、SIGTRAP、SIGXCPU、SIGXFSZ
默许会导致进程退出的信号有:SIGALRM、SIGHUP、SIGINT、SIGKILL、SIGPIPE、SIGPOLL、SIGPROF、SIGSYS、SIGTERM、SIGUSR1、SIGUSR2、SIGVTALRM
默许会导致进程中止的信号有:SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU
默许进程疏忽的信号有:SIGCHLD、SIGPWR、SIGURG、SIGWINCH
6.5、信号处理函数与相关结构
6.5.1、信号装置
(1)、signal()
#include
void(*signal(intsignum,void(*handler))(int)))(int);
假如该函数原型不容易了解的话,能够参阅下面的分解办法来了解:
typedefvoid(*sighandler_t)(int);
sighandler_tsignal(intsignum,sighandler_thandler));
第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,能够疏忽该信号(参数设为SIG_IGN);能够选用体系默许办法处理信号(参数设为SIG_DFL);也能够自己完成处理办法(参数指定一个函数地址)。
假如signal()调用成功,回来最终一次为装置信号signum而调用signal()时的handler值;失利则回来SIG_ERR。
(2)、sigaction()
#include
intsigaction(intsignum,conststructsigaction*act,structsigaction*oldact));
sigaction函数用于改动进程接收到特定信号后的行为。该函数的第一个参数为信号的值,能够为除SIGKILL及SIGSTOP外的任何一个特定有用的信号(为这两个信号界说自己的处理函数,将导致信号装置过错)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,能够为空,进程会以缺省办法对信号处理;第三个参数oldact指向的目标用来保存原来对相应信号的处理,可指定oldact为NULL。假如把第二、第三个参数都设为NULL,那么该函数可用于查看信号的有用性。
6.5.2、发送信号函数
(1)intraise(intsig);对当时进程发送指定信号
(2)intpause(void);将进程挂起等候信号
(3)intkill(pid_tpid,intsig);经过进程编号发送信号
(4)unsignedintalarm(unsignedintseconds);指定时刻(秒)发送SIGALRM信号。seconds为0时取消一切已设置的alarm恳求;
(5)intsigqueue(pid_tpid,intsig,constunionsigvalval);类似于kill函数,多了附带共用体unionsigval形数,将共用体中的成员intsival_int或void*sival_ptr的值传递给信号处理函数中的界说类型siginfo_t中的intsi_int或void*si_ptr;
(6)intsetitimer(intwhich,conststructitimerval*value,structitimerval*oldvalue);可定时发送信号,依据which可指定三种信号类型:SIGALRM、SIGVTALRM和SIGPROF;效果时刻也因which值不同而不同;structitimerval的成员it_interval界说间隔时刻,it_value为0时,使计时器失效;
(7)voidabort(void)将形成进程中止;除非捕获SIGABORT信号;
6.5.3、信号集及信号集操作
sigfillset(sigset_t*set);设置一切的信号到set信号会集;
sigemptyset(sigset_t*set);从set信号会集清空一切信号;
sigaddset(sigset_t*set,intsig);在set信号会集加入sig信号;
sigdelset(sigset_t*set,intsig);在set信号会集删去sig信号;
6.5.4、堵塞信号相关函数
intsigprocmask(inthow,constsigset_t*set,sigset_t*set);依据how值,设置堵塞信号集,或开释堵塞的信号集
intsigpending(sigset_t*set);获取在堵塞中的一切信号;
intsigsuspend(constsigset_t*set);类似于pause()函数!

未经允许不得转载:IT技术网站 » linux系统编程(linux系统编程手册pdf)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

志在指尖 用双手敲打未来

登录/注册IT技术大全

热门IT技术

C#基础入门   SQL server数据库   系统SEO学习教程   WordPress小技巧   WordPress插件   脚本与源码下载