语言基础
C语言的malloc怎么使用
C语言中malloc是动态分配内存的函数,参数是分配的字节数,分配失败返回空指针NULL,返回成功返回指向被分配内存的指针,数据类型是void* 可以指向任何类型的数据,因此在必要时需要进行类型转换。在申请完空间之后要使用free对内存进行释放。
Malloc与calloc,realloc的区别
void* realloc(void* ptr, unsigned newsize); void* malloc(unsigned size); void* calloc(size_t numElements, size_t sizeOfElement); malloc是申请一块大小为N的内存,返回首地址 calloc是申请n块长度为“size”字节的连续区域,返回首地址 realloc将ptr的内存大小增大到size
宏定义与内联函数的区别
宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。
内联函数
inline 对编译器来说只是个建议,取决于编译器。有点像宏但是比宏更安全,会做安全检查或自动类型转换。 相当于把内联函数里面的内容写在调用内联函数处。 没有函数调用的开销,但是会造成膨胀,占内存。
strlen 和 sizeof 区别
1.sizeof 是运算符,并不是函数,结果在编译时得到而非运行中获得;strlen 是字符处理的库函数。 2.sizeof 参数可以是任何数据的类型或者数据(sizeof 参数不退化);strlen 的参数只能是字符指针且结尾是’\0’的字符串。 3.因为 sizeof 值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小。 Strlen是计算字符串的长度,不包括\0,sizeof是计算空间大小。
什么是虚函数,什么是纯虚函数,什么是抽象类?
虚函数是指一个类中希望重载的成员函数,当用基类指针或引用指向一个继承类对象时,调用一个虚函数,实际调用的是继承类的版本。
纯虚函数是一个在基类中说明的虚函数,它在基类里没有定义,要求任何派生类型都定义自己的版本。纯虚函数为各派生类提供一个公共接口,从基类继承来的纯虚函数,在派生类中仍是虚函数。
在C++中,含有纯虚函数的类称为抽象类,它不能生成对象。抽象类是不完整的,它只能用作基类。
虚函数的实现条件与原理
条件: 1.要有虚函数 2.虚函数的实现是依靠父类指针指向一个子类实例,然后通过父类指针调用子类的成员函数。 原理: 虚函数表,如果子类中有虚函数重写了父类的虚函数,对于派生类的实例,他的虚函数表结构如下: 重写虚函数,父类虚函数,子类虚函数,当我们通过父类指针调用重写虚函数时,父类指针的虚函数表中指向的虚函数的地址已被重写后的函数地址替换,所以实现了多态。
析构函数没有声明为虚析构函数的后果
当派生类对象经由一个基类指针被删除,而该基类带着一个non-virtual析构函数(即非虚析构函数),其结果是未有定义的,实际在执行时通常会发生的是对象的派生成分没有被销毁(即派生类的析构函数没有执行),这样就造成基类成分被销毁了,但是派生类成分没有被销毁,可能会形成资源泄漏、败坏之数据结构,在调试器上浪费很多时间进行调试。
内存泄漏的原因
内存泄露的原因
1.在类的构造函数和析构函数中没有匹配的调用new和delete函数
2.没有正确地清除嵌套的对象指针
3.在释放对象数组时在delete中没有使用方括号 方括号是告诉编译器这个指针指向的是一个对象数组,同时也告诉编译器正确的对象地址值并调用对象的析构函数,如果没有方括号,那么这个指针就被默认为只指向一个对象,对象数组中的其他对象的析构函数就不会被调用,结果造成了内存泄露。如果在方括号中间放了一个比对象数组大小还大的数字,那么编译器就会调用无效对象(内存溢出)的析构函数,会造成堆的奔溃。如果方括号中间的数字值比对象数组的大小小的话,编译器就不能调用足够多个析构函数,结果会造成内存泄露。 释放单个对象、单个基本数据类型的变量或者是基本数据类型的数组不需要大小参数,释放定义了析构函数的对象数组才需要大小参数。
4.指向对象的指针数组不等同于对象数组 对象数组是指:数组中存放的是对象,只需要delete []p,即可调用对象数组中的每个对象的析构函数释放空间 指向对象的指针数组是指:数组中存放的是指向对象的指针,不仅要释放每个对象的空间,还要释放每个指针的空间,delete []p只是释放了每个指针,但是并没有释放对象的空间,正确的做法,是通过一个循环,将每个对象释放了,然后再把指针释放了。
5.缺少拷贝构造函数
6.没有将基类的析构函数定义为虚函数
深拷贝和浅拷贝
浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针
堆和栈的区别
1)堆和栈中的存储内容:栈存局部变量、函数参数等。堆存储使用new、malloc申请的变量等;
2)申请方式:栈内存由系统分配,堆内存由自己申请;
3)申请后系统的响应:栈——只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 堆——首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表 中删除,并将该结点的空间分配给程序;
4)申请大小的限制:Windows下栈的大小一般是2M,堆的容量较大;
5)申请效率的比较:栈由系统自动分配,速度较快。堆使用new、malloc等分配,较慢;
总结:栈区优势在处理效率,堆区优势在于灵活;
New和malloc区别?
1.malloc 和 free 都是函数。 new 和 delete 是C++ 的运算符!
2.malloc 用 分配内存不会自动调用构造函数, new 就会。
3.malloc 分配的空间的大小必须指定, new会自动分配。
4.malloc 和 new 分配的内存都在堆上面。
5.malloc 分配空间失败会返回 空指针NULL 而 new 分配失败了会抛出std::bad_alloc异常。
6.不管是new 或者 malloc 分配的空间之后, 都应该记得释放分配的内存。 因为系统不会自动释放你申请的空间, 空间在堆上面。
C和C++的内存分区:
1)、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其 操作方式类似于数据结构中的栈。 2)、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 3)、静态存储区(static)存放全局变量和静态变量的存储区,初始化的变量放在初始化区,未初始化的变量放在未初始化区。在程序结束后释放这块空间 一块区域。 - 程序结束后由系统释放。 4)、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放。 5)、程序代码区—存放源程序的二进制代码。
static,static局部变量?生命周期?static关键字(全局,局部,成员变量,成员函数)
静态局部变量:用于函数体内部修饰变量,这种变量的生存期长于该函数。 存在的意义就是随着第一次函数的调用而初始化,却不随着函数的调用结束而销毁。在第一次调用的时候初始化,且只初始化一次,也就是你第二次调用,不会继续初始化,而会直接跳过。 那么我们总结一下,静态局部变量的特点(与局部变量的对比): (1)该变量在全局数据区分配内存(局部变量在栈区分配内存); (2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化(局部变量每次函数调用都会被初始化); (3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0(局部变量不会被初始化); (4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,也就是不能在函数体外面使用它(局部变量在栈区,在函数结束后立即释放内存);
2.静态全局变量:定义在函数体外,用于修饰全局变量,表示该变量只在本文件可见。 静态全局变量不能被其它文件所用(全局变量可以); 其它文件中可以定义相同名字的变量,不会发生冲突(自然了,因为static隔离了文件,其它文件使用相同的名字的变量,也跟它没关系了);
3.静态函数:准确的说,静态函数跟静态全局变量的作用类似: 1.静态函数不能被其它文件所用; 2.其它文件中可以定义相同名字的函数,不会发生冲突;
4.静态数据成员:用于修饰 class 的数据成员,即所谓“静态成员”。这种数据成员的生存期大于 class 的对象(实体 instance)。静态数据成员是每个 class 有一份,普通数据成员是每个 instance 有一份,因此静态数据成员也叫做类变量,而普通数据成员也叫做实例变量。 对于非静态数据成员,每个类对象(实例)都有自己的拷贝。而静态数据成员被当作是类的成员,由该类型的所有对象共享访问,对该类的多个对象来说,静态数据成员只分配一次内存。 静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。
5.静态成员函数:用于修饰 class 的成员函数 1.静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数; 2.非静态成员函数可以任意地访问静态成员函数和静态数据成员; 3.静态成员函数不能访问非静态成员函数和非静态数据成员; 4.调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以用类名::函数名调用(因为他本来就是属于类的,用类名调用很正常)
重载 重写区别
重载 函数名相同,函数参数,函数参数个数,函数参数顺序至少有一个不同。函数的返回值类型可以相同也可以不同,发生在一个类的内部,不能跨作用域。 重写,一般发生在子类和父类继承关系之间。子类重新定义父类中有相同名称和参数的虚函数。
指针和引用区别
(1)当引用被创建时,它必须被初始化。而指针则可以在任何时候被初始化。 (2)一旦一个引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用。而指针则可以在任何时候指向另一个对象。 (3)不可能有NULL引用。必须确保引用是和一块合法的存储单元关联。
智能指针
auto_ptr 管理权的转移 ->不建议使用 unique_ptr 防拷贝 ->简单粗暴 shared_ptr 利用引用计数,每有一个指针指向相同的一片内存时,引用计数+1,每有一个指针取消指向一片内存时,引用计数-1.为0时释放内存 week_ptr 弱指针 ->辅助shared_ptr解决循环引用的问题。
悬挂指针和野指针
野指针指申请了没有初试化的指针。 悬挂指针指曾经初始化过的指针,但是delete了指针指向的内容,该指针未置空。
指针函数 函数指针
指针函数: 顾名思义,它的本质是一个函数,不过它的返回值是一个指针。 函数指针 的本质是一个指针,该指针的地址指向了一个函数,所以它是指向函数的指针。 int *fun(int x,int y); 指针函数 int (*fun) (int x, int y) 函数指针
区别就是括号的优先级 定义一个f Int * f(int x, inty); -----------1 Int (*f)(int x, int y); -----------2 1中的f是一个函数,所以f是一个返回值为指针的函数。 2中得f是一个指针,所以f是一个指向函数的指针。
操作系统
进程与线程
1.区别:进程是资源分配的单元,线程是是资源调度的单元 进程好比一栋房子(可以有很多房间–线程,所以关系是1:n,一个线程仅属于一栋房子); 房间自己有独立空间,但是有公摊-公共空间(比如客厅)–共享地址空间
线程自己基本上不拥有系统资源,但是他可与同属一个进程的其他线程共享进程所拥有的全部资源。由于线程比进程小,基本上不拥有系统资源,线程上下文切换比进程上下文切换快得多,故对他的调度所付出的开销就会小很多,从而显著提高系统资源的利用率和吞吐量。
2.进程和线程在通信方式 线程之间有共享空间-距离”近“。通信方式–互斥锁、条件变量、读写锁、信号量等等
进程之间的通信,需要跨空间,方式较少,进程5种–socket,管道,共享内存,消息队列,信号量 常考要对比这五种方式–不同的方式: 3.为何说进程是资源分配(地址空间给房子的,分配管理地址是以房子为单位) 为何说线程是资源调度的单位(在某个房间内执行任务,该忙完之后给另外一个房间,不同房间之间按照优先级竞争调度) 4.执行效率,进程快还是线程快?(开放题目,系统是怎么根据任务级别去调度,资源节省的是什么) 地址、数据指令:线程距离进切换快节省时间,进程是需要发现自己找的东西不在自己房子的所有房间内,需要通过比如管道去寻找另外其他的房子的房间内所有信息是否有东西,所有耗时大。 两者的差异在于地址切换是开销;同在一个地址空间内不存在太多的地址切换; 所以大部分线程是比进程更轻量级的处理;(当然,在那种一房子仅有一个房间,那么两种是没有本质差别的) 5.线程开销低?能否尽量少用进程? 代码健壮性,狡兔三窟,万一一个房子着火了就系统崩溃了;比如守护进程非常关键; 房子独立,便于资源独立和保密资源; 6.用进程还是线程的三个方面 某个任务对当前的进程关系大不大? 该任务 某崩溃了对原来的影响怎么样 该任务的执行效率是否很重要–重要选择线程 问题:Linux内核算是一个进程还是线程? 是进程:该进程有诸多内核线程存在?????
进程间通信方式(详细)
(1)管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。 (2)信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。 (3)消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。 (4)共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。 (5)信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。 (6)套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛
线程间通信方式的简要介绍
信号量:相当于初始化为N的互斥量,可以规定同时访问一块共享资源的最大线程数。 读写锁:读时共享,写时独占;写的优先级高;锁只有一把(读方式和写方式) 条件变量:和互斥锁结合使用。使用条件变量将某一线程处于阻塞状态,一旦条件满足,就会唤醒因为该条件被阻塞的线程。 互斥锁:多个线程要访问同一个共享资源,首先要加锁,得到锁的那一个线程才能访问共享资源。 自旋锁:发生阻塞时,自旋锁会让CPU一直去循环请求锁的权限
谈一下锁机制
为什么有锁机制:多个线程访问同一个共享资源,可能会造成各线程之间相互覆盖共享资源,造成数据访问不一致的现象。为了避免这种现象,就需要锁机制。
死锁条件:首先有线程和资源,每个线程都在等待其中一个资源,但所有的资源都被占用。所有的线程都在相互等待,但他们永远不会释放已经占有的资源,于是任何线程都无法继续,死锁发生。
死锁的四个条件
产生死锁的必要条件: 1.互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。 2.请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。 3.不可剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。 4.环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。
死锁的两个实例
- 对一个锁加锁两次
- 线程A拥有锁1,线程B拥有锁2,线程A请求锁2,线程B请求锁1.
Linux文件类型
1.普通文件 2.目录文件 3.链接文件 4.设备文件 5.管道文件
用户态和内核态
1.区别(执行权限区分) (1).两者以资源操作权限为区分,执行权限不一样,相互协作,用户态无法调度内核态,内核态也不干涉用户态 (2)运行空间不一样:标准Linux0-3G是一个作为有用户态。3-4G内核态作为使用;用户态共享内核态; 两者不可以互相直接访问,如果访问会出现访问非法地址或内核崩溃!
2.内核态可以通过什么方式访问用户态? 临时改变内核空间,3-4G编程0-4G改变,访问完恢复改变; 用户态正常工作是如何进入内核态? 系统调用(不是C的库调用), 中断(保存现场,进入中断上下文,上下文是处在内核态返回), 异常(会发信息给内核)
3.两者如何通讯?(I/Ocontrol,sysctl动态修改内核参数,socket,procfs进程文1.API函数,get_user(x, ptr) 在内核中被调用,获取用户空间指定地址的数值并保存到内核变量x中。 put_user(x, ptr) 在内核中被调用,将内核空间的变量x的数值保存到到用户空间指定地址处。 2. proc文件系统 3.netlink 用户态和内核态之间的进程间通信。 4.使用文件,当处于内核空间的时候,直接操作文件,将想要传递的信息写入文件,用户空间读取这个文件就可以得到想要的数据了。 5.mmap系统调用,将内核空间的地址映射到用户空间。
进程状态
进程状态: 初始态,就绪态,运行态,挂起态与终止态。
线程状态: 就绪:准备等待可用的CPU资源,其他条件一切准备好。当线程被pthread_create创建时或者阻塞状态结束后就处于准备状态。 运行:线程已经获得CPU的使用权,并且正在运行,在多核心的机器中同时存在多个线程正在运行。如果这种情况不加以控制,会造成整个程序没响应。 阻塞:指一个线程在执行过程中暂停,以等待某个条件的触发。 例如线程可能在处理有关I/O的任务,可能I/O设备繁忙尚未响应或没有可用的I/O缓存。 也可能当前线程等待一个可用的条件便来变量。 错误地对一个已被锁住的互斥量加锁 调用sigwait等待尚未发生的信号。 终止:线程已经从回调函数中返回,或者调用pthread_exit返回,或者被强制终止。
ps aux 和top
ps命令是系统在过去执行的进程的静态快照 top命令反应的是系统进程动态信息,默认10s更新一次
驱动是如何加载的?
动态加载 编译驱动程序,然后添加 静态加载 在内核中添加删除驱动,重新编译内核
动态链接和静态链接的优缺点,链接库位于哪个存储区 重写与重载有什么区别? static的作用? static函数能不能访问其他非static的变量?为什么?
虚拟内存机制
在早期的计算机中,没有虚拟内存的概念,我们要运行一个程序,要把程序全部装入内存,然后运行,在运行多个程序后,会出现很多问题。由于指令都是直接访问物理内存的,那么进程就可以修改其他进程的数据,甚至会修改内核地址空间的数据,而内存是随机分配的,所以程序运行的地址也是不正确的。 为了解决这个问题,虚拟内存就被创造出来, 对于32位的系统,每一个进程运行时都会得到4G的虚拟内存,进程认为他得到的这4G的虚拟空间是一个连续的地址空间,实际上,它通常是被分隔成多个物理内存碎片,还有一部分存储在外部磁盘存储器上,在需要时进行数据交换。 每次程序要访问地址空间上的某一个地址,都需要把地址翻译为实际物理内存地址 所有进程共享这整一块物理内存,每个进程只把自己目前需要的虚拟地址空间映射到物理内存上 进程需要知道哪些地址空间上的数据在物理内存上,哪些不在,还有在物理内存上的哪里,这就需要通过页表来记录。 页表的每一个表项分为两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)。 当进程访问某个虚拟地址的时候,就会先看页表,如果发现对应的数据不在物理内存上,就会发生缺页异常。 发生缺页异常时,操作系统立即阻塞该进程,并将硬盘里对应的页换入内存,然后使该进程就绪,如果内存已经满了,没有空地方了,那就由操作系统决定覆盖某页。
孤儿进程、僵尸进程
孤儿进程: 一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。 僵尸进程: 僵尸进程: 子进程死亡,而父进程没有进行回收,此时进程变为僵尸态,解决 kill 该进程。
linux中遇到文件无法删除怎么解决
普通的删除指令 rm -rf file.sh 如果遇到无法删除 先查看文件属性 lsattr i 属性设置之后可使文件不能被删除、改名,设置连接也无法写入或添加数据,只有 root 用户才能设置 a 属性设置之后,文件只能增加数据,既不能删除也不能修改数据,只有 root 用户才能设置 如果被设置为 a或 i 属性 首先要修改文件属性 chattr -ia 修改属性即可后删除。
linux基本命令
ls 列出当前文件夹下目录项 ls –l 显示目录项的详细信息 cd 打开摸个目录 cd … 打开上一个目录 cd ~ 打开当前用户目录 cd / 进入根目录 ps aux 查看所有进程 top 任务管理器 top后按数字键盘1 查看每个逻辑cpu的状态 x 排序高亮 shift+< 或shift+> 切换排序关键字。 Shift + m 根据内存大小排序 shift + p 根据CPU占用排序 pwd 查看当前目录 mkdir 新建目录 touch 常见空文件 rm 删除文件 rmdir 删除目录 chmod 改变文件状态 shutdown –h now 立即关机 tree 查看当前文件结构树 cat 查看文件内容 cat filename tac 倒叙查看文件内容 tac filename mv file1 file2 location 将文件1和文件2移动到目标位置 cp filename dirname 复制文件到目录 cp filename1 filename2 复制文件1并重命名为文件2 cat filename 查看文件内容 tac filename 逆转查看文件内容 软链接就像windows下的快捷方式 Linux下的软链接行为和windows下的快捷方式差不多,但是如果是用相对路径创建的软链接,在软链接移动之后就会失效,无法访问。这一点和windows快捷方式不同,windows快捷方式随便放哪里都行。 chmod 修改权限操作
查找文件中语句所在的那一行
grep命令 文件内容为
在test.txt文件中,筛选包含mother的行 grep mother test.txt
在test.txt文件中,筛选不包含mother的行,加上-v参数即可: grep -v mother test.txt
Linux中断的响应流程
处理器收到来自中断控制器的中断处理请求,保存中断上下文,跳转到中断对应的处理处,(快速完成中断中断上半部,中断上半部返回后执行中断下半部。如果做了上下半部处理的话),中断处理函数返回时恢复现场。
Linux系统启动流程
1).开机bios自检,加载硬盘 2).读取MBR,进行MBR引导 3).boot loader 4).加载内核 kernel 5).启动init进程,依据inittab文件设定运行级别 6).init进程,执行rc.sysinit文件 7).启动内核模块,执行不同级别的脚本程序 8).执行/etc/rc.d/rc.local 9).启动mingetty,进入系统登录界面
编译需要哪几步,那几步的作用
1:预编译:预编译做的事情为:把伪指令转换为实际指令命令 gcc –E a:#define a b b:#条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等 c:#include 头文件加入到编译的文件中 d:一些符号处理如file local 等等;
2:编译 命令是 gcc -S 把预编译好的文件逐条转化为汇编语言 优化阶段,经过预编译得到的输出文件中,只有常量;如数字、字符串、变量的定义, 以及c语言的关键字,如main,if,else,for,while,{,}, +,-,*,\等等。 编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后, 将其翻译成等价的中间代码表示或汇编代码。如下都是汇编代码;操作寄存器
3.汇编 命令gcc -c 将.c文件直接编译成.o的二进制文件:
4:链接 命令是 gcc *.c 链接命令是ld 链接的时候要考虑代码和数据所要放的内存位置,可以通过链接脚本来设置(这里下次课再说)
根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:
(1)静态链接
在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
(2) 动态链接
在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
GCC 编译参数
-lpthread 链接阶段,链接这个库 -lm 链接数学库 如果你的程序中使用dlopen、dlsym、dlclose、dlerror 显示加载动态库,需要设置链接选项 –ldl
-c 只编译并生成目标文件。 -g 生成调试信息。GNU 调试器可利用该信息。 -o FILE 生成指定的输出文件。用在生成可执行文件时。 -O0 不进行优化处理。 -O 或 -O1 优化生成代码。 -O2 进一步优化。 -O3 比 -O2 更进一步优化,包括 inline 函数。 -w 不生成任何警告信息。
GDB调试
基础指令: -g:使用该参数编译可以执行文件,得到调试表。
gdb ./a.out
list: list 1 列出源码。根据源码指定 行号设置断点。
b: b 20 在20行位置设置断点。
run/r: 运行程序
n/next: 下一条指令(会越过函数)
s/step: 下一条指令(会进入函数)
p/print:p i 查看变量的值。
continue:继续执行断点后续指令。
finish:结束当前函数调用。
quit:退出gdb当前调试。
bt:打印堆栈信息
info break 查看断点
GDB多线程调试
- Makefile 上加上 -g参数生成可调试信息,可以进行调试
- pthread不是linux下默认的库,也就是连接的时候,无法找到pthread库中函数的入口地址,连接会失败,在makefile中加上-lpthread参数即可解决。
- gdb text 进入调试
- l 15 列出15行源码
- Info thread 可以查看被调试的线程。显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID。 前面有*的是当前调试的线程。
- set scheduler-locking off|on|step
在使用step或continue命令调试当前被调试线程的时候,其他线程也是同时执行的,如果我们只想要被调试的线程执行,而其他线程停止等待,那就要锁定要调试的线程,只让它运行。 off:不锁定任何线程,所有线程都执行。 on:只有当前被调试的线程会执行。 step:阻止其他线程在当前线程单步调试的时候抢占当前线程。只有当next、continue、util以及finish的时候,其他线程才会获得重新运行的。
Select 、poll、epoll 区别
1、支持一个进程所能打开的最大连接数 select 单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是3232,同理64位机器上FD_SETSIZE为3264),当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。 poll poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的 epoll 虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接 2、FD剧增后带来的IO效率问题 select 因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。 poll 同上 epoll 因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。 3、 消息传递方式 select 内核需要将消息传递到用户空间,都需要内核拷贝动作 poll 同上 epoll epoll通过内核和用户空间共享一块内存来实现的。 4.移植性 Select 全平台 Epoll只能在linux平台使用
多线程与多进程
多进程并发服务器的设计思路
- socket
- bind
- listen
- while(1)
{ Accept Fork If(pid = 0) { 关闭lfd 读写操作 } If(pid > 0) { Close(cfd) Continue; } }
多线程并发服务器的设计思路
- socket
- bind
- listen
- while(1)
{ Cfd = accept Pthread_create Pthread_detach } //子线程 {
读写操作 Close(cfd) 线程退出 }
子线程,父线程分辨
Int pid = Fork() Pid > 0 父进程 Pid = 0 子进程
多个客户端和一个服务端连接的情况
多进程 多线程
Select 1.Socket 2.Bind 3.listen Fd_set rset allset FD_ZERO(r_set) FD_SET(lfd, r_set) While(1) { R_set = all_set Ret = select(最大文件描述符 + 1, &r_set) If(r_set > 0) { If(isset(lfd)) { Cfd = accept Fd_set } For(int I = lfd + 1; I < 最大文件描述符; i++) { Fd_isset(I, &r_set) { 读写 }
} } } Poll Epoll
具体讲一讲socket编程的步骤
TCP:
S:
Socket
Bind
Listen
Accept
{
Read
write
}
Close
C:
Socket
Bind
Connect
{
Read
write
}
close
UDP:
S:
Socket
Bind
Listen
{
Recfrom
sendto
}
Close
C:
Socket
{
Sendto
Recfrom
}
Close
计算机网络
OSI参考模型有几层
TCP/IP是一套用于网络通信的协议集合或者系统。TCP/IP协议模型就有OSI模型分为7层。但其实一般我们所谈到的都是四层的TCP/IP协议栈。
网络7层,每一层的作用
物理层:二进制的形式在物理介质上传输数据。
数据链路层:在物理层提供的比特流的基础上,通过差错控制、流量控制方法,使有差错的物理线路变为无差错的数据链路,即提供可靠的通过物理介质传输数据的方法。(传输有地址的帧,错误检测功能)
网络层:在数据链路层提供的两个相邻端点之间的数据帧的传送功能上,进一步管理网络中的数据通信,控制数据链路层与传输层之间的信息转发,建立、维持和终止网络的连接,将数据设法从源端经过若干个中间节点传送到目的端(点到点),从而向传输层提供最基本的端到端的数据传输服务。(为数据包选择路由)
传输层:向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输,同时向高层屏蔽下层数据通信的细节,即向用户透明地传送报文。(提供端到端的接口)
会话层:组织和协调两个会话进程之间的通信 ,并对数据交换进行管理。
表示层:对上层信息进行变换,保证一个主机应用层信息被另一个主机的应用层程序理解,表示层的数据转换包括数据的加密、压缩、格式转换。
应用层:为特定应用程序提供数据传输服务
TCP和UDP的区别
1.基于连接与无连接 2.TCP要求系统资源较多,UDP较少; 3.UDP程序结构较简单 4.流模式(TCP)与数据报模式(UDP); 5.TCP保证数据正确性,UDP可能丢包 6.TCP保证数据顺序,UDP不保证 具体编程时的区别 1.socket()的参数不同 2.UDP Server不需要调用listen和accept 3.UDP收发数据用sendto/recvfrom函数 4.TCP:地址信息在connect/accept时确定 5.UDP:在sendto/recvfrom函数中每次均需指定地址信息 6.UDP:shutdown函数无效
TCP协议如何保证可靠传输
- 数据被分割成TCP认为最合适发送的数据块。
- TCP给每个数据包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
- 校验和,目的是检测数据在传输过程中的任何变化,如果收到的校验和有差错,TCP将丢弃这个报文段和不确认收到此报文。
- TCP的接收端会丢弃重复的数据
- 流量控制:TCP连接的每一端都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据,当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。
- 拥塞控制:当网络拥塞时,减少数据发送
- ARQ协议:基本原理是每发完一个分组就停止发送,等待对方确认,在收到确认后再发下一个分组。
- 超时重传:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文。
?心跳包
TCP拥塞控制
慢开始:刚开始发送方的拥塞窗口值为1,发送窗口大小就等于拥塞窗口,因此刚开始发送方发送1,接收方收到确认报文后,发送方拥塞窗口值变2,之后变4,变8,直到到达上限,开始进入拥塞控制算法 2.拥塞避免:发送超时重传,判断网络可能出现拥塞:采取两个措施1.将慢开始上限更新为发生拥塞时的一般;2.将发送包的值减少为1,并重新开始执行慢开始算法。 3.快重传:使发送方尽快进行重传,而不是等到计数器超时再重传。 当某个数据包在网络中丢失,接收方会不停地针对这个丢失的数据包发送重复确认,当发送三个重复确认时,发送方立即重新发送该数据包 4.快恢复:当发送方接受到三个重复确认时,执行快恢复算法,拥塞窗口变为当前一半,并开始拥塞避免算法。
?TCP沾包,如何避免沾包
?说一下三次握手,四次挥手
?为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
?为为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
假设网络传输状态不好,最后一个ACK可能会丢失,sever如果没有接受到ACK,会重发FIN,2MSL这段时间就是用来接受重新发送的FIN的。 Client在发送出ACK之后进入到time_wait状态,client会设置一个定时器,等待2MSL的时间,如果在该时间段neural再次收到FIN,那么client会重发ACK并再次等待2MSL,如果直到2MSL结束,client都没有再次收到FIN,那么client推断ACK已经被成功接受,则结束TCP连接。
为什么不能用两次握手进行连接?
我们可以假设这样一种场景,如果是两次握手进行连接。客户端向服务器发送一次连接请求,这个请求并没有丢失,而是在网络中停留了很长时间,服务器迟迟没有发出确认应答,客户端重新发出连接请求,服务器应答并建立连接,传输数据后关闭。然后停留在网络中的连接请求又发送给服务器,服务器应答并建立连接,这就会导致不必要的错误和资源浪费。如果是三次握手进行连接,就算第一次滞留在网络中的连接请求发送给服务器,服务器应答,但是客户端并不会再次发送确认,所以不会发生错误。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP有一个保活计时器,每次收到客户端的请求,就会把这个计数器置零,如果等待时间超过一定时间,一般是两个小时,服务器就会开始发送探测报文段,75s一次,连续发送10个报文都没反应,服务器会关闭连接。
嵌入式相关
不能用 sizeof()函数,如何判断操作系统是16位,还是32位
16位系统中,int变量的范围-32768到+32767,32767+1变为-32768。可以利用这个特性来判断。 32767的二进制表示 0111 1111 1111 1111 32767 + 1 为 1000 0000 0000 0000 而-32768在二进制中的补码为 1000 0000 0000 0000 -32768的绝对值为 32768 二进制码为 1000 0000 0000 0000 每位取反 0111 1111 1111 1111 反码加一 1000 0000 0000 0000 刚好就是-32768
大小端模式
0x667788 88为低字节, 66为高字节 大端模式:高地址放低字节 小端模式:低地址放低字节 ARM默认小端,X86小端,C51大端
|