我把qq设置为不可以开数据,禁止手机程序自动运行后台使用,现在想开数据,可是不知道怎么找到设置的步骤红米note2

后使用快捷导航没有帐号?
注册成为酷七会员,享受更多独家内容。
才可以下载或查看,没有帐号?
唉,可能是由于一锁屏后就会自动断网,所以上q时,一旦锁屏了就无法听到好友的消息提示音了,只有当打开手机时重新连网接收消息,这不是坑爹吗?!这样的功能都行?有没有什么方法可以让它后台也可以运行的,毕竟qq是个很基础的应用 吧,只想在手机锁屏时也能接收到信息,听到那熟悉的嘀嘀声......
把推送打开&&数据连接打开
Lv3 即将转粉, 积分 194, 距离下一级还需 166 积分
Lv3 即将转粉
刚才 试了,不行啊
Lv8 专业软粉, 积分 3861, 距离下一级还需 1139 积分
Lv8 专业软粉
如果锁屏前是使用wifi登陆qq的话,锁屏后wifi断掉了就无法收到推送。
所以锁屏前需要用gprs或者3g登陆过qq,锁屏后才会收到推送。
如果必须使用wifi,那么或者一直充着电(锁屏后wifi也不断),或者装一个锁屏不断wifi的应用。
刚才 试过了,上次用wifi,现在用gprs,也打开了消息推送,情况是有消息时屏幕会亮,弹出消息提示框,但是还是不会有声音啊,没有嘀嘀声,一般都是听声音来收到消息的啊&
Lv3 即将转粉, 积分 194, 距离下一级还需 166 积分
Lv3 即将转粉
nothingkit 发表于
如果锁屏前是使用wifi登陆qq的话,锁屏后wifi断掉了就无法收到推送。
所以锁屏前需要用gprs或者3g登陆过qq ...
刚才 试过了,上次用wifi,现在用gprs,也打开了消息推送,情况是有消息时屏幕会亮,弹出消息提示框,但是还是不会有声音啊,没有嘀嘀声,一般都是听声音来收到消息的啊
设置->铃声+声音->为以下各项播放声音
把“其他所有通知”勾上
另外,无论是QQ还是微信的推送,响的声音都是wp7自带的推送声音,不是QQ的嘀嘀声。&
Lv5 初级软粉, 积分 1110, 距离下一级还需 170 积分
Lv5 初级软粉
我的2G卡,任何时候都能顺利推送。
楼主人品合格吗?
可能大家机子不同&
Lv8 专业软粉, 积分 3861, 距离下一级还需 1139 积分
Lv8 专业软粉
火日王 发表于
刚才 试过了,上次用wifi,现在用gprs,也打开了消息推送,情况是有消息时屏幕会亮,弹出消息提示框,但 ...
设置-&铃声+声音-&为以下各项播放声音
把“其他所有通知”勾上
另外,无论是QQ还是微信的推送,响的声音都是wp7自带的推送声音,不是QQ的嘀嘀声。
勾上了,还是木有&
Lv3 即将转粉, 积分 194, 距离下一级还需 166 积分
Lv3 即将转粉
nothingkit 发表于
设置-&铃声+声音-&为以下各项播放声音
把“其他所有通知”勾上
勾上了,还是木有
Lv3 即将转粉, 积分 194, 距离下一级还需 166 积分
Lv3 即将转粉
Zz张 发表于
我的2G卡,任何时候都能顺利推送。
楼主人品合格吗?
可能大家机子不同
Lv5 初级软粉, 积分 1116, 距离下一级还需 164 积分
Lv5 初级软粉
擦 跟卡无关,跟声音亦无故。关闭省电模式!亲
Lv5 初级软粉, 积分 1110, 距离下一级还需 170 积分
Lv5 初级软粉
火日王 发表于
可能大家机子不同
Lv3 即将转粉, 积分 194, 距离下一级还需 166 积分
Lv3 即将转粉
终于明白了,首先要在qq的设置 里打开消息推送,在铃声里勾上“其他所有通知”,在gprs的情况下是可以正常接收消息的,每当 有新的消息时就会有系统的提示音,而且屏幕还会亮,终于可以实现qq的基本功能了,我上次试了,之所以不成功,是因为我在用的是wifi,手机屏幕一旦锁上就会自动 断开wifi的,所以接不了消息,so ga
Lv8 专业软粉, 积分 3861, 距离下一级还需 1139 积分
Lv8 专业软粉
我把常用邮箱都绑到wp7里了,电脑也一直用win8(dp、cp、rp、rtm各个版本一直用过来),结合推送和同步功能,很好用
不过是挺耗流量的,我每月300m的流量刚刚好,不过上个月用得奔放了些,倒数第二天就用完了
Lv3 即将转粉, 积分 194, 距离下一级还需 166 积分
Lv3 即将转粉
我才200m一个月啊
(Lv3 即将转粉)
最专业的超人气windows10论坛
广告投放:&
合作邮箱:&
公司地址:&北京市海淀区上地创业路17号
Powered by Discuz!当前位置: >>
腾讯后台面试题及答案
简单归纳:fd 只是一个整数,在 open 时产生。起到一个索引的作用,进程通过 PCB 中的文 件描述符表找到该 fd 所指向的文件指针 filp。 文件描述符的操作(如: open)返回的是一个文件描述符,内核会在每个进程空间中维护一个文 件描述符表, 所有打开的文件都将通过此表中的文件描述符来引用; 而流(如: fopen)返回的是一个 FILE 结构指针, FILE 结构是包含有文件描述符的,FILE 结构 函数可以看作是对 fd 直接操作的系统调用的封装, 它的优点是带有 I/O 缓存 每个进程在 PCB(Process Control Block)即进程控制块中都保存着一份文件描述符表,文 件描述符就是这个表的索引,文件描述表中每个表项都有一个指向已打开文件的指针,现 在我们明确一下:已打开的文件在内核中用 file 结构体表示,文件描述符表中的指针指向 file 结构体。 linux 和 os: netstat :显示网络状态 Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Memberships) 等等。 从整体上看,netstat 的输出结果可以分为两个部分: 一个是 Active Internet connections,称为有源 TCP 连接,其中&Recv-Q&和&Send-Q&指%0A 的 是接收队列和发送队列。这些数字一般都应该是 0。如果不是则表示软件包正在队列中堆 积。这种情况只能在非常少的情况见到。 另一个是 Active UNIX domain sockets,称为有源 Unix 域套接口(和网络套接字一样,但是 只能用于本机通信,性能可以提高一倍)。 Proto 显示连接使用的协议,RefCnt 表示连接到本套接口上的进程号,Types 显示套接口的类 型,State 显示套接口当前的状态,Path 表示连接到套接口的其它进程使用的路径名。 tcpdump:主要是截获通过本机网络接口的数据,用以分析。能够截获当前所有通过本机网 卡的数据包。它拥有灵活的过滤机制,可以确保得到想要的数据。 用简单的话来定义 tcpdump,就是:dump the traffic on a network,根据使用者的定义对网络 上的数据包进行截获的包分析工具。 tcpdump 可以将网络中传送的数据包的“ 头” 完全截获 下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤,并提供 and、or 、 not 等逻辑语句来帮助你去掉无用的信息。 ipcs:检查系统上共享内存的分配 用途 报告进程间通信设施状态。 ipcs 命令往标准输出写入一些关于活动进程间通信设施的信息。如果没有指定任何标志, ipcs 命令用简短格式写入一些关于当前活动消息队列、共享内存段、信号量、远程队列和 本地队列标题。Linux 下 ipcs 指令的用法详解。ipcs 是 Linux 下显示进程间通信设施状态的工具。可以显示 消息队列、共享内存和信号量的信息。对于程序员可能更有用些,普通的系统管理员一般 用不到此指令。 ipcrm:手动解除系统上共享内存的分配 用途 删除消息队列、信号集、或者共享内存标识。(如果这四个命令没听说过或者不能熟练使用,基本上可以回家,通过的概率较小 ^_^ , 这四个命令的熟练掌握程度基本上能体现面试者实际开发和调试程序的经验) 查看 cpu 信息 #cat /proc/cpuinfo # cat /proc/meminfo 查看硬盘信息 # df -lh 更详细的信息 # cat /proc/scsi/scsi 查看网卡信息 # dmesg | grep eth 更常用的命令(显示系统核心版本号、名称、机器类型等) # uname -a cpu 内存硬盘等等与系统性能调试相关的命令必须熟练掌握,设置修改权限 tcp 网络状态 查看各进程状态抓包相关等相关命令必须熟练掌握 awk sed 需掌握 共享内存的使用实现原理(必考必问,然后共享内存段被映射进进程空间之后,存在于进 程空间的什么位置?共享内存段最大限制是多少?) $sysctl kern.ipc.shmmax kern.ipc.shmmax:
Linux 的 2.2.x 以后的内核版本支持多种共享内存方式,比如:内存映射 mmap、POSIX 共享内 存、System V 共享内存; 共享内存定义:共享内存是最快的可用 IPC(进程间通信)形式。它允许多个不相关的进 程去访问同一部分逻辑内存。共享内存是由 IPC 为一个进程创建的一个特殊的地址范围, 它将出现在进程的地址空间中。其他进程可以把同一段共享内存段“ 连接到” 它们自己的地 址空间里去。所有进程都可以访问共享内存中的地址。如果一个进程向这段共享内存写了 数据,所做的改动会立刻被有访问同一段共享内存的其他进程看到。因此共享内存对于数 据的传输是非常高效的。 共享内存的原理:共享内存是最有用的进程间通信方式之一,也是最快的 IPC 形式。两个 不同进程 A、B 共享内存的意思是,同一块物理内存被映射到进程 A、B 各自的进程地址空 间。进程 A 可以即时看到进程 B 对共享内存中数据的更新,反之亦然。 c++ 进程内存空间分布(注意各部分的内存地址谁高谁低,注意栈从高到低分配,堆从低 到高分配) ELF 是什么?其大小与程序中全局变量的是否初始化有什么关系(注意未初始化的数据放 在 bss 段) Linux ELF ELF = Executable and Linkable Format,可执行连接格式,是 UNIX 系统实验室 (USL )作为应用程序二进制接口(Application Binary Interface,ABI )而开发和发布的。 扩展名为 elf 。工具接口标准委员会 (TIS) 选择了正在发展中的 ELF 标准作为工作在 32 位 INTEL 体系上不同操作系统之间可移植的二进制文件格式。假定开发者定义了一个二进制 接口集合,ELF 标准用它来支持流线型的软件发展。应该减少不同执行接口的数量。因此 可以减少重新编程重新编译的代码。 BSS 段 可执行程序包括 BSS 段、数据段、代码段(也称文本段)。 BSS(Block Started by Symbol )通常是指用来存放程序中未初始化的全局变量和静态变量 的一块内存区域。特点是:可读写的,在程序执行之前 BSS 段会自动清 0。所以,未初始的 全局变量在程序执行之前已经成 0 了。 注意和数据段的区别,BSS 存放的是未初始化的全局变量和静态变量,数据段存放的是初 始化后的全局变量和静态变量。 UNIX 下可使用 size 命令查看可执行文件的段大小信息。如 size a.out。 可执行文件:包含了代码和数据。具有可执行的程序。 可重定位文件:包含了代码和数据(这些数据是和其他重定位文件和共享的 object 文件一起连接时使用的) 共享 object 文件(又可叫做共享库):包含了代码和数据(这些数据是在连接 时候被连接器 ld 和运行时动态连接器使用的)。 使创建共享库容易,使动态装载和共享库的结合更加容易。在 ELF 下,在 C++ 中,全局的构造函数和析构函数在共享库和静态库中用同样方法处理。 使用过哪些进程间通讯机制,并详细说明(重点) makefile 编写,虽然比较基础,但是会被问到 mkdir mf cd mf vim makefile hello.o:hello.c hello.h gcc C c hello.o -Lm make ./hello gdb 调试相关的经验,会被问到 GDB 是 GNU 开源组织发布的一个强大的 UNIX 下的程序调试工具。或许,各位比较喜欢那 种图形界面方式的,像 VC、BCB 等 IDE 的调试,但如果你是在 UNIX 平台下做软件,你 会发现 GDB 这个调试工具有比 VC、BCB 的图形化调试器更强大的功能。所谓“ 寸有所长, 尺有所短” 就是这个道理。 一般来说,GDB 主要帮助你完成下面四个方面的功能: 1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。 2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式) 3、当程序被停住时,可以检查此时你的程序中所发生的事。 4、动态的改变你程序的执行环境。 《GDB 使用手册》 gcc -g -o hello hello.c 。 GDB 常用命令简介 GDB 的命令很多,本文不会全部介绍,仅会介绍一些最常用的。在介绍之前,先介绍 GDB 中的一个非常有用的功能:补齐功能。它就如同 Linux 下 SHELL 中的命令补齐一样。 当你输入一个命令的前几个字符,然后输入 TAB 键,如果没有其它命令的前几个字符与此 相同,SHELL 将补齐此命令。如果有其它命令的前几个字符与此相同,你会听到一声警告 声,再输入 TAB 键,SHELL 将所有前几个字符与此相同的命令全部列出。而 GDB 中的补 齐功能不仅能补齐 GDB 命令,而且能补齐参数。 本文将先介绍常用的命令,然后结合一个具体的例子来演示如何实际使用这些命令。 下面的所有命令除了第一条启动 GDB 命令是在 SHELL 下输入的,其余都是 GDB 内的命 令。大部分 GDB 内的命令都可以仅输入前几个字符,只要不与其它指令冲突。如 quit 可以 简写为 q,因为以 q 打头的命令只有 quit。List 可以简写为 l,等等 3.1 启动 GDB 你可以输入 GDB 来启动 GDB 程序。GDB 程序有许多参数,在此没有必要详细介绍, 但一个最为常用的还是要介绍的:如果你已经编译好一个程序,我们假设文件名为 hello, 你想用 GDB 调试它,可以输入 gdb hello 来启动 GDB 并载入你的程序。如果你仅仅启动了 GDB,你必须在启动后,在 GDB 中再载入你的程序。 3.2 载入程序 === file 在 GDB 内,载入程序很简单,使用 file 命令。如 file hello。当然,程序的路径名要正 确。 退出 GDB === quit 在 GDB 的命令方式下,输入 quit ,你就可以退出 GDB 。你也可以输入 'C-d' 来退出 GDB。 3.3 运行程序 === run 当你在 GDB 中已将要调试的程序载入后,你可以用 run 命令来执行。如果你的程序需 要参数,你可以在 run 指令后接着输入参数,就象你在 SHELL 下执行一个需要参数的命令 一样。 3.4 查看程序信息 === info info 指令用来查看程序的信息,当你用 help info 查看帮助的话,info 指令的参数足足占 了两个屏幕,它的参数非常多,但大部分不常用。我用 info 指令最多的是用它来查看断点 信息。 3.4.1 查看断点信息 info br br 是断点 break 的缩写,记得 GDB 的补齐功能吧。用这条指令,你可以得到你所设置的所 有断点的详细信息。包括断点号,类型,状态,内存地址,断点在源程序中的位置等。 3.4.2 查看当前源程序 info source 3.4.3 查看堆栈信息 info stack 用这条指令你可以看清楚程序的调用层次关系。 3.4.4 查看当前的参数 info args 3.5 列出源一段源程序 === list 3.5.1 列出某个函数 list FUNCTION 3.5.2 以当前源文件的某行为中间显示一段源程序 list LINENUM 3.5.3 接着前一次继续显示 list 3.5.4 显示前一次之前的源程序 list 3.5.5 显示另一个文件的一段程序 list FILENAME:FUNCTION 或 listFILENAME:LINENUM 3.6 设置断点 === break 现在我们将要介绍的也许是最常用和最重要的命令:设置断点。无论何时,只要你的 程序已被载入,并且当前没有正在运行,你就能设置,修改,删除断点。设置断点的命令 是 break。有许多种设置断点的方法。如下: 3.6.1 在函数入口设置断点 如何定位内存泄露? 内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的、大小任意的(内存块的大小 可以在程序运行期决定)、使用完后必须显示释放的内存。应用程序一般使用 malloc 、 realloc、new 等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用 free 或 delete 释放该内存块。否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。 C++程序缺乏相应的手段来检测内存信息,只能使用 top 指令观察进程的动态内存总额。而 且程序退出时,我们无法获知任何内存泄漏信息 使用 Linux 命令回收内存,可以使用 ps、kill 两个命令检测内存使用情况和进行回收。在使 用超级用户权限时使用命令“ ps ” ,它会列出所有正在运行的程序名称和对应的进程号 (PID )。kill 命令的工作原理是向 Linux 操作系统的内核送出一个系统操作信号和程序的 进程号(PID) 动态链接和静态链接的区别 动态链接是指在生成可执行文件时不将所有程序用到的函数链接到一个文件,因为有许多 函数在操作系统带的 dll 文件中,当程序运行时直接从操作系统中找。 而静态链接就是把 所有用到的函数全部链接到 exe 文件中。 动态链接是只建立一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在 运行时再装入;而静态链接是把所有的代码和数据都复制到本模块中,运行时就不再需要 库了。 32 位系统一个进程最多有多少堆内存 多线程和多进程的区别(重点面试官最最关心的一个问题,必须从 cpu 调度,上下文切 换,数据共享,多核 cup 利用率,资源占用,等等各方面回答,然后有一个问题必须会被 问到:哪些东西是一个线程私有的?答案中必须包含寄存器,否则悲催) 我们按照多个不同的维度,来看看多进程和多线程的对比(注:都是相对的,不是说一个 好得不得了,另一个差的无法忍受) 维度 多进程 多线程 总结 数据共享、同步 数据是分开的:共享复杂,需要用 IPC;同步简单 多线程共享进程数据:共享简单;同步复杂 各有优势 内存、CPU 占用内存多,切换复杂,CPU 利用率低 占用内存少,切换简单,CPU 利用率高 线程占优 创建销毁、切换 创建销毁、切换复杂,速度慢 创建销毁、切换简单,速度快 线程占优 编程调试 编程简单,调试简单 编程复杂,调试复杂 进程占优 可靠性 进程间不会相互影响 一个线程挂掉将导致整个进程挂掉 进程占优 分布式 适应于多核、多机分布;如果一台机器不够,扩展到多台机器比较简单 适应于多核分布 进程占优 然后我们来看下线程和进程间的比较 子进程继承父进程的属性: 子线程继承主线程的属性: 实际用户 ID,实际组 ID,有效用户 ID,有效组 ID; 附加组 ID; 进程组 ID; 会话 ID; 控制终端; 设置用户 ID 标志和设置组 ID 标志; 当前工作目录; 根目录; 文件模式创建屏蔽字(umask); 信号屏蔽和安排; 针对任一打开文件描述符的在执行时关闭(close-on-exec)标志; 环境; 连接的共享存储段; 存储映射; 资源限制; 进程中的所有信息对该进程的所有线程都是共享的; 可执行的程序文本; 程序的全局内存; 堆内存; 栈; 文件描述符; 信号的处理是进程中所有线程共享的(注意:如果信号的默认处理是终止该进程那么即是 把信号传给某个线程也一样会将进程杀掉); 父子进程之间的区别: 子线程特有的: fork 的返回值(=0 子进程); 进程 ID 不同; 两个进程具有不同的父进程 ID; 子进程的 tms_utime,tms_stime,tms_cutime 以及 tms_ustime 均被设置为 0; 不继承父进程设置的文件锁; 子进程的未处理闹钟被清除; 子进程的未处理信号集设置为空集; 线程 ID; 一组寄存器值; 栈; 调度优先级和策略; 信号屏蔽字; errno 变量; 线程私有数据;1)需要频繁创建销毁的优先用线程。 实例:web 服务器。来一个建立一个线程,断了就销毁线程。要是用进程,创建和销毁的 代价是很难承受的。 2)需要进行大量计算的优先使用线程。 所谓大量计算,当然就是要消耗很多 cpu,切换频繁了,这种情况先线程是最合适的。 实例:图像处理、算法处理 3)强相关的处理用线程,若相关的处理用进程。 什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。 一般的 server 需要完成如下任务:消息收发和消息处理。消息收发和消息处理就是弱相关 的任务,而消息处理里面可能又分为消息解码、业务处理,这两个任务相对来说相关性就 要强多了。因此消息收发和消息处理可以分进程设计,消息解码和业务处理可以分线程设 计。 4)可能扩展到多机分布的用进程,多核分布的用线程。 5)都满足需求的情况下,用你最熟悉、最拿手的方式。 至于” 数据共享、同步“ 、“ 编程、调试” 、“ 可靠性” 这几个维度的所谓的“ 复杂、简单” 应该怎 么取舍,只能说:没有明确的选择方法。一般有一个选择原则:如果多进程和多线程都能 够满足要求,那么选择你最熟悉、最拿手的那个。 一般运行一个程序称为一个进程。 进程可以创建线程,也可以创建进程。 线程是由进程管理的,线程之间、线程和父进程(创建线程的进程)之间可以共享内存变 量(需要使用策略的)。 进程之间一般不可以直接共享内存变量,需要使用一些进程间的控制共享内存变量。 如果你使用并行计算,建议使用线程。进程是个容器或者说资源管理者,有独立的内存地址空间。 线程依赖于它所在的进程,共享进程的资源和内存地址空间。 unix 特别是 linux 里面,线程与进程接近;windows 的进程完全是个容器,线程更轻量级。 具体可以了解 linux 下的 fork 以及 clone,windows 的 createprocess、createthread 等 什么是进程。最直观的就是一个个 pid,官方的说法就:进程是程序在计算机上的一次执行 活动。 TCP 和 UDP 分析 主要参看 2 篇博文: http://blog.csdn.net/dog250/article/details/6612496 http://blog.csdn.net/dog250/article/details/6896949 还有就是谢老师写的《计算机网络》第五版,.TCP/IP 详解(卷一,卷二)以及《Unix 网络编 程》以及 Linux 源代码之外,RFC 一、概念: key:TCP 是一种面向连接的、可靠的、字节流服务 1.面向链接:TCP 面向链接,面向连接意味着两个使用 TCP 的应用(通常是一个客户和一 个服务器)在彼此交换数据之前必须通过三次握手先建立一个 TCP 连接。在一个 TCP 中仅 有两方彼此通信,多播和广播不能用于 TCP。UDP 是不可靠的传输,传输前不需要建立链 接,可以应用多播和广播实现一对多的通信。 2.可靠性:TCP 提供端到端的流量控制,对收到的数据进行确认,采用超时重发,对失序 的数据进行重新排序等机制保证数据通信的可靠性。而 UDP 是一种不可靠的服务,接收方 可能不能收到发送方的数据报。 3.TCP 是一种流模式的协议,UDP 是一种数据报模式的协议。进程的每个输出操作都正好 产生一个 UDP 数据报,并组装成一份待发送的 IP 数据报。TCP 应用程序产生的全体数据与 真正发送的单个 IP 数据报可能没有什么联系。TCP 会有粘包和半包的现象。 4.效率上:速度上,一般 TCP 速度慢,传输过程中需要对数据进行确认,超时重发,还要 对数据进行排序。UDP 没有这些机制所以速度快。数据比例,TCP 头至少 20 个字节,UDP 头 8 个字节,相对效率高。组装效率上:TCP 头至少 20 个字节,UDP 头 8 个字节,系统组 装上 TCP 相对慢。 5. 用途上:用于 TCP 可靠性,http ,ftp 使用。而由于 UDP 速度快,视频,在线游戏多用 UDP,保证实时性 对于第三点的理解。TCP 可能发送 100 个“ 包” ,而接收到 50 个“ 包” ,不是丢“ 包” 了,而是 每次接受的“ 包” 都比发送的多,其实 TCP 并没有包的概念。例如,每次发 10 个字节,可能 读得时候一次读了 20 个字节。TCP 是一种流模式的协议,在接收到的缓存中按照发送的包 得顺序自动按照顺序拼接好,因为数据基本来自同一个主机,而且是按照顺序发送过来 的,TCP 的缓存中存放的就是,连续的数据。感觉好像是多封装了一步比 UDP。而 UDP 因 为可能两个不同的主机,给同一个主机发送,(一个端口可能收到多个应用程序的数 据),或者按照 TCP 那样合并数据,必然会造成数据错误。我觉得关键的原因还是,TCP 是面向连接,而 UDP 是无连接的,这就导致,TCP 接收的数据为一个主机发来且有序无误 的,而 UDP 可能是多个主机发来的无序,可能错误的。 写一个 c 程序辨别系统是 16 位 or32 位 法一:int k=~0; if((unsigned int)k &63356) cout&&&at least 32bits&&& else cout&&&16 bits&&& 法二://32 为系统 int i=65536; cout&&i&& int j=65535; cout&&j&& 写一个 c 程序辨别系统是大端 or 小端字节序 用联合体:如 char 类型的,可以看他输出的是 int 的高字节还是低字节 信号:列出常见的信号,信号怎么处理? 信号是 Linux 编程中非常重要的部分,本文将详细介绍信号机制的基本概念、Linux 对信号 机制的大致实现方法、如何使用信号,以及有关信号的几个系统调用。 信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中 断。从它的命名可以看出,它的实质和使用很象中断。所以,信号可以说是进程控制的一 部分。 一、信号的基本概念 本节先介绍信号的一些基本概念,然后给出一些基本的信号类型和信号对应的事件。基本 概念对于理解和使用信号,对于理解信号机制都特别重要。下面就来看看什么是信号。 1、基本概念 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。进程之间可以互相通 过系统调用 kill 发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程 发生了某个事件。注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任 何数据。 收 到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:第一种是类似中 断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处 理。第二种 方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。第三种方法是,对 该信号的处理保留系统的默认值,这种缺省操作,对大部分的信 号的缺省操作是使得进程 终止。进程通过系统调用 signal 来指定进程对某个信号的处理行为。 在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号,当有信号发送给进 程时,对应位置位。由此可以看出,进程对不同的信号可以同时保留,但对于同一个信 号,进程并不知道在处理之前来过多少个。 2、信号的类型 发出信号的原因很多,这里按发出信号的原因简单分类,以了解各种信号: (1) 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。 (2) 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序 正文区),或执行一个特权指令及其他各种硬件错误。 (3) 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用 exec 时,原有资 源已经释放,而目前系统资源又已经耗尽。 (4) 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调 用。 (5) 在用户态下的进程发出的信号。如进程调用系统调用 kill 向其他进程发送信号。 (6) 与终端交互相关的信号。如用户关闭一个终端,或按下 break 键等情况。 (7) 跟踪进程执行的信号。 Linux 支持的信号列表如下。很多信号是与机器的体系结构相关的,首先列出的是 POSIX.1 中列出的信号: 信号 值 处理动作 发出信号的原因 ---------------------------------------------------------------------SIGHUP 1 A 终端挂起或者控制进程终止 SIGINT 2 A 键盘中断(如 break 键被按下) SIGQUIT 3 C 键盘的退出键被按下 SIGILL 4 C 非法指令 SIGABRT 6 C 由 abort(3)发出的退出指令 SIGFPE 8 C 浮点异常 SIGKILL 9 AEF Kill 信号 SIGSEGV 11 C 无效的内存引用 SIGPIPE 13 A 管道破裂: 写一个没有读端口的管道 SIGALRM 14 A 由 alarm(2)发出的信号 SIGTERM 15 A 终止信号 SIGUSR1 30,10,16 A 用户自定义信号 1 SIGUSR2 31,12,17 A 用户自定义信号 2 SIGCHLD 20,17,18 B 子进程结束信号 SIGCONT 19,18,25 进程继续(曾被停止的进程) SIGSTOP 17,19,23 DEF 终止进程 SIGTSTP 18,20,24 D 控制终端(tty)上按下停止键 SIGTTIN 21,21,26 D 后台进程企图从控制终端读 SIGTTOU 22,22,27 D 后台进程企图从控制终端写 下面的信号没在 POSIX.1 中列出,而在 SUSv2 列出 信号 值 处理动作 发出信号的原因 -------------------------------------------------------------------SIGBUS 10,7,10 C 总线错误(错误的内存访问) SIGPOLL A Sys V 定义的 Pollable 事件,与 SIGIO 同义 SIGPROF 27,27,29 A Profiling 定时器到 SIGSYS 12,-,12 C 无效的系统调用 (SVID) SIGTRAP 5 C 跟踪/断点捕获 SIGURG 16,23,21 B Socket 出现紧急条件(4.2 BSD) SIGVTALRM 26,26,28 A 实际时间报警时钟信号(4.2 BSD) SIGXCPU 24,24,30 C 超出设定的 CPU 时间限制(4.2 BSD) SIGXFSZ 25,25,31 C 超出设定的文件大小限制(4.2 BSD) (对于 SIGSYS,SIGXCPU,SIGXFSZ,以及某些机器体系结构下的 SIGBUS,Linux 缺省 的动作是 A (terminate),SUSv2 是 C (terminate and dump core))。 下面是其它的一些信号 信号 值 处理动作 发出信号的原因 ---------------------------------------------------------------------SIGIOT 6 C IO 捕获指令,与 SIGABRT 同义 SIGEMT 7,-,7 SIGSTKFLT -,16,- A 协处理器堆栈错误 SIGIO 23,29,22 A 某 I/O 操作现在可以进行了(4.2 BSD) SIGCLD -,-,18 A 与 SIGCHLD 同义 SIGPWR 29,30,19 A 电源故障(System V) SIGINFO 29,-,- A 与 SIGPWR 同义 SIGLOST -,-,- A 文件锁丢失 SIGWINCH 28,28,20 B 窗口大小改变(4.3 BSD, Sun) SIGUNUSED -,31,- A 未使用的信号(will be SIGSYS) (在这里,- 表示信号没有实现;有三个值给出的含义为,第一个值通常在 Alpha 和 Sparc 上有效,中间的值对应 i386 和 ppc 以及 sh,最后一个值对应 mips。信号 29 在 Alpha 上为 SIGINFO / SIGPWR ,在 Sparc 上为 SIGLOST。) 处理动作一项中的字母含义如下 A 缺省的动作是终止进程 B C D E F缺省的动作是忽略此信号 缺省的动作是终止进程并进行内核映像转储(dump core) 缺省的动作是停止进程 信号不能被捕获 信号不能被忽略上 面介绍的信号是常见系统所支持的。以表格的形式介绍了各种信号的名称、作用及其在 默认情况下的处理动作。各种默认处理动作的含义是:终止程序是指进程退 出;忽略该信 号是将该信号丢弃,不做处理;停止程序是指程序挂起,进入停止状况以后还能重新进行 下去,一般是在调试的过程中(例如 ptrace 系统调 用);内核映像转储是指将进程数据在 内存的映像和进程在内核结构中存储的部分内容以一定格式转储到文件系统,并且进程退 出执行,这样做的好处是为程序员提 供了方便,使得他们可以得到进程当时执行时的数据 值,允许他们确定转储的原因,并且可以调试他们的程序。 注意 信号 SIGKILL 和 SIGSTOP 既不能被捕捉,也不能被忽略。信号 SIGIOT 与 SIGABRT 是一个信号。可以看出,同一个信号在不同的系统中值可能不一样,所以建议最好使用为 信号定义的名字,而不要直接使用信号的值。 二、信 号 机 制 上 一节中介绍了信号的基本概念,在这一节中,我们将介绍内核如何实现信号机制。即内 核如何向一个进程发送信号、进程如何接收一个信号、进程怎样控制自己对信 号的反应、 内核在什么时机处理和怎样处理进程收到的信号。还要介绍一下 setjmp 和 longjmp 在信号中 起到的作用。 1、内核对信号的基本处理方法 内 核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该 信号的位。这里要补充的是,如果信号发送给一个正在睡眠的进程,那么要看 该进程进入 睡眠的优先级,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中 信号域相应的位,而不唤醒进程。这一点比较重要,因为进程检 查是否收到信号的时机 是:一个进程在即将从内核态返回到用户态时;或者,在一个进程要进入或离开一个适当 的低调度优先级睡眠状态时。 内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个 进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进 程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。 内 核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状 态。前面介绍概念的时候讲过,处理信号有三种类型:进程接收到信号后退 出;进程忽略 该信号;进程收到信号后执行用户设定用系统调用 signal 的函数。当进程接收到一个它忽 略的信号时,进程丢弃该信号,就象没有收到该信号似 的继续运行。如果进程收到一个要 捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的 函数的方法很巧妙,内核是在用户栈上创 建一个新的层,该层中将返回地址的值设置成用 户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处, 从函数返回再弹出栈顶时, 才返回原先进入内核的地方。这样做的原因是用户定义的处理 函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可 以获得任何权 限)。 在信号的处理方法中有几点特别要引起注意。第一,在一些系统中,当一个进程处理完中 断信号返回用户态之前,内核清除用户区中设 定的对该信号的处理例程的地址,即下一次 进程对该信号的处理方法又改为默认值,除非在下一次信号到来之前再次使用 signal 系统 调用。这可能会使得进程 在调用 signal 之前又得到该信号而导致退出。在 BSD 中,内核不 再清除该地址。但不清除该地址可能使得进程因为过多过快的得到某个信号而导致堆栈溢 出。为了避免出现上述情况。在 BSD 系统中,内核模拟了对硬件中断的处理方法,即在处 理某个中断时,阻止接收新的该类中断。 第二个要 引起注意的是,如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进 程睡眠在可中断的优先级上,这时该信号引起进程作一次 longjmp,跳出睡眠 状态,返回 用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回一样, 但返回了一个错误代码,指出该次系统调用曾经被中断。这要注 意的是,BSD 系统中内 核可以自动地重新开始系统调用。 第三个要注意的地方:若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号 时,该进程被唤醒,但不做 longjmp,一般是继续睡眠。但用户感觉不到进程曾经被唤醒, 而是象没有发生过该信号一样。 第 四个要注意的地方:内核对子进程终止(SIGCLD)信号的处理方法与其他信号有所区 别。当进程检查出收到了一个子进程终止的信号时,缺省情况下,该进程 就象没有收到该 信号似的,如果父进程执行了系统调用 wait,进程将从系统调用 wait 中醒来并返回 wait 调 用,执行一系列 wait 调用的后续操作(找 出僵死的子进程,释放子进程的进程表项),然 后从 wait 中返回。SIGCLD 信号的作用是唤醒一个睡眠在可被中断优先级上的进程。如果 该进程捕捉了这个 信号,就象普通信号处理一样转到处理例程。如果进程忽略该信号,那 么系统调用 wait 的动作就有所不同,因为 SIGCLD 的作用仅仅是唤醒一个睡眠在可被 中断 优先级上的进程,那么执行 wait 调用的父进程被唤醒继续执行 wait 调用的后续操作,然后 等待其他的子进程。 如果一个进程调用 signal 系统调用,并设置了 SIGCLD 的处理方法,并且该进程有子进程处 于僵死状态,则内核将向该进程发一个 SIGCLD 信号。 2、setjmp 和 longjmp 的作用 前面在介绍信号处理机制时,多次提到了 setjmp 和 longjmp,但没有仔细说明它们的作用和 实现方法。这里就此作一个简单的介绍。 在 介绍信号的时候,我们看到多个地方要求进程在检查收到信号后,从原来的系统调用中 直接返回,而不是等到该调用完成。这种进程突然改变其上下文的情况,就是 使用 setjmp 和 longjmp 的结果。setjmp 将保存的上下文存入用户区,并继续在旧的上下文中执行。这就 是说,进程执行一个系统调用,当因为资 源或其他原因要去睡眠时,内核为进程作了一次 setjmp,如果在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用 longjmp,该 操作是内核 为进程将原先 setjmp 调用保存在进程用户区的上下文恢复成现在的上下文,这 样就使得进程可以恢复等待资源前的状态,而且内核为 setjmp 返回 1,使 得进程知道该次 系统调用失败。这就是它们的作用。 三、有关信号的系统调用 前面两节已经介绍了有关信号的大部分知 识。这一节我们来了解一下这些系统调用。其 中,系统调用 signal 是进程用来设定某个信号的处理方法,系统调用 kill 是用来发送信号给 指定进程的。这 两个调用可以形成信号的基本操作。后两个调用 pause 和 alarm 是通过信 号实现的进程暂停和定时器,调用 alarm 是通过信号通知进程定时器到时。所 以在这里, 我们还要介绍这两个调用。 1、signal 系统调用 系统调用 signal 用来设定某个信号的处理方法。该调用声明的格式如下: void (*signal(int signum, void (*handler)(int)))(int); 在使用该调用的进程中加入以下头文件: #include &signal.h& 上述声明格式比较复杂,如果不清楚如何使用,也可以通过下面这种类型定义的格式来使 用(POSIX 的定义): typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 但这种格式在不同的系统中有不同的类型定义,所以要使用这种格式,最好还是参考一下 联机手册。 在调用中,参数 signum 指出要设置处理方法的信号。第二个参数 handler 是一个处理函数, 或者是 SIG_IGN:忽略参数 signum 所指的信号。 SIG_DFL:恢复参数 signum 所指信号的处理方法为默认值。 传递给信号处理例程的整数参数是信号值,这样可以使得一个信号处理例程处理多个信 号。系统调用 signal 返回值是指定信号 signum 前一次的处理例程或者错误时返回错误代码 SIG_ERR。下面来看一个简单的例子: #include &signal.h& #include &unistd.h& #include &stdio.h& void sigroutine(int dunno) { /* 信号处理例程,其中 dunno 将会得到信号的值 */ switch (dunno) { case 1: printf(&Get a signal -- SIGHUP &);
case 2: printf(&Get a signal -- SIGINT &); case 3: printf(&Get a signal -- SIGQUIT &); } } int main() { printf(&process id is %d &,getpid()); signal(SIGHUP, sigroutine); //* 下面设置三个信号的处理方法 signal(SIGINT, sigroutine); signal(SIGQUIT, sigroutine); for (;;) ; } 其中信号 SIGINT 由按下 Ctrl-C 发出,信号 SIGQUIT 由按下 Ctrl-发出。该程序执行的结果 如下: localhost:~$ ./sig_test process id is 463 Get a signal -SIGINT //按下 Ctrl-C 得到的结果 Get a signal -SIGQUIT //按下 Ctrl-得到的结果 //按下 Ctrl-z 将进程置于后台 [1]+ Stopped ./sig_test localhost:~$ bg [1]+ ./sig_test & localhost:~$ kill -HUP 463 //向进程发送 SIGHUP 信号 localhost:~$ Get a signal CSIGHUP kill -9 463 //向进程发送 SIGKILL 信号,终止进程 localhost:~$ 2、kill 系统调用 系统调用 kill 用来向进程发送一个信号。该调用声明的格式如下: int kill(pid_t pid, int sig); 在使用该调用的进程中加入以下头文件: #include &sys/types.h& #include &signal.h& 该 系统调用可以用来向任何进程或进程组发送任何信号。如果参数 pid 是正数,那么该调 用将信号 sig 发送到进程号为 pid 的进程。如果 pid 等于 0,那么信 号 sig 将发送给当前进 程所属进程组里的所有进程。如果参数 pid 等于-1,信号 sig 将发送给除了进程 1 和自身以 外的所有进程。如果参数 pid 小于- 1,信号 sig 将发送给属于进程组-pid 的所有进程。如果 参数 sig 为 0,将不发送信号。该调用执行成功时,返回值为 0;错误时,返回-1,并设置相 应 的错误代码 errno。下面是一些可能返回的错误代码: EINVAL:指定的信号 sig 无效。 ESRCH:参数 pid 指定的进程或进程组不存在。注意,在进程表项中存在的进程,可能是 一个还没有被 wait 收回,但已经终止执行的僵死进程。 EPERM: 进程没有权力将这个信号发送到指定接收信号的进程。因为,一个进程被允许 将信号发送到进程 pid 时,必须拥有 root 权力,或者是发出调用的进程的 UID 或 EUID 与 指定接收的进程的 UID 或保存用户 ID(savedset-user-ID)相同。如果参数 pid 小于-1,即该 信号发送给一个组,则该错误 表示组中有成员进程不能接收该信号。 3、pause 系统调用 系统调用 pause 的作用是等待一个信号。该调用的声明格式如下: int pause(void); 在使用该调用的进程中加入以下头文件: #include &unistd.h& 该调用使得发出调用的进程进入睡眠,直到接收到一个信号为止。该调用总是返回-1,并 设置错误代码为 EINTR(接收到一个信号)。下面是一个简单的范例: #include &unistd.h& #include &stdio.h& #include &signal.h& void sigroutine(int unused) { printf(&Catch a signal SIGINT &); } int main() { signal(SIGINT, sigroutine); pause(); printf(&receive a signal &); } 在这个例子中,程序开始执行,就象进入了死循环一样,这是因为进程正在等待信号,当 我们按下 Ctrl-C 时,信号被捕捉,并且使得 pause 退出等待状态。 4、alarm 和 setitimer 系统调用 系统调用 alarm 的功能是设置一个定时器,当定时器计时到达时,将发出一个信号给进程。 该调用的声明格式如下: unsigned int alarm(unsigned int seconds); 在使用该调用的进程中加入以下头文件: #include &unistd.h& 系 统调用 alarm 安排内核为调用进程在指定的 seconds 秒后发出一个 SIGALRM 的信号。如 果指定的参数 seconds 为 0,则不再发送 SIGALRM 信号。后一次设定将取消前一次的设 定。该调用返回值为上次定时调用到发送之间剩余的时间,或者因为没有前一次定时调用 而返回 0。 注意,在使用时,alarm 只设定为发送一次信号,如果要多次发送,就要多次使用 alarm 调 用。 对于 alarm,这里不再举例。现在的系统中很多程序不再使用 alarm 调用,而是使用 setitimer 调用来设置定时器,用 getitimer 来得到定时器的状态,这两个调用的声明格式如下: int getitimer(int which, struct itimerval *value); int setitimer(int which, const struct itimerval *value, struct itimerval*ovalue); 在使用这两个调用的进程中加入以下头文件: #include &sys/time.h& 该系统调用给进程提供了三个定时器,它们各自有其独有的计时域,当其中任何一个到 达,就发送一个相应的信号给进程,并使得计时器重新开始。三个计时器由参数 which 指 定,如下所示: TIMER_REAL:按实际时间计时,计时到达将给进程发送 SIGALRM 信号。 ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达将发送 SIGVTALRM 信号给进 程。 ITIMER_PROF:当进程执行时和系统为该进程执行动作时都计时。与 ITIMER_VIR-TUAL 是一对,该定时器经常用来统计进程在用户态和内核态花费的时间。计时到达将发送 SIGPROF 信号给进程。 定时器中的参数 value 用来指明定时器的时间,其结构如下: struct itimerval { struct timeval it_ /* 下一次的取值 */ struct timeval it_ /* 本次的设定值 */ }; 该结构中 timeval 结构定义如下: struct timeval { long tv_ /* 秒 */ long tv_ /* 微秒,1 秒 = 1000000 微秒*/ }; 在 setitimer 调用中,参数 ovalue 如果不为空,则其中保留的是上次调用设定的值。定时器 将 it_value 递减到 0 时,产生一个信号,并将 it_value 的值设 定为 it_interval 的值,然后重 新开始计时,如此往复。当 it_value 设定为 0 时,计时器停止,或者当它计时到期,而 it_interval 为 0 时停止。调用成功时,返回 0 ;错误时,返回 -1 ,并设置相应的错误代码 errno: EFAULT:参数 value 或 ovalue 是无效的指针。 EINVAL:参数 which 不是 ITIMER_REAL、ITIMER_VIRT 或 ITIMER_PROF 中的一个。 下面是关于 setitimer 调用的一个简单示范,在该例子中,每隔一秒发出一个 SIGALRM,每 隔 0.5 秒发出一个 SIGVTALRM 信号: #include &signal.h& #include &unistd.h& #include &stdio.h& #include &sys/time.h& void sigroutine(int signo) { switch (signo) { case SIGALRM: printf(&Catch a signal -- SIGALRM &); case SIGVTALRM: printf(&Catch a signal -- SIGVTALRM &); } } int main() { struct itimerval value,ovalue,value2; sec = 5; printf(&process id is %d &,getpid()); signal(SIGALRM, sigroutine); signal(SIGVTALRM, sigroutine); value.it_value.tv_sec = 1; value.it_value.tv_usec = 0; value.it_interval.tv_sec = 1; value.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &value, &ovalue); value2.it_value.tv_sec = 0; value2.it_value.tv_usec = 500000; value2.it_interval.tv_sec = 0; value2.it_interval.tv_usec = 500000; setitimer(ITIMER_VIRTUAL, &value2, &ovalue); for (;;) ; } 该例子的屏幕拷贝如下: localhost:~$ ./timer_test process id is 579 Catch a signal CSIGVTALRM Catch a signal CSIGALRM Catch a signal CSIGVTALRM Catch a signal CSIGVTALRM Catch a signal CSIGALRM Catch a signal C GVTALRM 本文简单介绍了 Linux 下的信号,如果希望了解其他调用,请参考联机手册或其他文档。i++是否原子操作?并解释为什么? 说出你所知道的 linux 系统的各类同步机制(重点),什么是死锁?如何避免死锁(每个技 术面试官必问) 死锁的条件。(互斥条件( Mutualexclusion ): 1 、资源不能被共享,只能由一个进程使 用。2、请求与保持条件(Hold andwait ):已经得到资源的进程可以再次申请新的资源。 3、非剥夺条件(Nopre-emption ):已经分配的资源不能从相应的进程中被强制地剥夺。 4、循环等待条件(Circularwait):系统中若干进程组成环路,该环路中每个进程都在等待 相邻进程正占用的资源。处理死锁的策略:1. 忽略该问题。例如鸵鸟算法,该算法可以应 用在极少发生死锁的的情况下。为什么叫鸵鸟算法呢,因为传说中鸵鸟看到危险就把头埋 在地底下,可能鸵鸟觉得看不到危险也就没危险了吧。跟掩耳盗铃有点像。2. 检测死锁并 且恢复。3.仔细地对资源进行动态分配,以避免死锁。4.通过破除死锁四个必要条件之一, 来防止死锁产生。) 列举说明 linux 系统的各类异步机制 exit()与_exit()的区别? _exit 终止调用进程,但不关闭文件,不清除输出缓存,也不调用出口函数。exit 函数将终 止调用进程。在退出程序之前,所有文件关闭,缓冲输出内容将刷新定义,并调用所有已 刷新的“ 出口函数” (由 atexit 定义)。 ‘ exit()’ 与‘ _exit()’ 有不少区别在使用‘ fork()’ ,特别是‘ vfork()’ 时变得很突出。 ‘ exit() ’ 与‘ _exit() ’ 的基本区别在于前一个调用实施与调用库里用户状态结构 (user-mode constructs)有关的清除工作(clean-up),而且调用用户自定义的清除程序 如何实现守护进程? 守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行 某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。 Linux 的大多数服 务器就是用守护进程实现的。比如,Internet 服务器 inetd,Web 服务器 httpd 等。同时,守 护进程完成许多系统任务。比如,作业规划进程 crond,打印进程 lpd 等。 守护进程的编程本身并不复杂,复杂的是各种版本的 Unix 的实现机制不尽相同,造成不同 Unix 环境下守护进程的编程规则并不一致。需要注意,照搬某些书上的规则(特别是 BSD4.3 和低版本的 System V)到 Linux 会出现错误的。下面将给出 Linux 下守护进程的编 程要点和详细实例。 一. 守护进程及其特性 守护进程最重要的特性是后台运行。在这一点上 DOS 下的常驻内存程序 TSR 与之相似。其 次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制 终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它 的父进程(特别是 shell)中继承下来的。最后,守护进程的启动方式有其特殊之处。它可 以在 Linux 系统启动时从启动脚本/etc/rc.d 中启动,可以由作业规划进程 crond 启动,还可 以由用户终端(shell)执行。 总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护 进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。如果对进程 有比较深入的认识就更容易理解和编程了。 二. 守护进程的编程要点 前面讲过,不同 Unix 环境下守护进程的编程规则并不一致。所幸的是守护进程的编程原则 其实都一样,区别在于具体的实现细节不同。这个原则就是要满足守护进程的特性。同 时,Linux 是基于 Syetem V 的 SVR4 并遵循 Posix 标准,实现起来与 BSD4 相比更方便。编 程要点如下; 1. 在后台运行。 为避免挂起控制终端将 Daemon 放入后台执行。方法是在进程中调用 fork 使父进程终止, 让 Daemon 在子进程中后台执行。 if(pid=fork()) exit(0); //是父进程,结束父进程,子进程继续 2. 脱离控制终端,登录会话和进程组 有必要先介绍一下 Linux 中的进程与控制终端,登录会话和进程组之间的关系:进程属于 一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进 程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。控制终 端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不 受它们的影响。方法是在第 1 点的基础上,调用 setsid()使进程成为会话组长: setsid(); 说明:当进程是会话组长时 setsid() 调用失败。但第一点已经保证进程不是会话组长。 setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程 组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。 3. 禁止进程重新打开控制终端 现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过 使进程不再成为会话组长来禁止进程重新打开控制终端: if(pid=fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程不再是会话组长) 4. 关闭打开的文件描述符 进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造 成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们: for(i=0;i 关闭打开的文件描述符 close(i);& 5. 改变当前工作目录 进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。 对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 /tmpchdir(&/&) 6. 重设文件创建掩模 进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存 取位。为防止这一点,将文件创建掩模清除:umask(0); 7. 处理 SIGCHLD 信号 处理 SIGCHLD 信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来 时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程 (zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响 服务器进程的并发性能。在 Linux 下可以简单地将 SIGCHLD 信号的操作设为 SIG_IGN。 signal(SIGCHLD,SIG_IGN); 这样,内核在子进程结束时不会产生僵尸进程。这一点与 BSD4 不同,BSD4 下必须显式等 待子进程结束才能释放僵尸进程。 三. 守护进程实例 守护进程实例包括两部分:主程序 test.c 和初始化程序 init.c。主程序每隔一分钟向/tmp 目录 中的日志 test.log 报告运行状态。初始化程序中的 init_daemon 函数负责生成守护进程。读者 可以利用 init_daemon 函数生成自己的守护进程。 linux 的内存管理机制是什么? Linux 虚拟内存的实现需要 6 种机制的支持:地址映射机制、内存分配回收机制、缓存和刷 新机制、请求页机制、交换机制和内存共享机制 内存管理程序通过映射机制把用户程序的逻辑地址映射到物理地址。当用户程序运行时, 如果发现程序中要用的虚地址没有对应的物理内存,就发出了请求页要求。如果有空闲的 内存可供分配,就请求分配内存(于是用到了内存的分配和回收) ,并把正在使用的物理页 记录在缓存中(使用了缓存机制)。如果没有足够的内存可供分配,那么就调用交换机制; 腾出一部分内存。另外,在地址映射中要通过 TLB( 翻译后援存储器)来寻找物理页;交换 机制中也要用到交换缓存,并且把物理页内容交换到交换文件中,也要修改页表来映射文 件地址。 linux 的任务调度机制是什么? 在每个进程的 task_struct 结构中有以下四项:policy、priority、counter、rt_priority。这四项 是选择进程的依据。其中,policy 是进程的调度策略,用来区分实时进程和普通进程,实 时进程优先于普通进程运行;priority 是进程(包括实时和普通)的静态优先级;counter 是进 程剩余的时间片,它的起始值就是 priority 的值;由于 counter 在后面计算一个处于可运行状 态的进程值得运行的程度 goodness 时起重要作用,因此,counter 也可以看作是进程的动态 优先级。rt_priority 是实时进程特有的,用于实时进程间的选择。 Linux 用函数 goodness()来衡量一个处于可运行状态的进程值得运行的程度。该函数综合了 以上提到的四项,还结合了一些其他的因素,给每个处于可运行状态的进程赋予一个权值 (weight),调度程序以这个权值作为选择进程的唯一依据。关于 goodness()的情况在后面将 会详细分析。 五种 I/O 模式―― 阻塞(默认 IO 模式), 非阻塞(常用于管道), I/O 多路复用(IO 多路复用的应用场景), 信号 I/O, 异步 I/O 五种 I/O 模式: 【1 】 阻塞 I/O 创建的 I/O 都是阻塞 I/O) 【2 】 非阻塞 I/O 将 fd 设置为非阻塞的 I/O) 【3】 I/O 多路复用 【4】 信号驱动 I/O 【5】 异步 I/O(Linux 下的 I/O 操作默认是阻塞 I/O ,即 open 和 socket (可以通过 fcntl 或者 open 时使用 O_NONBLOCK 参数, (I/O 多路复用,通常需要非阻塞 I/O 配合使用) (SIGIO)一般来说,程序进行输入操作有两步: 1.等待有数据可以读 2.将数据从系统内核中拷贝到程序的数据区。 对于 sock 编程来说: 第一步: 一般来说是等待数据从网络上传到本地。当数据包到达的时候,数据 将会从网络层拷贝到内核的缓存中; 第二步: 是从内核中把数据拷贝到程序的数据区中。 阻塞 I/O 模式 //进程处于阻塞模式时,让出 CPU,进入休眠 状态 阻塞 I/O 模式是最普遍使用的 I/O 模式。是 Linux 系统下缺省的 IO 模式。 大部分程序使用的都是阻塞模式的 I/O 。 一个套接字建立后所处于的模式就是阻塞 I/O 模式。(因为 Linux 系统默认的 IO 模式是阻塞模式) 对于一个 UDP 套接字来说,数据就绪的标志比较简单: (1)已经收到了一整个数据报 (2)没有收到。 而 TCP 这个概念就比较复杂,需要附加一些其他的变量。 一个进程调用 recvfrom ,然后系统调用并不返回知道有数据报到达本地系统, 然后系统将数据拷贝到进程的缓存中。(如果系统调用收到一个中断信号,则它的调用会 被中断) 我们称这个进程在调用 recvfrom 一直到从 recvfrom 返回这段时间是阻塞的。当 recvfrom 正常返回时,我们的进程继续它的操作。非阻塞模式 I/O //非阻塞模式的使用并不普遍,因为非阻塞模 式会浪费大量的 CPU 资源。 当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核: “ 当我请求 的 I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上 返回一个错误给我。” 我们开始对 recvfrom 的三次调用,因为系统还没有接收到网络数据,所以内核马 上返回一个 EWOULDBLOCK 的错误。 第四次我们调用 recvfrom 函数,一个数据报已经到达了,内核将它拷贝到我们的 应用程序的缓冲区中,然后 recvfrom 正常返回,我们就可以对接收到的数据进行处理 了。 当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不听的测试是 否一个文件描述符有数据可读(称做 polling(轮询))。应用程序不停的 polling 内核来检查是 否 I/O 操作已经就绪。这将是一个极浪费 CPU 资源的操作。这种模式使用中不是很普遍。 例如: 对管道的操作,最好使用非阻塞方式!I/O 多路复用 //针对批量 IP 操作时,使用 I/O 多路复用,非 常有好。 在使用 I/O 多路技术的时候,我们调用 select() 函数和 poll() 函数或 epoll 函数(2.6 内核开始支持),在调用它们的时候阻塞,而不是我们来调用 recvfrom(或 recv)的时候阻 塞。 当我们调用 select 函数阻塞的时候, select 函数等待数据报套接字进入读就绪状 态。当 select 函数返回的时候,也就是套接字可以读取数据的时候。这时候我们就可以调用 recvfrom 函数来将数据拷贝到我们的程序缓冲区中。 对于单个 I/O 操作,和阻塞模式相比较,select()和 poll()或 epoll 并没有什么高级的 地方。 而且,在阻塞模式下只需要调用一个函数: 读取或发送函数。 在使用了多路复用技术后,我们需要调用两个函数了: 先调用 select()函数或 poll()函数,然后才能进行真正的读 写。 多路复用的高级之处在于:: 它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的 任意一个进入读就绪状态,select()函数就可以返回。 IO 多路技术一般在下面这些情况中被使用: 1、当一个客户端需要同时处理多个文件描述符的输入输出操作的时候(一般来说是标准的 输入输出和网络套接字),I/O 多路复用技术将会有机会得到使用。 2、当程序需要同时进行多个套接字的操作的时候。 3 、如果一个 TCP 服务器程序同时处理正在侦听网络连接的套接字和已经连接好的套接 字。 4、如果一个服务器程序同时使用 TCP 和 UDP 协议。 5、如果一个服务器同时使用多种服务并且每种服务可能使用不同的协议(比如 inetd 就是 这样的)。 异步 IO 模式有:: 1、信号驱动 I/O 模式 2、异步 I/O 模式 信号驱动 I/O 模式 //自己没有用 过。 我们可以使用信号,让内核在文件描述符就绪的时候使用 SIGIO 信号来通知我 们。我们将这种模式称为信号驱动 I/O 模式。 为了在一个套接字上使用信号驱动 I/O 操作,下面这三步是所必须的。 (1)一个和 SIGIO 信号的处理函数必须设定。 (2)套接字的拥有者必须被设定。一般来说是使用 fcntl 函数的 F_SETOWN 参数来 进行设定拥有者。 ( 3 )套接字必须被允许使用异步 I/O 。一般是通过调用 fcntl 函数的 F_SETFL 命令, O_ASYNC 为参数来实现。 虽然设定套接字为异步 I/O 非常简单,但是使用起来困难的部分是怎样在程序中 断定产生 SIGIO 信号发送给套接字属主的时候,程序处在什么状态。 1.UDP 套接字的 SIGIO 信号 (比较简单) 在 UDP 协议上使用异步 I/O 非常简单.这个信号将会在这个时候产生: 1、套接字收到了一个数据报的数据包。 2、套接字发生了异步错误。 当我们在使用 UDP 套接字异步 I/O 的时候,我们使用 recvfrom()函数来读取数 据报数据或是异步 I/O 错误信息。 2.TCP 套接字的 SIGIO 信号 (不会使用) 不幸的是,异步 I/O 几乎对 TCP 套接字而言没有什么作用。因为对于一个 TCP 套接字来说,SIGIO 信号发生的几率太高了,所以 SIGIO 信号并不能告诉我们究竟 发生了什么事情。 在 TCP 连接中, SIGIO 信号将会在这个时候产生: l 在一个监听某个端口的套接字上成功的建立了一个新连接。 l 一个断线的请求被成功的初始化。 l 一个断线的请求成功的结束。 l 套接字的某一个通道(发送通道或是接收通道)被关闭。 l 套接字接收到新数据。 l 套接字将数据发送出去。 l 发生了一个异步 I/O 的错误。 一个对信号驱动 I/O 比较实用的方面是 NTP(网络时间协议 Network TimeProtocol)服务 器,它使用 UDP。这个服务器的主循环用来接收从客户端发送过来的数据报数据包,然后 再发送请求。对于这个服务器来说,记录下收到每一个数据包的具体时间是很重要的。 因为那将是返回给客户端的值,客户端要使用这个数据来计算数据报在网络上来回所花费 的时间。图 6-8 表示了怎样建立这样的一个 UDP 服务器。异步 I/O 模式 //比如写操作,只需用写,不一定写入磁盘(这就是异步 I/O)的 好处。异步 IO 的好处效率高。 当我们运行在异步 I/O 模式下时,我们如果想进行 I/O 操作,只需要告诉内核我 们要进行 I/O 操作,然后内核会马上返回。具体的 I/O 和数据的拷贝全部由内核来完 成,我们的程序可以继续向下执行。当内核完成所有的 I/O 操作和数据拷贝后,内核将通 知我们的程序。 异步 I/O 和 信号驱动 I/O 的区别是: 1、信号驱动 I/O 模式下,内核在操作可以被操作的时候通知给我们的应用程序 发送 SIGIO 消息。 2、异步 I/O 模式下,内核在所有的操作都已经被内核操作结束之后才会通知我 们的应用程序。 select,poll,epoll . Epoll 是何方神圣? Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内 核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已,并没有什么神秘的。 其 实 在 Linux 下 设 计 并 发 网 络 程 序 , 向 来 不 缺 少 方 法 , 比 如 典 型 的 Apache 模 型 ( Process Per Connection ,简称 PPC ), TPC ( ThreadPer Connection )模型,以及 select 模型和 poll 模型,那为何还要再引入 Epoll 这个东东呢?那还是有得说说的 … 2. 常用模型的缺点 如果不摆出来其他模型的缺点,怎么能对比出 Epoll 的优点呢。 2.1 PPC/TPC 模型 这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我。只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。可是别烦我是有代价的,它要时间和空间 啊,连接多了之后,那么多的进程 / 线程切换,这开销就上来了;因此这类模型能接受的 最大连接数都不会高,一般在几百个左右。 2.2 select 模型 1. 最 大 并 发 数 限 制 , 因 为 一 个 进 程 所 打 开 的 FD ( 文 件 描 述 符 ) 是 有 限 制 的 ,
由 FD_SETSIZE 设置,默认值是
,因此 Select 模型的最大 并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧 … 2. 效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下 降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了??!! 3. 内核 / 用户空间内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问 题上 select 采取了内存拷贝方法。 2.3 poll 模型 基本上效率和 select 是相同的,select 缺点的 2 和 3 它都没有改掉。 3. Epoll 的提升 把其他模型逐个批判了一下,再来看看 Epoll 的改进之处吧,其实把 select 的缺点反过来 那就是 Epoll 的优点了。 3.1. Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大 于 2048, 一般来说这个数目和系统内存关系很大,具体数目可以 cat /proc/sys/fs/file-max 察看。 3.2. 效率提升, Epoll 最大的优点就在于它只管你“ 活跃” 的连接,而跟连接总数无关,因 此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。 3.3. 内存拷贝, Epoll 在这点上使用了“ 共享内存 ” ,这个内存拷贝也省略了。 4. Epoll 为什么高效 Epoll 的高效和其数据结构的设计是密不可分的,这个下面就会提到。 首先回忆一下 select 模型,当有 I/O 事件到来时,select 通知应用程序有事件到了快去处 理,而应用程序必须轮询所有的 FD 集合,测试每个 FD 是否有事件发生,并处理事 件;代码像下面这样: int res = select(maxfd+1, &readfds,NULL, NULL, 120); if (res & 0) { for (int i = 0; i &MAX_CONNECTION; i++) { if (FD_ISSET(allConnection[i], &readfds)) { handleEvent(allConnection[i]); } } } // if(res == 0) handle timeout, res & 0handle error Epoll 不仅会告诉应用程序有 I/0 事件到来,还会告诉应用程序相关的信息,这些信息是应 用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个 FD 集 合。 int res = epoll_wait(epfd, events, 20,120); for (int i = 0; i &i++) { handleEvent(events[n]); } 5. Epoll 关键数据结构 前面提到 Epoll 速度快和其数据结构密不可分,其关键数据结构就是: struct epoll_event { __uint32_ // Epoll events epoll_data_ // User data variable }; typedef union epoll_data { void * __uint32_t u32; __uint64_t u64; } epoll_data_t; 可见 epoll_data 是一个 union 结构体 , 借助于它应用程序可以保存很多类型的信息 :fd 、 指针等等。有了它,应用程序就可以直接定位目标了。 标准库函数和系统调用的区别? 1、系统调用和库函数的关系 系统调用通过软中断 int 0x80 从用户态进入内核态。 函数库中的某些函数调用了系统调用。 函数库中的函数可以没有调用系统调用,也可以调用多个系统调用。 编程人员可以通过函数库调用系统调用。 高级编程也可以直接采用 int 0x80 进入系统调用,而不必通过函数库作为中介。 如果是在核心编程,也可以通过 int 0x80 进入系统调用,此时不能使用函数库。因为函数 库中的函数是内核访问不到的。 2、从用户调用库函数到系统调用执行的流程。 1) 假设用户调用 ssize_t write (int fields, cont void *buff, size_tnbytes);库函数。 2) 库函数会执行 int 0x80 中断。因为中断使得进程从用户态进入内核态,所以参数通过寄 存器传送。 3) 0x80 中断对应的中断例程被称为 system call handler。其工作是: i. 存储大多数寄存器到内核堆栈中。这是汇编代码写的。 ii. 执行真正的系统调用函数DDsystem callservice routine。这是 C 代码。 iii. 通过 ret_from_sys_call()返回,回到用户态的库函数。这是汇编代码。 1、系统调用 系统调用提供的函数如 open, close, read, write, ioctl 等,需包含头文件 unistd.h。以 write 为 例:其函数原型为 size_t write(int fd, const void *buf, size_t nbytes),其操作对象为文件描述 符或文件句柄 fd(file descriptor),要想写一个文件,必须先以可写权限用 open 系统调用打开 一个文件,获得所打开文件的 fd ,例如 fd=open(/&/dev/video/&, O_RDWR) 。fd 是一个整型 值,每新打开一个文件,所获得的 fd 为当前最大 fd 加 1。Linux 系统默认分配了 3 个文件描 述符值:0-standard input,1-standard output,2-standard error。 系统调用通常用于底层文件访问(low-level file access),例如在驱动程序中对设备文件的 直接访问。 系统调用是操作系统相关的,因此一般没有跨操作系统的可移植性。 系统调用发生在内核空间,因此如果在用户空间的一般应用程序中使用系统调用来进行文 件操作,会有用户空间到内核空间切换的开销。事实上,即使在用户空间使用库函数来对 文件进行操作,因为文件总是存在于存储介质上,因此不管是读写操作,都是对硬件(存 储器)的操作,都必然会引起系统调用。也就是说,库函数对文件的操作实际上是通过系 统调用来实现的。例如 C 库函数 fwrite()就是通过 write()系统调用来实现的。 这样的话,使用库函数也有系统调用的开销,为什么不直接使用系统调用呢?这是因为, 读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单 位而言),这时,使用库函数就可以大大减少系统调用的次数。这一结果又缘于缓冲区技 术。在用户空间和内核空间,对文件操作都使用了缓冲区,例如用 fwrite 写文件,都是先 将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操作结束时,才将用户缓冲区的 内容写到内核缓冲区,同样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到 文件对应的硬件媒介。 2、库函数调用 标准 C 库函数提供的文件操作函数如 fopen, fread, fwrite, fclose,fflush, fseek 等,需包含头文 件 stdio.h 。以 fwrite 为例,其函数原型为 size_t fwrite(const void *buffer,size_t size, size_t item_num, FILE *pf),其操作对象为文件指针 FILE *pf,要想写一个文件,必须先以可写权 限 用 fopen 函 数 打 开 一 个 文 件 , 获 得 所 打 开 文 件 的 FILE 结 构 指 针 pf , 例 如 pf=fopen(/&~/proj/filename/&,/&w/&)。实际上,由于库函数对文件的操作最终是通过系统调用 实现的,因此,每打开一个文件所获得的 FILE 结构指针都有一个内核空间的文件描述符 fd 与之对应。同样有相应的预定义的 FILE 指针: stdin - standard input , stdout - standard output,stderr-standard error。 库函数调用通常用于应用程序中对一般文件的访问。 库函数调用是系统无关的,因此可移植性好。 由于库函数调用是基于 C 库的,因此也就不可能用于内核空间的驱动程序中对设备的操作 ping 命令所利用的原理是这样的:网络上的机器都有唯一确定的 IP 地址,我们给目标 IP 地 址发送一个数据包,对方就要返回一个同样大小的数据包,根据返回的数据包我们可以确 定目标主机的存在,可以初步判断目标主机的操作系统等。 补充一个坑爹坑爹坑爹坑爹的问题:系统如何将一个信号通知到进程? 信号机制是异步的;当一个进程接收到一个信号时,它会立刻处理这个信号,而不会等待 当前函数甚至当前一行代码结束运行。信号有几十种,分别代表着不同的意义。信号之间 依靠它们的值来区分,但是通常在程序中使用信号的名字来表示一个信号。在 Linux 系统 中,这些信号和以它们的名称命名的常量均定义在/usr/include/bits/signum.h 文件中。(通常 程序中不需要直接包含这个头文件,而应该包含&signal.h&。) 信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来 源,最常用发送信号的系统函数是 kill, raise, alarm 和 setitimer 以及 sigqueue 函数,软件来源 还包括一些非法运算等操作。 发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及 abort()。 进程可以通过三种方式来响应一个信号:(1)忽略信号,即对信号不做任何处理,其中, 有两个信号不能忽略:SIGKILL 及 SIGSTOP ;(2)捕捉信号。定义信号处理函数,当信 号发生时,执行相应的处理函数;(3)执行缺省操作, c 语言: 宏定义和展开(必须精通) #define 是 C 语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便, 并能在一定程度上提高程序的运行效率,但学生在学习时往往不能理解该命令的本质,总 是在此处产生一些困惑,在编程时误用该命令,使得程序的运行与预期的目的不一致,或 者在读别人写的程序时,把运行结果理解错误,这对 C 语言的学习很不利。 1#define 命令剖析 1.1 #define 的概念 #define 命令是 C 语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标 识符被称为宏名,被定义的字符串称为替换文本。 该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。 (1) 简单的宏定义: #define &宏名&&字符串& 例: #define PI 3.) 带参数的宏定义 #define &宏名& (&参数表&) &宏体& 例:#define A(x) x 一个标识符被宏定义后,该标识符便是一个宏名。这时,在程序中出现的是宏名,在该程 序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译,宏替 换是简单的替换。 1.2 宏替换发生的时机 为了能够真正理解#define 的作用,让我们来了解一下对 C 语言源程序的处理过程。当我们 在一个集成的开发环境如 TurboC 中将编写好的源程序进行编译时,实际经过了预处理、编 译、汇编和连接几个过程 这里要说一下宏的展开次序,如果宏有参数,如 TO_STRING_MACRO(x)中的 x,我们称之 为形参,而宏实际的参数我们称之为实参,如 TO_STRING_MACRO(a)中的 a。 宏的展开是个很复杂的过程,但可以用以下三步来简单描述, 首先用实参替换形参,将实参代入宏文本中; 然后如果实参也是宏,则展开实参; 最后再继续处理宏替换后的宏文本,若宏文本也包含宏则继续展开,否则完成展开。 但是有个例外,那就是第一步后,将实参代入宏文本后,实参之前如果遇到字符“ #” 或 “ ##” ,即使实参是宏,也不再展开实参,而是当作文本处理。 位操作(必须精通) 在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“ 位运算” 来完成所 有的运算和操作。一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的 位操作可以有效地提高程序运行的效率。C 语言提供了位运算的功能,这使得 C 语言也能 像汇编语言一样用来编写系统程序。 位运算符 C 语言提供了六种位运算符: & 按位与 | 按位或 ^ 按位异或 ~ 取反 && 左移 && 右移 指针操作和计算(必须精通) &是地址操作符,用来引用一个内存地址。通过在变量名字前使用&操作符,我们可以得 到该变量的内存地址。 1 2 3 4 5 6 7 8 9 // 声明一个 int 指针 int* // 声明一个 int 值 intval = 1; // 为指针分配一个 int 值的引用 ptr = & // 对指针进行取值,打印存储在指针地址中的内容 intderef = * printf(&%d\n&, deref); 指针是一个存储计算机内存地址的变量。在这份教程里“ 引用” 表示计算机内存地址。从指 针指向的内存读取数据称作指针的取值。指针可以指向某些具体类型的变量地址,例如 int、long 和 double。指针也可以是 void 类型、NULL 指针和未初始化指针。本文会对上 述所有指针类型进行探讨。 根据出现的位置不同,操作符 * 既可以用来声明一个指针变量,也可以用作指针的取 值。当用在声明一个变量时, * 表示这里声明了一个指针。其它情况用到 * 表示指针的取 值。 &是地址操作符,用来引用一个内存地址。通过在变量名字前使用& 操作符,我们可 以得到该变量的内存地址。 语言中指针的 15 个问题 aqiaoboy 1 指针的四要素 1 指针变量,表示一个内存地址,通常为逻辑地址,与实际的物理地址还有一个映射关系. 2 指针变量的长度,在 WIN32 下为四个字节, 3 指针指向的变量 该内存地址空间下存放的变量,具体内容可能是各种类型的变量. 4 指针指向的变量的长度,以该内存地址空间开始的内存空间大小. 2 Const,volatile 修饰指针的含义 const char *cpch=” hello’ ; 表示指针指向的变量不可改变,但指针本身是可以改变的 char * 指针指向的变量可以改变,但指针本身不可改变. Const char * 两者都不可变. 3 堆和栈上的指针 指针所指向的这块内存是在哪里分配的,在堆上称为堆上的指针,在栈上为栈上的指针. 在堆上的指针,可以保存在全局数据结构中,供不同函数使用访问同一块内存. 在栈上的指针,在函数退出后,该内存即不可访问. 4 什么是指针的释放? 具体来说包括两个概念. 1 释放该指针指向的内存,只有堆上的内存才需要我们手工释放,栈上不需要. 2 将该指针重定向为 NULL. 5 near,far 型指针的区别? 老式的 IBM PC 兼容机才有这种区别,因为老式机不能完全支持 32 位指针, 所以才分为 16 位指针,(near),和 32 位指针(far) 从 386 开始没有这种区别,都是 32 位指针. 6 数据结构中的指针? 其实就是指向一块内存的地址,通过指针传递,可实现复杂的内存访问. 7 函数指针? 指向一块函数的入口地址. 8 指针作为函数的参数? 比如指向一个复杂数据结构的指针作为函数变量 这种方法避免整个复杂数据类型内存的压栈出栈操作,提高效率. 注意:指针本身不可变,但指针指向的数据结构可以改变. 9 指向指针的指针? 指针指向的变量是一个指针,即具体内容为一个指针的值,是一个地址. 此时指针指向的变量长度也是 4 位. 10 指针与地址的区别? 区别: 1 指针意味着已经有一个指针变量存在,他的值是一个地址,指针变量本身也存放在一个长度 为四个字节的地址当中,而地址概念本身并不代表有任何变量存在. 2 指针的值,如果没有限制,通常是可以变化的,也可以指向另外一个地址. 地址表示内存空间的一个位置点,他是用来赋给指针的,地址本身是没有大小概念,指针指 向变量的大小,取决于地址后面存放的变量类型. 11 指针与数组名的关系? 其值都是一个地址,但前者是可以移动的,后者是不可变的. 12 怎样防止指针的越界使用问题? 必须让指针指向一个有效的内存地址, 1 防止数组越界 2 防止向一块内存中拷贝过多的内容 3 防止使用空指针 4 防止改变 const 修改的指针 5 防止改变指向静态存储区的内容 6 防止两次释放一个指针 7 防止使用野指针.13 指针的类型转换? 指针转换通常是指针类型和 void * 类型之前进行强制转换,从而与期望或返回 void 指针的函 数进行正确的交接.14 什么是指针退化? 如果用一个数组作为函数入参 比如 void fun(char a[100]) { cout&&SIZEOF(A)& } 15 指针的移动问题? 指针 P ++具体移动的字节数等于指针指向的变量类型大小.写得还比较全的 函数参数我都用指针没有用数组 14 什么是指针退化? 如果用一个数组作为函数入参 比如 void fun(char a[100]) { cout&&SIZEOF(A)& } 像这样定义也不能保证检查传进来的参数对不对 [code] int a(char s[100]) { s[100]=1; return 0; } main() { char s[20]=&hello&; a(s); } [/code] 内存分配(必须精通) sizeof 必考 内存分配方式有三种: (1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的 整个运行期间都存在。例如全局变量,static 变量。 (2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函 数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率 很高,但是分配的内存容量有限。 (3)从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多 少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由我们决 定,使用非常灵活,但问题也最多。 各类库函数必须非常熟练的实现 哪些库函数属于高危函数,为什么?(strcpy 等等) c++: 一个 String 类的完整实现必须很快速写出来(注意:赋值构造,operator=是关键) 虚函数的作用和实现原理(必问必考,实现原理必须很熟) 有虚函数的类内部有一个称为“ 虚表” 的指针(有多少个虚函数就有多少个指针),这个就 是用来指向这个类虚函数。也就是用它来确定调用该那个函数。 实际上在编译的时候,编译器会自动加入“ 虚表” 。虚表的使用方法是这样的:如果派生类 在自己的定义中没有修改基类的虚函数,就指向基类的虚函数;如果派生类改写了基类的 虚函数(就是自己重新定义),这时虚表则将原来指向基类的虚函数的地址替换为指向自 身虚函数的指针。那些被 virtual 关键字修饰的成员函数,就是虚函数。虚函数的作用,用 专业术语来解释就是实现多态性(Polymorphism ),多态性是将接口与实现进行分离;用 形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略。 每个类都有自己的 vtbl,vtbl 的作用就是保存自己类中虚函数的地址,我们可以把 vtbl 形象 地看成一个数组,这个数组的每个元素存放的就是虚函数的地址, 虚函数的效率低,其原因就是,在调用虚函数之前,还调用了获得虚函数地址的代码。 sizeof 一个类求大小(注意成员变量,函数,虚函数,继承等等对大小的影响) 指针和引用的区别(一般都会问到) 相同点:1. 都是地址的概念; 指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。 区别:1. 指针是一个实体,而引用仅是个别名; 2. 引用使用时无需解引用(*),指针需要解引用; 3. 引用只能在定义时被初始化一次,之后不可变;指针可变; 4. 引用没有 const,指针有 const; 5. 引用不能为空,指针可以为空; 6. “ sizeof 引用” 得到的是所指向的变量 ( 对象) 的大小,而“ sizeof 指针” 得到的是指针本身 (所指向的变量或对象的地址)的大小; 7. 指针和引用的自增(++)运算意义不一样; 8.从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。 多重类构造和析构的顺序 先调用基类的构造函数,在调用派生类的构造函数 先构造的后析构,后构造的先析构 stl 各容器的实现原理(必考) STL = Standard Template Library,标准模板库 STL 共有六大组件 1、容器。2、算法。3、迭代器。4、仿函数。6、适配器。 序列式容器: vector-数组,元素不够时再重新分配内存,拷贝原来数组的元素到新分配的数组中。 list-单链表。 deque- 分配中央控制器 map( 并非 map 容器),map 记录着一系列的固定长度的数组的地址. 记住这个 map 仅仅保存的是数组的地址,真正的数据在数组中存放着.deque 先从 map 中央的 位置(因为双向队列,前后都可以插入元素)找到一个数组地址,向该数组中放入数据,数 组不够时继续在 map 中找空闲的数组来存数据。当 map 也不够时重新分配内存当作新的 map,把原来 map 中的内容 copy 的新 map 中。所以使用 deque 的复杂度要大于 vector,尽量 使用 vector。 stack-基于 deque。 queue-基于 deque。 heap-完全二叉树,使用最大堆排序,以数组(vector)的形式存放。 priority_queue-基于 heap。 slist-双向链表。 关联式容器: set,map,multiset,multimap-基于红黑树(RB-tree),一种加上了额外平衡条件的二叉搜索树。 hash table-散列表。将待存数据的 key 经过映射函数变成一个数组(一般是 vector)的索引,例 如:数据的 key%数组的大小=数组的索引(一般文本通过算法也可以转换为数字),然后将 数据当作此索引的数组元素。有些数据的 key 经过算法的转换可能是同一个数组的索引值 (碰撞问题,可以用线性探测,二次探测来解决),STL 是用开链的方法来解决的,每一个 数组的元素维护一个 list,他把相同索引值的数据存入一个 list,这样当 list 比较短时执行删 除,插入,搜索等算法比较快。 hash_map,hash_set,hash_multiset,hash_multimap-基于 hashtable。 extern c 是干啥的,(必须将编译器的函数名修饰的机制解答的很透彻) volatile 是干啥用的,(必须将 cpu 的寄存器缓存机制回答的很透彻) volatile 的本意是“ 易变的”因为访问寄存器要比访问内存单元快的多,所以编译器一般都会 作减少存取内存的优化,但有可能会读脏数据。当要求使用 volatile 声明变量值的时候,系 统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地 说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而 可以提供对特殊地址的稳定访问;如果不使用 volatile,则编译器将对所声明的语句进行优 化。(简洁的说就是:volatile 关键词影响编译器编译的结果,用 volatile 声明的变量表示该 变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错) 5.volatile 的本质: 1& 编译器的优化 在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取 到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改 变时,会同时把变量的新值 copy 到该寄存器中,以便保持一致。 当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取 的值和实际的变量值不一致。 当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的 值和实际的变量值不一致。 2&volatile

我要回帖

更多关于 禁止手机程序自动运行 的文章

 

随机推荐