资源描述
Linux下开发基础知识
touch命令:可用于新建文件,如; touch /tmp/zhangxian.txt
关于volatile的介绍 (类型修饰符,指令关键字)
1.编译器优化介绍
由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬件重新排序引起的问题的解决办法是在从硬件(或者其他处理器)的角度看必须以特定顺序执行的操作之间设置内存屏障(memory barrier),linux 提供了一个宏解决编译器的执行顺序问题。
2. void Barrier(void)
这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。
3. volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以消除一些代码。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化。
gcc and g++分别是GNU的c & c++编译器。
gcc/g++在执行编译的时候一般有下面4步:
⒈预处理,生成.i的文件[预处理器cpp];
⒉将预处理后的文件转换成汇编语言,生成文件.s[编译器egcs];
⒊由汇编变为目标代码(机器代码)生成.o的文件[汇编器as];
⒋连接目标代码,生成可执行程序[链接器ld];
gcc编译流程分为4个步骤,分别为:
预处理(Pre-Processing)
编译(compiling)
汇编(Assembling)
链接(Linking)
g++ -o -c -g功能
-o:指定生成可执行文件的名称。使用方法为:g++ -o afile file.cpp file.h ... (可执行文件不可与待编译或链接文件同名,否则会生成相应可执行文件且覆盖原编译或链接文件),如果不使用-o选项,则会生成默认可执行文件a.out。
-c:只编译不链接,只生成目标文件。
-g:添加gdb调试选项。
Linux下C语言应用编程
1. 文件I/O编程
2. 多进程编程的基本知识,核心是fork、exec、wait三大系统的调用;(可能需要补充操作系统进程并发运行的基本知识)
3. 进程间通信的几种方法:信号,管道,共享内存,信号量,消息队列。(最重要的是信号和管道)
4. 多线程编程的基础知识;
5. 网络编程:socket编程, TCP socket编程和UDP socket编程;
6. 开发工具(包括:编辑器vim、编译器gcc、调试器gdb、工程管理工具autoconf、程序库的制作等);
编译器vim的三种模式:
1. 切换到编辑模式的四种方式,编辑模式可以输入任意内容
a 光标向后移动一位
i 当前位置
o 另起新行
s 删除光标所在字符
r 替换光标所在字符
2. 尾行模式,用于保存内容、查找替换、设置行号等等功能性操作
:q //quit退出vi编辑器
:w //write保存修改的内容
:wq //保存并退出
:q! //强制退出,当对文本内容作了修改而不想要保存时
:w! //强制保存,当没有文本的写权限时
make和makefile文件:
makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作(因为makefile就像一个Shell脚本一样,可以执行操作系统的命令)。makefile带来的好处就是—“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译。makefile文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并链接生成可执行文件,要求定义源文件之间的依赖关系。
make工具最主要也最基本的功能就是根据makefile文件中描述的源程序之间的相互关系来完成自动编译、维护多源文件工程。
Linux下的常用命令
Linux 删除文件夹和文件的命令
-r 就是向下递归,不管有多少级目录,一并删除
-f 就是直接强行删除,不作任何提示的意思
删除文件夹实例:
rm -rf /var/log/httpd/access
将会删除/var/log/httpd/access目录以及其下所有文件、文件夹
删除文件使用实例:
rm -f /var/log/httpd/access.log
将会强制删除/var/log/httpd/access.log这个文件
文件I/O编程:
系统调用、API与系统命令之间的关系
Linux系统调用是指操作系统提供给用户程序的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的特殊服务。比如用户可以通过文件系统相关的调用请求系统打开文件、关闭文件或读写文件,可以通过时钟相关的系统调用获得系统时间或设置系统时间等。
为了更好的保护内核空间,将程序的运行空间分为内核空间和用户空间,它们运行在不同的级别上,在逻辑上是相互隔离的。在Linux中,用户程序不能直接访问内核提供的服务,必须通过系统调用来使用内核提供的服务。用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间函数。
Linux中的用户编程接口(API)遵循了UNIX中最流行的应用编程界面标准-POSIX标准。(这些系统调用编程接口主要是通过C库(libc)实现的。)这套标准定义了一系列API。在Linux中(Unix也如此)这些API主要是通过C库(libc)实现的,它除了定义的一些标准的C函数外,一个很重要的任务就是提供了一套封装例程(wrapper routine)将系统调用在用户空间包装后供用户编程使用。
可用的文件I/O函数很多,包括:打开文件、读文件、写文件等。大多数Linux文件I/O只需要用到5个函数:open、read、write、lseek以及close。这5个函数是不带缓存的I/O函数,它们是POSIX标准的组成部分。
在C语言中也有文件I/O函数,例如:fread、fwrite、fprintf、fscanf等。这些函数是带缓存的I/O函数,它们属于ANSI C的组成部分。
Linux下系统调用、API、系统命令、内核函数的区别与联系
1. 系统调用:应用程序和内核间的桥梁,是应用程序访问内核的入口点;但通常情况下,应用程序通过操作系统提供的API进行编程而不是使用系统调用直接编程; linux的全部系统调用加起来大约只有250个左右。
这些系统调用按照功能逻辑大致可分为“进程控制”、“文件系统控制”、“系统控制”、“存管管理”、“网络管理”、“socket控制”、“用户管理”、“进程间通信”几类;
可以使用man 2 syscalls 命令查看系统调用的说明,或者到 <内核源码目录>/include/asm-i386/unistd.h源文件种找到它们的原本。
1.系统调用在内核里的主要用途:控制硬件;设置系统状态或读取内核数据;进程管理
2.什么的服务应该存在于内核,或者说什么功能应该在内核而不是在用户空间;(如服务是否必须获得内核数据;安全的角度;效率的角度等)
2. API:API常以C库(libc)的形式提供,C库提供了绝大部分API,每个系统调用在C库中都有对应的封装函数(通常封装函数与系统调用的名称相同)。系统调用与C库函数并不是一一对应的,有些C库函数可能使用多个系统调用来实现,也有可能多个C库函数使用同一个系统调用来实现,也有些C库函数不使用任何系统调用来实现。
3. 系统命令:系统命令是使用Linux提供的C库函数实现的可执行程序,可用strace查看命令执行时所使用的系统调用。
4. 内核函数:系统调用是用户进入内核的接口,它本身不是内核函数,进入内核后每个系统调用会找到自己对应的内核函数(即系统调用服务例程)
从用户的角度看,从底层往上看分别是:内核函数,系统调用,API,系统命令
内核函数和系统调用的关系:内核函数和普通函数很像,只不过在内核中实现,因此要满足一些内核编程的要求。系统调用是一层用户进入内核的接口,它本身并非内核函数,进入内核后,不同的系统调用会找到对应到各自的内核函数即,系统调用服务例程。实际对请求服务的是内核函数而非调用接口(系统调用)。比如系统调用getpid实际就是调用内核函数sys_getpid。
Linux下不带缓存的I/O和带缓存的I/O:
概念:
不带缓存的I/O:
UNIX的文件I/O read、write是不带缓存的,不带缓存是指每个read、write都调用内核的一个系统调用,它们是POSIX.1的组成部分。不是内核不提供缓存,而是对于用户层来说,没有提供缓存,而对内核来说还是有缓存的
数据:数据流->内核缓存->磁盘
带缓存的I/O:
是指在用户层上再建立了一层缓存区(流缓存区),目的是为了减少read,write等系统调用的使用次数,降低系统开销。带缓存的I/O是指标准I/O库,它由ANSI C标准说明,标准I/O库代替用户处理很多细节,比如缓存分配、以优化长度执行I/O等,提供缓存的目的是为了尽量减少read和write的调用次数;
数据:数据流->流缓存区->内核缓存->磁盘
例如:当用fwrite函数向磁盘写数据时,先把数据写入流缓冲区中,当达到一定条件,比如流缓冲区满了,或刷新流缓冲,这时候才会把数据一次送往内核提供的块缓冲,再经块缓冲写入磁盘。(双重缓冲)
用数据流来形容两者的差异:
无缓存I/O操作的数据流:数据->内核缓存区->磁盘
标准I/O操作的数据流:数据->流缓存区->内核缓存区->磁盘
文件描述符
对内核而言,所有打开文件都由文件描述符应用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。
当读、写一个文件时,用open或create返回的文件描述符标识该文件,将其作为参数传送给read或write。在windows环境下,文件描述符大体上相当于文件句柄。
在POSIX.1应用程序中,整数0、1、2应被代换成符号常数:
STDIN_FILENO 代表标准输入(默认是键盘)、标准输出(默认是屏幕)、标准错误输出(默认是屏幕),这些常数都定义在头文件<unistd.h>中。
多进程编程
进程是一个具有一定独立功能的程序对某个数据集合的一次运行活动。进程的实体结构:
1. 进程控制块(PCB);
2. 程序段;
3. 数据段;
PCB位于内核的内存里面的一块区域,是操作系统中最重要的记录型数据结构。PCB中记录了操作系统所需的,用于描述进程进展情况及控制进程所需的全部信息。
进程块主要包括下述4方面的信息:
1. 进程描述信息
2. 处理机状态信息
3. 进程调度信息:进程状态、进程优先级(用于描述进程使用处理机的优先级别的一个整数,优先级别高的进程先获得处理机)、进程调度所需的其他信息(如进程已等待CPU的时间总和、进程已执行的时间总和等)、事件(指进程被阻塞的原因)。
4. 进程控制信息:程序和数据的地址(指出该进程的程序和数据所在的内存或外存地址,以便再调度到该进程执行时,能从中找到其程序和数据);进程同步和通信机制(指实现进程同步和进程通信时所必须的机制,如消息队列指针、信号量等。这些数据应全部或部分存放在PCB中)。
进程的三种状态:
进程的三种基本状态:
(1)就绪状态:进程已获得除CPU外的所有必要资源,只等待CPU时的状态。一个系统会将多个处于就绪状态的进程排成一个就绪队列。
(2)执行状态:进程已获CPU,正在执行。单处理机系统中,处于执行状态的进程只一个;多处理机系统中,有多个处于执行状态的进程。
(3)阻塞状态:正在执行的进程由于某种原因而暂时无法继续执行,便放弃处理机而处于暂停状态,即进程执行受阻。(这种状态又称等待状态或封锁状态)
通常导致进程阻塞的典型事件有:请求I/O,申请缓冲空间等。
进程间通信
进程间通信有如下一些目的:
1. 数据传输
2. 共享数据
3. 通知事件
4. 资源共享的同步
5. 进程控制
多线程编程初步
线程是一个程序内部可以被操作系统调度并发运行的任务。
在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就只能采用几种进程间通信的方式,但无论是信号、管道、共享内存,还是信号量,它们的操作都比较繁杂,不直观;而且大多数要使用内核对象,数据要在用户空间和内核空间进行切换,开销很大。
但如果采用多线程方式,线程间的通信(数据交换)既直观又效率高,例如可以使用共享的全局变量。
和进程相比,线程是一种“节俭”的多任务操作方式。运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需的时间。
(据统计,总的来说,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别)
常用的线程函数
1. 创建线程 : pthread_create
int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, (void*)(*start_rtn)(void*),void *arg);
若线程创建成功,则返回0。若线程创建失败,则返回出错编号,并且*thread中的内容是未定义的。
返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于指定各种不同的线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。
linux下用C语言开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。
参数
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。当线程运行函数的参数不止一个时,那么需要把这些参数放到一个结构中,然后将找个结构的地址当作参数传输参数。
2. 线程互斥锁的初始化 pthread_mutex_init
int pthread_mutex_init (pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init()函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为空,则使用默认的互斥锁属性,默认属性为快速互斥锁 。互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
3.线程初始化条件变量函数pthread_cond_init();
extern int pthread_cond_init ((pthread_cond_t * cond, const pthread_condattr_t *cond_attr));
其中cond是一个指向结构pthread_cond_t的指针,
cond_attr是一个指向结构pthread_condattr_t的指针。
结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用;如果选择为PTHREAD_PROCESS_SHARED则为多个进程间各线程公用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。
返回值:函数成功返回0;任何其他返回值都表示错误。
释放一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t *cond)。
3. 结束线程:pthread_exit //线程自行结束(自杀)使用pthread_exit
4. 等待线程结束:pthread_join
pthread_create调用成功后,新生成的线程和老线程谁先被调度执行,取决于OS,应用程序无法控制。因此,应用程序员如需等待指定线程结束,需要使pthread_join。
int pthread_join(pthread_t th, void ** thread_return);
th 是要等待结束的线程的标识。
指针thread_return指向的位置存放的是终止线程的返回状态。
5. sched_yield()这个函数可以使另一个级别等于或高于当前线程的线程先运行。如果没有符合条件的线程,那么这个函数将会立刻返回,然后继续执行当前线程的程序。
在成功完成之后返回零,否则返回-1.
多线程的同步与互斥
由于多个线程间极有可能回访问共享的资源(典型的是访问同一个全局变量),因此多个线程间存在竞争,这就需要在多个线程间进行同步。
互斥锁
多线程的同步采用加锁的机制,类似于多进程间的信号量机制。在主线程中初始化锁为解锁状态(类似于初始化0-1信号量为1)
1. 在编译时初始化锁为解锁状态;
2. 在访问共享对象前进行加速操作(类似于对信号量执行P操作);
3. 在访问共享对象后进行解锁操作(类似于对信号量执行V操作);
信号量
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。从本质上讲,互斥锁就是一个0-1信号量。因此当公共资源总量超过1时,互斥锁就不能用于线程同步了,此时可以采用信号量。
信号量的本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用sem_post()函数增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,调用函数sem_wait()减少信号量。
线程属性
创建线程时指定线程的属性
线程的绑定
轻进程:可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的。
一个轻进程可以控制一个或多个线程。
默认情况下,启动多少轻进程,哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。
绑定:是某个线程固定的“邦”在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
设置线程绑定状态的函数为:
线程创建后改变属性
一个线程如果想杀死另一个线程的话,可以通过调用pthread_cancel来完成。类似于多进程中的kill。
线程调度和优先级设置
线程是独立执行的。它们被分派到处理器内核上,并执行分给它们的任务。每个线程均有一个调度策略和优先级,决定何时以及如何分配到处理器上。线程或线程组的调度策略可以使用这些函数通过属性对象来设置:
调用形式
#include <pthread.h>
#include <sched.h>
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
void pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_setschedparam(pthread_attr_t *restrict attr,const struct sched_param *restrict param);
pthread_attr_setinheritesched( )用于确定如何设置线程的调度属性,可以从创建者线程或从一个属性对象来继承调度属性。inheritsched可以为如下值:
PTHREAD_INHERIT_SCHED:线程调度属性是从创建者线程继承得到,attr的任何调度属性都被忽略。
PTHREAD_EXPLICIT_SCHED:线程调度属性设置为属性对象attr的调度属性。
如果inheritsched值是PTHREAD_EXPLICIT_SCHED,则pthread_attr_setschedpolicy( )被用于设置调度策略,而pthread_attr_setschedparam( )被用于设置优先级。
pthread_attr_setschedpolicy( )设置线程属性对象attr的调度策略。policy的值可以为在<sched.h>头文件中定义的以下值。
SCHED_FIFO:先进先出调度策略,执行线程运行到结束。
SCHED_RR:轮询调度策略,按照时间片将每个线程分配到处理器上。
SCHED_OTHER:另外的调度策略(根据实现定义)。这是任何新创建线程的默认调度策略。
使用pthread_attr_setschedparam( )可以设置调度策略所使用的属性对象attr的调度参数。param是包含参数的结构体。sched_param结构体至少需要定义这个数据成员:
struct sched_param {
int sched_priority;
//...
};
它可能还有其他的数据成员,以及多个用来返回和设置最小优先级、最大优先级、调度器、参数等的函数。如果调度策略是SCHED_FIFO或SCHED_RR,那么要求具有值的唯一成员是sched_priority。
按照如下方法使用sched_get_priority_max( )和sched_get_priority_max( ),可以得到优先级的最大值和最小值。
设置调度策略:sched_setscheduler()函数
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
sched_setscheduler()函数将pid所指定进程的调度策略和调度参数分别设置为param指向的sched_param结构中指定的policy和参数。sched_param结构中的sched_priority成员的值可以为任何整数,该整数位于policy所指定调度策略的优先级范围内(含边界值)。policy参数的可能值在头文件中定义。
1. 如果存在pid所描述的进程,将会为进程ID等于pid的进程设置调度策略和调度参数。
2. 如果pid为零,将会为调用进程设置调度策略和调度参数。
3. 如果进程pid含多个进程或轻量进程(即该进程是多进程的),此函数将影响进程中各个子进程。
更改其他进程的调度参数需要有相应的特权。调用进程必须具有相应的特权,或者是具有PRIV_RTSCHED权限的组的成员,才能成功调用sched_setscheduler()。如果sched_setscheduler()函数成功地将pid所指定调度策略和调度参数分别设置为policy和结构param指定值 ,则该函数调用成功。
网络编程
展开阅读全文