linux C的学习笔记
来源:互联网 发布:易知投资有限责任公司 编辑:程序博客网 时间:2024/05/22 06:43
引导块、超级块、索引节点表和数据块
普通文件--文本文件、二进制文件
文件描述符的前三项对于一般的进程是固定的且由系统自动打开。
a. 如果两边都是有符号数,或者都是无符号数,那么较低Rank的类型转换成较高Rank的类型。例如unsigned int和unsigned long做算术运算时都转成unsigned long。b. 否则,如果一边是无符号数另一边是有符号数,无符号数的Rank不低于有符号数的Rank,则把有符号数转成另一边的无符号类型。例如unsigned long和int做算术运算时都转成unsigned long,unsigned long和long做算术运算时也都转成unsigned long。c. 剩下的情况是:一边有符号另一边无符号,并且无符号数的Rank低于有符号数的Rank。这时又分为两种情况,如果这个有符号数类型能够覆盖这个无符号数类型的取值范围,则把无符号数转成另一边的有符号类型。例如遵循LP64的平台上unsignedint和long在做算术运算时都转成long。d. 否则,也就是这个有符号数类型不足以覆盖这个无符号数类型的取值范围,则把两边都转成有符号数的Rank对应的无符号类型。例如在遵循ILP32的平台上unsignedint和long在做算术运算时都转成unsigned long
1、取出8~15位。unsigned int a, b, mask = 0x0000ff00;a = 0x12345678;b = (a & mask) >> 8; /* 0x00000056 */2、将8~15位清0。unsigned int a, b, mask = 0x0000ff00;a = 0x12345678;b = a & ~mask; /* 0x12340078 */3、将8~15位置1。unsigned int a, b, mask = 0x0000ff00;a = 0x12345678;b = a | mask; /* 0x1234ff78 */
1、一个数和自己做异或的结果是02、从异或的真值表可以看出,不管是0还是1,和0做异或保持原值不变,和1做异或得到原值的相反值3、如果a1 ^ a2 ^ a3 ^ ... ^ an的结果是1,则表示a1、a2、a3...an之中1的个数为奇数个,否则为 偶数个4、x ^ x ^ y == y,因为x ^ x == 0,0 ^ y == y
寄存器(Register)程序计数器(PC,Program Counter)指令译码器(Instruction Decoder)算术逻辑单元(ALU,Arithmetic and Logic Unit)地址和数据总线(Bus)
如果处理器没有MMU,或者有MMU但没有启用,CPU执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片(以下称为物理内存,以便与虚拟内存区分)接收,这称为物理地址(Physical Address,以下简称PA)
如果处理器启用了MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address,以下简称VA),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将VA映射成PA
1. 操作系统在初始化或分配、释放内存时会执行一些指令在物理内存中填写页表,然后用指令设置MMU,告诉MMU页表在物理内存中的什么位置。2. 设置好之后,CPU每次执行访问内存的指令都会自动引发MMU做查表和地址转换操作,地址转换操作由硬件自动完成,不需要用指令控制MMU去做。
1. 用户程序要访问的一个VA,经MMU检查无权访问。2. MMU产生一个异常,CPU从用户模式切换到特权模式,跳转到内核代码中执行异常服务程序。3. 内核把这个异常解释为段错误,把引发异常的进程终止掉。
是一个开放标准,各种UNIX系统的可执行文件都采用ELF格式,它有三种不同的类型:
语句标号单独属于一个命名空间。例如在函数中局部变量和语句标号可以重名,互不影响。由于使用标号的语法和使用其它标识符的语法都不一样,编译器不会把它和别的标识符弄混struct,enum和union(下一节介绍union)的类型Tag属于一个命名空间。由于Tag前面总是带struct,enum或union关键字,所以编译器不会把它和别的标识符弄混。struct和union的成员名属于一个命名空间。由于成员名总是通过.或->运算符来访问而不会单独使用,所以编译器不会把它和别的标识符弄混。所有其它标识符,例如变量名、函数名、宏定义、typedef的类型名、enum成员等等都属于同一个命名空间。如果有重名的话,宏定义覆盖所有其它标识符,因为它在预处理阶段而不是编译阶段处理,除了宏定义之外其它几类标识符按上面所说的规则处理,内层作用域覆盖外层作用域。
外部链接(External Linkage),如果最终的可执行文件由多个程序文件链接而成,一个标识符在任意程序文件中即使声明多次也都代表同一个变量或函数,则这个标识符具有ExternalLinkage。具有External Linkage的标识符编译后在符号表中是GLOBAL的符号。例如上例中main函数外面的a和c,main和printf也算。内部链接(Internal Linkage),如果一个标识符在某个程序文件中即使声明多次也都代表同一个变量或函数,则这个标识符具有Internal Linkage。例如上例中main函数外面的b。如果有另一个foo.c程序和main.c链接在一起,在foo.c中也声明一个static int b;,则那个b和这个b不代表同一个变量。具有Internal Linkage的标识符编译后在符号表中是LOCAL的符号,但main函数里面那个a不能算Internal Linkage的,因为即使在同一个程序文件中,在不同的函数中声明多次,也不代表同一个变量。无链接(No Linkage)。除以上情况之外的标识符都属于No Linkage的,例如函数的局部变量,以及不表示变量和函数的其它标识符。
static,用它修饰的变量的存储空间是静态分配的,用它修饰的文件作用域的变量或函数具有 Internal Linkage。auto,用它修饰的变量在函数调用时自动在栈上分配存储空间,函数返回时自动释放,例如上例中 main函数里的b其实就是用auto修饰的,只不过auto可以省略不写,auto不能修饰文件作用域的变量。register,编译器对于用register修饰的变量会尽可能分配一个专门的寄存器来存储,但如果实在分配不开寄存器,编译器就把它当auto变量处理了,register不能修饰文件作用域的变量。现在一般编译器的优化都做得很好了,它自己会想办法有效地利用CPU的寄存器,所以现在register关键字也用得比较少了。extern,上面讲过,链接属性是根据一个标识符多次声明时是不是代表同一个变量或函数来分类的, extern关键字就用于多次声明同一个标识符,下一章再详细介绍它的用法。typedef,它并不是用来修饰变量的,而是定义一个类型名
静态生存期(Static Storage Duration),具有外部或内部链接属性,或者被static修饰的变量,在程序开始执行时分配和初始化一次,此后便一直存在直到程序结束。这种变量通常位于.rodata,.data或.bss段,例如上例中main函数外的A,a,b,c,以及main函数里的a。自动生存期(Automatic Storage Duration),链接属性为无链接并且没有被static修饰的变量,这种变量在进入块作用域时在栈上或寄存器中分配,在退出块作用域时释放。例如上例中main函数里的b和c。动态分配生存期(Allocated Storage Duration),以后会讲到调用malloc函数在进程的堆空间中分配内存,调用free函数可以释放这种存储空间。
typedef struct {unsigned int one:1;unsigned int two:3;unsigned int three:10;unsigned int four:5;unsigned int :2;unsigned int five:8;unsigned int six:8;} demo_type;
demo_type s = { 1, 5, 513, 17, 129, 0x81 };printf("sizeof demo_type = %u\n", sizeof(demo_type));printf("values: s=%u,%u,%u,%u,%u,%u\n", s.one, s.two, s.three, s.four, s.five, s.six);return 0;
1. 一是使预处理的速度变慢了,要处理很多本来不需要处理的头文件。2. 二是如果有foo.h包含bar.h,bar.h又包含foo.h的情况,预处理器就陷入死循环了(其实编译器都会规定一个包含层数的上限)。3. 三是头文件里有些代码不允许重复出现,虽然变量和函数允许多次声明(只要不是多次定义就行),但头文件里有些代码是不允许多次出现的,比如typedef类型定义和结构体Tag定义等,在一个程序文件中只允许出现一次。
$ tree.|-- main.c`-- stack|-- is_empty.c|-- pop.c|-- push.c|-- stack.c`-- stack.h1directory, 6 files我们把stack.c、push.c、pop.c、is_empty.c编译成目标文件:$ gcc -c stack/stack.c stack/push.c stack/pop.c stack/is_empty.c然后打包成一个静态库libstack.a:$ ar rs libstack.a stack.o push.o pop.o is_empty.oar: creating libstack.a库文件名都是以lib开头的,静态库以.a作为后缀,表示Archive。ar命令类似于tar命令,起一个打包的作用,但是把目标文件打包成静态库只能用ar命令而不能用tar命令。选项r表示将后面的文件列表添加到文件包,如果文件包不存在就创建它,如果文件包中已有同名文件就替换成新的。s是专用于生成静态库的,表示为静态库创建索引,这个索引被链接器使用。ranlib命令也可以为静态库创建索引,以上命令等价于:$ ar r libstack.a stack.o push.o pop.o is_empty.o$ ranlib libstack.a
然后我们把libstack.a和main.c编译链接在一起:$ gcc main.c -L. -lstack -Istack -o main-L选项告诉编译器去哪里找需要的库文件,-L.表示在当前目录找。-lstack告诉编译器要链接libstack库,-I选项告诉编译器去哪里找头文件。注意,即使库文件就在当前目录,编译器默认也不会去找的,所以-L.选项不能少。编译器默认会找的目录可以用-print-search-dirs选项查看
0x0804 8000-0x080f 4000是从/bin/bash加载到内存的,访问权限为r-x,表示TextSegment,包 含.text段、.rodata段、.plt段等0x080f 4000-0x080f 9000也是从/bin/bash加载到内存的,访问权限为rw-,表示Data Segment,包含 .data段、.bss段等。0x0928 3000-0x0949 7000不是从磁盘文件加载到内存的,这段空间称为堆(Heap),以后会讲到用malloc函数动态分配内存是在这里分配的。从0xb7ca 8000开始是共享库的映射空间,每个共享库也分为几个Segment,每个Segment有不同的访问权限。可以看到,从堆空间的结束地址(0x0949 7000)到共享库映射空间的起始地址(0xb7ca 8000)之间有很大的地址空洞,在动态分配内存时堆空间是可以向高地址增长的。堆空间的地址上限(0x09497000)称为Break,堆空间要向高地址增长就要抬高Break,映射新的虚拟内存页面到物理内存,这是通过系统调用brk实现的,malloc函数也是调用brk向内核请求分配内存的。0xbfac 5000-0xbfad a000是栈空间,其中高地址的部分保存着进程的环境变量和命令行参数,低地址的部分保存函数栈帧,栈空间是向低地址增长的,但显然没有堆空间那么大的可供增长的余地,因为实际的应用程序动态分配大量内存的并不少见
第一,虚拟内存管理可以控制物理内存的访问权限。物理内存本身是不限制访问的,任何地址都可以读写,而操作系统要求不同的页面具有不同的访问权限第二,虚拟内存管理最主要的作用是让每个进程有独立的地址空间。所谓独立的地址空间是指,不同进程中的同一个VA被MMU映射到不同的PA,并且在某一个进程中访问任何地址都不可能访问到另外一个进程的数据第三,VA到PA的映射会给分配和释放内存带来方便,物理地址不连续的几块内存可以映射成虚拟地址连续的一块内存第四,一个系统如果同时运行着很多进程,为各进程分配的内存之和可能会大于实际可用的物理内存,虚拟内存管理使得这种情况下各进程仍然能够正常运行。因为各进程分配的只不过是虚拟内存的页面,这些页面的数据可以映射到物理页面,也可以临时保存到磁盘上而不占用物理页面,在磁盘上临时保存虚拟内存页面的可能是一个磁盘分区,也可能是一个磁盘文件,称为交换设备(Swap Device)系统中可分配的内存总量 = 物理内存的大小 + 交换设备的大小
从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理
1. make仍然尝试更新缺省目标,首先检查目标main是否需要更新,这就要检查三个条件main.o、stack.o和maze.o是否需要更新。2. make会进一步查找以这三个条件为目标的规则,然后发现main.o和maze.o需要更新,因为它们都有一个条件是maze.h,而这个文件的修改时间比main.o和maze.o晚,所以执行相应的命令更新main.o和maze.o。3. 既然main的三个条件中有两个被更新过了,那么main也需要更新,所以执行命令gccmain.ostack.o maze.o -o main更新main。main: main.o stack.o maze.ogcc main.o stack.o maze.o -o mainmain.o: main.c main.h stack.h maze.hgcc -c main.cstack.o: stack.c stack.h main.hgcc -c stack.cmaze.o: maze.c maze.h main.hgcc -c maze.c
all,执行主要的编译工作,通常用作缺省目标。install,执行编译后的安装工作,把可执行文件、配置文件、文档等分别拷到不同的安装目录。clean,删除编译生成的二进制文件。distclean,不仅删除编译生成的二进制文件,也删除其它生成的文件,例如配置文件和格式转换后的文档,执行make distclean之后应该清除所有这些文件,只留下源文件。
=延时展开:=立即展看$@,表示规则中的目标。$<,表示规则中的第一个条件。$?,表示规则中所有比目标新的条件,组成一个列表,以空格分隔。$^,表示规则中的所有条件,组成一个列表,以空格分隔。
$ gcc -MM *.cmain.o: main.c main.h stack.h maze.hmaze.o: maze.c maze.h main.hstack.o: stack.c stack.h main.h
all: mainmain: main.o stack.o maze.ogcc $^ -o $@clean:-rm main *.o.PHONY: cleansources = main.c stack.c maze.cinclude $(sources:.c=.d)%.d: %.cset -e; rm -f $@; \$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \rm -f $@.$$$$
1. 一开始堆空间由一个空闲块组成,长度为7×8=56字节,除头节点之外的长度为48字节。
2. 调用malloc分配8个字节,要在这个空闲块的末尾截出16个字节,其中新的头节点占了8个字节,另外8个字节返回给用户使用,注意返回的指针p1指向头节点后面的内存块。
3. 又调用malloc分配16个字节,又在空闲块的末尾截出24个字节,步骤和上一步类似。
4. 调用free释放p1所指向的内存块,内存块(包括头节点在内)归还给了malloc,现在malloc管理着两块不连续的内存,用环形链表串起来。注意这时p1成了野指针,指向不属于用户的内存,p1所指向的内存地址在Break之下,是属于当前进程的,所以访问p1时不会出现段错误,但在访问p1时这段内存可能已经被malloc再次分配出去了,可能会读到意外改写数据。另外注意,此时如果通过p2向右写越界,有可能覆盖右边的头节点,从而破坏malloc管理的环形链表,malloc就无法从一个空闲块的指针字段找到下一个空闲块了,找到哪去都不一定,全乱套了。
5. 调用malloc分配16个字节,现在虽然有两个空闲块,各有8个字节可分配,但是这两块不连续,malloc只好通过brk系统调用抬高Break,获得新的内存空间。在[
K&R]的实现中,每次调用sbrk函数时申请1024×8=8192个字节,在Linux系统上sbrk函数也是通过brk实现的,这里为了画图方便,我们假设每次调用sbrk申请32个字节,建立一个新的空闲块。
6. 新申请的空闲块和前一个空闲块连续,因此可以合并成一个。在能合并时要尽量合并,以免空闲块越割越小,无法满足大的分配请求。
7. 在合并后的这个空闲块末尾截出24个字节,新的头节点占8个字节,另外16个字节返回给用户。
8. 调用free(p3)释放这个内存块,由于它和前一个空闲块连续,又重新合并成一个空闲块。注意,Break只能抬高而不能降低,从内核申请到的内存以后都归malloc管了,即使调用free也不会还给内核。
全缓冲如果缓冲区写满了就写回内核。常规文件通常是全缓冲的。行缓冲如果用户程序写的数据中有换行符就把这一行写回内核,或者如果缓冲区写满了就写回内核。标准输入和标准输出对应终端设备时通常是行缓冲的。(除了写满缓冲区、写入换行符之外,行缓冲还有两种情况会自动做Flush操作。如果:用户程序调用库函数从无缓冲的文件中读取或者从行缓冲的文件中读取,并且这次读操作会引发系统调用从内核读取数据)无缓冲用户程序每次调库函数做写操作都要通过系统调用写回内核。标准错误输出通常是无缓冲的,这样用户程序产生的错误信息可以尽快输出到设备。
1. as,汇编器2. ld,链接器 用--verbose选项可以显示默认链接脚本3. readelf,读ELF文件信息4. objdump,显示目标文件中的信息,本书主要用它做反汇编5. hexdump,以十六进制或ASCII码显示一个文件6. ar,把目标文件打包成静态库7. ranlib,给ar打包的静态库建索引8. nm,查看符号表
1、以可写的方式fopen一个文件时,如果文件不存在会自动创建,而open一个文件时必须明确指定O_CREAT才会创建文件,否则文件不存在就出错返回。2、以w或w+方式fopen一个文件时,如果文件已存在就截断为0字节,而open一个文件时必须明确指定O_TRUNC才会截断文件,否则直接在原来的数据上改写
超级块(Super Block)描述整个分区的文件系统信息,例如块大小、文件系统版本号、上次mount的时间等等。超级块在每个块组的开头都有一份拷贝。块组描述符表(GDT,Group Descriptor Table)由很多块组描述符组成,整个分区分成多少个块组就对应有多少个块组描述符。每个块组描述符(Group Descriptor)存储一个块组的描述信息,例如在这个块组中从哪里开始是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等块位图(Block Bitmap)块位图就是用来描述整个块组中哪些块已用哪些块空闲的,它本身占一个块,其中的每个bit代表本块组中的一个块,这个bit为1表示该块已用,这个bit为0表示该块空闲可用。inode位图(inode Bitmap)和块位图类似,本身占一个块,其中每个bit表示一个inode是否空闲可用。inode表(inode Table)每个文件都有一个inode,一个块组中的所有inode组成了inode表。数据块(Data Block)对于常规文件,文件的数据存储在数据块中。对于目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它所在目录的数据块中,除文件名之外,ls -l命令看到的其它信息都保存在该文件的inode中。注意这个概念:目录也是一种文件,是一种特殊类型的文件。对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。设备文件、FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。
mke2fs [-cFMqrSvV][-b <区块大小>][-f <不连续区段大小>][-i <字节>][-N <inode数>][-l <文件 >][-L <标签>][-m <百分比值>][-R=<区块数>][ 设备名称][区块数]-b<区块大小> 指定区块大小,单位为字节。-c 检查是否有损坏的区块。-f<不连续区段大小> 指定不连续区段的大小,单位为字节。-F 不管指定的设备为何,强制执行mke2fs。-i<字节> 指定"字节/inode"的比例。-N<inode数> 指定要建立的inode数目。-l<文件> 从指定的文件中,读取文件西中损坏区块的信息。-L<标签> 设置文件系统的标签名称。-m<百分比值> 指定给管理员保留区块的比例,预设为5%。-M 记录最后一次挂入的目录。-q 执行时不显示任何信息。-r 指定要建立的ext2文件系统版本。-R=<区块数> 设置磁盘阵列参数。-S 仅写入superblock与group descriptors,而不更改inode able inode bitmap以及block bitmap-v 执行时显示详细信息。-V 显示版本信息。
#include <unistd.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>#include <stdlib.h>#include <string.h>int main(void){int fd, save_fd;char msg[] = "This is a test\n";fd = open("somefile", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);if(fd<0) {perror("open");exit(1);}save_fd = dup(STDOUT_FILENO);dup2(fd, STDOUT_FILENO);close(fd);write(STDOUT_FILENO, msg, strlen(msg));dup2(save_fd, STDOUT_FILENO);write(STDOUT_FILENO, msg, strlen(msg));close(save_fd);return 0;}
进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数进程的状态,有运行、挂起、停止、僵尸等状态进程切换时需要保存和恢复的一些CPU寄存器描述虚拟地址空间的信息描述控制终端的信息当前工作目录(Current Working Directory)umask掩码文件描述符表,包含很多指向file结构体的指针和信号相关的信息用户id和组id控制终端、Session和进程组进程可以使用的资源上限(Resource Limit)
int main(void){extern char **environ;int i;for(i=0; environ[i]!=NULL; i++)printf("%s\n", environ[i]);return 0;}
char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);execv("/bin/ps", ps_argv);execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);execve("/bin/ps", ps_argv, ps_envp);execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);execvp("ps", ps_argv);
1. 如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。2. 如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。3. 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。在第 33 章信号会讲到怎样使SIGPIPE信号不终止进程。4. 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回
1. 交互Shell(bash)fork/exec一个子Shell(sh)用于执行脚本,父进程bash等待子进程sh终止。2. sh读取脚本中的cd ..命令,调用相应的函数执行内建命令,改变当前工作目录为上一级目录。3. sh读取脚本中的ls命令,fork/exec这个程序,列出当前工作目录下的文件,sh等待ls终止。4. ls终止后,sh继续执行,读到脚本文件末尾,sh终止。5. sh终止后,bash继续执行,打印提示符等待用户输入。
环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给fork出来的子进程。用printenv命令可以显示当前Shell进程的环境变量。
只存在于当前Shell进程,用set命令可以显示当前Shell进程中定义的所有变量(包括本地变量和环境变量)和函数。
$0
相当于C语言main
函数的argv[0]
$1
、$2
...位置参数(Positional Parameter),相当于C语言main
函数的argv[1]
、argv[2]
...$#
相当于C语言main
函数的argc - 1
,注意这里的#
后面不表示注释$@
表示参数列表"$1" "$2" ...
,例如可以用在for
循环中的in
后面。$?
上一条命令的Exit Status$$
当前Shell的进程号用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SIGINT信号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。一个进程调用kill(2)函数可以发送信号给另一个进程。可以用kill(1)命令发送信号给某个进程,kill(1)命令也是调用kill(2)函数实现的,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。
1. 用户程序注册了SIGQUIT信号的处理函数sighandler。2. 当前正在执行main函数,这时发生中断或异常切换到内核态。3. 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。4. 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。5. sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。6. 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。
调用了malloc或free,因为malloc也是用全局链表来管理堆的。调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。一个线程可以调用pthread_cancel终止同一进程中的另一个线程。线程可以调用pthread_exit终止自己。
- linux C的学习笔记
- Linux C学习笔记
- Linux C 学习笔记
- linux c学习笔记
- Linux C语言的学习笔记
- Linux下的C学习笔记
- 【学习笔记】linux下的c语言的学习
- Linux c 学习笔记(一)
- Linux C语言学习笔记
- linux & C++Primer 学习笔记
- linux c/c++学习笔记
- linux学习笔记-C语言
- Linux &C 学习笔记2
- C的学习笔记
- linux & C++Primer 学习笔记--预处理器的简单介绍
- linux c 简单的ls程序编写 学习笔记
- Linux-C学习笔记-一劳永逸的顺序表
- Linux-C学习笔记--单链表的“浮云”操作
- 【LeetCode】 Best Time to Buy and Sell Stock III
- Android为CheckBox设置Style
- Perl 使用管道实现进程间的通信
- EPP 修改字体大小
- A、B两个整数集合,设计一个算法求他们的交集,尽可能的高效
- linux C的学习笔记
- Web服务请求异步化
- 指向Class Member的指针
- 线程同步之信号量("旗帜"就是方向,"旗帜"就是形象, "旗帜"就是指挥棒)
- pdf online document view
- 搭建nginx+php+mysql的环境
- [转]Ubuntu9.10下安装配置tftp服务器
- 安装gcc
- TCP状态转移图学习总结