操作系统-Linux环境下C语言编程课件_第1页
操作系统-Linux环境下C语言编程课件_第2页
操作系统-Linux环境下C语言编程课件_第3页
操作系统-Linux环境下C语言编程课件_第4页
操作系统-Linux环境下C语言编程课件_第5页
已阅读5页,还剩157页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux环境下C语言编程2007年10月Linux环境下C语言编程2007年10月1GCC简介GCC简介2C语言和LinuxLINUX中包含了很多软件开发工具。它们中的很多是用于C和C++应用程序开发的。C是一种能在UNIX的早期就被广泛使用的通用编程语言。它最早是由Bell实验室的DennisRitchie为了UNIX的辅助开发而写的,从此C就成为世界上使用最广泛的计算机语言。C能在编程领域里得到如此广泛支持的原因有:(1)它是一种非常通用的语言,并且它的语法和函数库在不同的平台上都是统一的,对开发者非常有吸引力;(2)用C写的程序执行速度很快;(3)C是所有版本UNIX上的系统语言;C语言和LinuxLINUX中包含了很多软件开发工具。它们中3GNUC编译器-gccLINUX上可用的C编译器是GNUC编译器,它建立在自由软件基金会编程许可证的基础上,因此可以自由发布。LINUX上的GNUC编译器(gcc)是一个全功能的ANCIC兼容编译器,而一般UNIX(如SCOUNIX)用的编译器是CC。GNUC编译器-gccLINUX上可用的C编译器是GNU4文件后缀文件后缀5Gcc编译文件gcc指令的一般格式为:gcc[选项]要编译的文件[选项][目标文件]其中,目标文件可缺省,gcc默认生成可执行的文件为:编译文件.out如:gcc–ohellohello.cGcc编译文件gcc指令的一般格式为:6GCC编译流程预处理(Preprocessing)编译(Compiling)汇编(Assembling)连接(Linking)GCC编译流程预处理(Preprocessing)7hello.c#include<stdio.h>intmain(){printf("Hello!Thisisourworld!\n");return0;}hello.c#include<stdio.h>8预处理阶段

在该阶段,编译器将上述代码中的stdio.h编译进来,并且用户可以使用Gcc的选项“-E”进行查看,该选项的作用是让Gcc在预处理结束后停止编译过程。编译命令:

gcc–Ehello.c–ohello.igcc进行了预处理,它把“stdio.h”的内容插入到hello.i文件中。预处理阶段

在该阶段,编译器将上述代码中的stdio.h编译9编译阶段在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成汇编语言。用户可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。编译命令: gcc–Shello.i–ohello.s编译阶段在这个阶段中,Gcc首先要检查代码的规范性、是否有10汇编阶段汇编阶段是把编译阶段生成的“.s”文件转成目标文件,在此可使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码了。编译命令:gcc–chello.s–ohello.o汇编阶段汇编阶段是把编译阶段生成的“.s”文件转成目标文件,11链接阶段系统把”stdio.h”中的实现都在libc.so.6库文件中,在没有特别指定时,gcc会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数“printf”了。编译命令: gcchello.o–ohello链接阶段系统把”stdio.h”中的实现都在libc.so.12运行程序[root@localhost]#./hello Hello!Thisisourworld!运行程序[root@localhost]#./hello13Gcc常用编译选项Gcc常用编译选项14Gdb调试器Gdb调试器是一款GNU开发组织并发布的UNIX/Linux下的程序调试工具。虽然,它没有图形化的友好界面,但是它强大的功能也足以与微软的VC工具等媲美。能监视程序中变量的值能设置断点以使程序在指定的代码行上停止执行能一行行执行代码Gdb调试器Gdb调试器是一款GNU开发组织并发布的U15基本gdb命令命令 描述file 装入想要调试的可执行文件.kill 终止正在调试的程序.list 列出产生执行文件的源代码的一部分.next 执行一行源代码但不进入函数内部.step 执行一行源代码而且进入函数内部.run 执行当前被调试的程序quit 终止gdbwatch 使你能监视一个变量的值而不管它何时被改变.print 显示表达式的值break 在代码里设置断点,这将使程序执行到这里时被挂起.make 能不退出gdb就可以重新产生可执行文件.shell 能不离开gdb就执行UNIXshell命令.基本gdb命令命令 描述16一个调试的例子-gdbtest.c#include<stdio.h>intsum(intm);intmain(){inti,n=0;sum(50);for(i=1;i<=50;i++){n+=i;}printf("Thesumof1-50is%d\n",n);}intsum(intm){inti,n=0;for(i=1;i<=m;i++)n+=i;printf("Thesumof1-mis%d\n",n);}一个调试的例子-gdbtest.c#include<std17编译程序使用Gcc对test.c进行编译,注意一定要加上选项“-g”,这样编译出的可执行代码中才包含调试信息,否则之后Gdb无法载入该可执行文件。gcc-ggdbtest.c-ogdbtest编译程序使用Gcc对test.c进行编译,注意一定要加上选项18用Gdb调试1开始调试:gdbgdbtest……(gdb)2查看文件命令:l列出带有行号的代码用Gdb调试1开始调试:19用Gdb调试(续)3设置断点设置断点是调试程序中是一个非常重要的手段,它可以使程序到一定位置暂停它的运行。因此,程序员在该位置处可以方便地查看变量的值、堆栈情况等,从而找出代码的症结所在。在Gdb中设置断点非常简单,只需在“b”后加入对应的行号即可。如:(Gdb)b6Breakpoint1at0x804846d:filegdbtest.c,line6.用Gdb调试(续)3设置断点20用Gdb调试(续)查看断点情况:键入“infob”来查看设置断点情况:(Gdb)infobNumTypeDispEnbAddressWhat1breakpointkeepy0x0804846dinmainattest.c:64运行代码:Gdb默认从首行开始运行代码,可键入“r”(run)即可(若想从程序中指定行开始运行,可在r后面加上行号)。(Gdb)rStartingprogram:/root/workplace/Gdb/testReadingsymbolsfromsharedobjectreadfromtargetmemory...done.LoadedsystemsuppliedDSOat0x5fb000Breakpoint1,main()attest.c:66sum(50);用Gdb调试(续)查看断点情况:键入“infob”来查看21用Gdb调试(续)5查看变量值在Gdb中只需键入“p”+变量值即可,如下所示:(Gdb)pn$1=0(Gdb)pi$2=134518440用Gdb调试(续)5查看变量值22用Gdb调试(续)6单步运行单步运行可以使用命令“n”(next)或“s”(step),它们之间的区别在于:若有函数调用的时候,“s”会进入该函数而“n”不会进入该函数。(Gdb)nThesumof1-mis12757for(i=1;i<=50;i++)(Gdb)ssum(m=50)attest.c:1616inti,n=0;用Gdb调试(续)6单步运行23用Gdb调试(续)7恢复程序运行在查看完所需变量及堆栈情况后,就可以使用命令“c”(continue)恢复程序的正常运行了。这时,它会把剩余还未执行的程序执行完,并显示剩余程序中的执行结果。(Gdb)cContinuing.Thesumof1-50is:1275Programexitedwithcode031.用Gdb调试(续)7恢复程序运行24Linux多线程技术Linux多线程技术25POSIX线程库Pthreads使用fork()创建进程代价昂贵进程间通信方式较复杂操作系统在实现进程间的切换比线程切换更费时

使用pthreads库创建线程创建进程比创建线程更快线程间的通信方式更容操作系统对线程的切换比对进程的切换更容易和快速

POSIX线程库Pthreads使用fork()创建进26线程的创建#include<pthread.h>intpthread_create(pthread_t*thread,pthread_attr_t*attr,void*(*start_routine)(void*),void*arg );第一个参数为指向线程标识符的指针。第二个参数用来设置线程属性。第三个参数是线程运行函数的起始地址。最后一个参数是运行函数的参数。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。线程的创建#include<pthread.h>27一个简单例子#include<stdio.h>

#include<pthread.h>

#include<string.h>

#include<sys/types.h>

#include<unistd.h>

pthread_tntid;

void*thr_fn(void*arg){

printids("newthread:");

return((void*)0);

}

intmain(){

interr;

err=pthread_create(&ntid,NULL,thr_fn,NULL);

if(err!=0){

printf("can'tcreatethread:%s\n",strerror(err));

return1;

}

sleep(1);

return0;

}一个简单例子#include<stdio.h>

#inclu28编译多线程程序gcc-omypthread-lpthreadmypthread.c

编译多线程程序gcc-omypthread-lpthr29线程的退出调用pthread_exit()结束线程执行voidpthread_exit(void*retval);让线程处理程序返回使用pthread_cancel()函数终止其他线程的执行intpthread_cancel(pthread_tthread);向线程t发送取消请求,默认情况下线程thread自己调用pthread_exit(PTHREAD_CANCELED),线程的退出调用pthread_exit()结束线程执行30等待线程结束使用pthread_join()函数等待被创建的线程结束pthread_join()函数会挂起创建线程的线程的执行直到等待到想要等待的子线程函数原型:intpthread_join(pthread_tth,void**thread_return);等待线程结束使用pthread_join()函数等待被31线程的分离主线程可以不断地创建子线程子线程本身自己有自我回收内存资源的能力函数原型:intpthread_detach(pthread_tth);pthread_detach()和pthread_join()一般情况下不能同时使用线程的分离主线程可以不断地创建子线程32获得当前线程的标志pthread_tpthread_self(void);本函数返回本线程的标识符。在LinuxThreads中,每个线程都用一个pthread_descr结构来描述,其中包含了线程状态、线程ID等所有需要的数据结构,此函数的实现就是在线程栈帧中找到本线程的pthread_descr结构,然后返回其中的p_tid项。

获得当前线程的标志pthread_tpthread_sel33一个例子#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<pthread.h>#defineTHREAD_NUMBER2intretval_hello1=2,retval_hello2=3;void*hello1(void*arg){ char*hello_str=(char*)arg;sleep(1);printf("%s\n",hello_str);pthread_exit(&retval_hello1);}一个例子#include<stdio.h>34void*hello2(void*arg){char*hello_str=(char*)arg;sleep(2);printf("%s\n",hello_str);pthread_exit(&retval_hello2);}intmain(intargc,char*argv[]){inti;intret_val;int*retval_hello[2];

pthread_tpt[THREAD_NUMBER];constchar*arg[THREAD_NUMBER];arg[0]="helloworldfromthread1";arg[1]="helloworldfromthread2";void*hello2(void*arg)35printf("Begintocreatethreads...\n");ret_val=pthread_create(&pt[0],NULL,hello1,(void*)arg[0]);if(ret_val!=0){printf("pthread_createerror!\n");exit(1);}ret_val=pthread_create(&pt[1],NULL,hello2,(void*)arg[1]);if(ret_val!=0){printf("pthread_createerror!\n");exit(1);}printf("Begintocreatethread36printf("Begintowaitforthreads...\n");for(i=0;i<THREAD_NUMBER;i++){ret_val=pthread_join(pt[i],(void**)&retval_hello[i]);if(ret_val!=0){printf("pthread_joinerror!\n"); exit(1);}else{printf("returnvalueis%d\n",*retval_hello[i]);}}printf("Now,themainthreadreturns.\n");return0;}printf("Begintowaitforthre37线程属性的初始化和撤销线程初始化: intpthread_attr_init(pthread_attr_t*attr)初始化线程属性对象attr,并用默认值填充线程撤销: intpthread_attr_destroy(pthread_attr_t*attr)销毁线程属性对象attr。*修改线程属性对象attr只有在线程创建前有效,在线程创建后修改对当前线程不起作用。*返回0成功,否则失败。线程属性的初始化和撤销线程初始化:38pthread_attr_t定义pthread_attr_t定义:

typedefstruct__pthread_attr_s

{

int__detachstate;

int__schedpolicy;

struct__sched_param__schedparam;

int__inheritsched;

int__scope;

size_t__guardsize;

int__stackaddr_set;

void*__stackaddr;

size_t__stacksize;

}pthread_attr_t;pthread_attr_t定义pthread_attr_t39线程的属性属性名意义

detachstate选择被创建的线程是处于可加入的状态还是分离状态

schedpolicy为被创建的线程选择调度策略。

schedparam为被创建的线程选择调度参数。

inheritsched选择对新创建的线程的调度策略和调度参数是否被schedpolicy和schedparam属性决定或者是通过父线程继承而得到的

scope为选择被创建的线程调度竞争范围。线程的属性属性名意义detachstate选择被创建的线程40线程的属性(续)detachstatePTHREAD_CREATE_JOINABLEPTHREAD_CREATE_DETACHED

默认:PTHREAD_CREATE_JOINABLE控制创建的线程是joinable态还是detached态schedpolicySCHED_OTHER(regular,non-realtimescheduling)SCHED_RR

(realtime,

round-robin)SCHED_FIFO(realtime,first-infirst-out)

默认:SCHED_OTHER优先级类别Schedparam默认:0线程优先级参数如果schedpolicy的值为SCHED_OTHER,schedpolicy此属性无关紧要线程创建后可修改此属性inheritschedPTHREAD_EXPLICIT_SCHEDPTHREAD_INHERIT_SCHED

默认:PTHREAD_EXPLICIT_SCHED说明此线程优先级是否继承于父线程还是通过上面两个属性确定scopePTHREAD_SCOPE_SYSTEMPTHREAD_SCOPE_PROCESS

默认:PTHREAD_SCOPE_SYSTEM设置线程绑定状态部分Linux不支持PTHREAD_SCOPE_PROCESS,需要查看man线程的属性(续)detachstatePTHREAD_CRE41相关函数-分离状态设置分离状态:pthread_attr_setdetachstateintpthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate);返回值:函数成功返回0;任何其他返回值都表示错误设置分离状态。参数detachstate的值为:PTHREAD_CREATE_DETACHED、PTHREAD_CREATE_JOINABLE。获取分离状态:pthread_attr_getdetachstateintpthread_attr_getdetachstate(pthread_attr_t*attr,int*detachstate);返回值:函数成功返回0;任何其他返回值都表示错误取线程分离状态:分离的或是非分离的。相关函数-分离状态设置分离状态:pthread_attr_s42相关函数-调度策略设置调度策略:pthread_attr_setschedpolicyintpthread_attr_setschedpolicy(pthread_attr_t*tattr,intpolicy);返回值:函数成功返回0;任何其他返回值都表示错误。POSIX标准定义的调度策略有:SCHED_FIFO(先入先出)、SCHED_RR(循环)、SCHED_OTHER(由不同版本的POSIX线程库定义的缺省调度策略)。获取调度策略:pthread_attr_getschedpolicyintpthread_attr_getschedpolicy(pthread_attr_t*tattr,int*policy);返回值:函数成功返回0;任何其他返回值都表示错误。相关函数-调度策略设置调度策略:pthread_attr_s43相关函数-调度参数设置调度参数:pthread_attr_setschedparamintpthread_attr_setschedparam(pthread_attr_t*tattr,conststructsched_param*param);返回值:函数成功返回0;任何其他返回值都表示错误。属性对象的调度参数定义在param结构中;在这个结构中只定义了优先级priority成员。新创建线程的优先级由属性对象中param结构的priority参数指定。有两种方式可以修改线程的优先级。可以在创建子线程前设置属性对象的优先级参数;也可以先修改父线程的优先级,然后创建子线程。sched_param结构中有可能存放着其他一些调度信息。所以在修改线程属性对象的调度参数前先取现有的调度参数是良好的习惯。一段合理的代码应该是这样的:先取线程属性对象中现有的调度参数,对取出的调度参数进行操作,再用修改过的调度参数重置线程属性对象。

获取调度参数:pthread_attr_getschedparamintpthread_attr_getschedparam(pthread_attr_t*tattr,conststructsched_param*param);返回值:函数成功返回0;任何其他返回值都表示错误。相关函数-调度参数设置调度参数:pthread_attr_s44相关函数-域设置域:pthread_attr_setscopeintpthread_attr_setscope(pthread_attr_t*tattr,intscope);返回值:函数成功返回0;任何其他返回值都表示错误。指定将来创建的线程是绑定(PTHREAD_SCOPE_SYSTEM)的还是非绑定的(PTHREAD_SCOPE_PROCESS)。在一个进程中可以同时有这两种不同类型的线程。

获取域:pthread_attr_getscopeintpthread_attr_getscope(pthread_attr_t*tattr,int*scope);返回值:函数成功返回0;任何其他返回值都表示错误。相关函数-域设置域:pthread_attr_setscop45例子:threadattr#include<stdio.h>#include<stdlib.h>#include<pthread.h>void*sum_val(void*arg){ intsum=0;inti;intcount=*(int*)arg;for(i=0;i<count;i++) sum=sum+i;printf("sumis%d\n",sum);pthread_exit(0);}例子:threadattr#include<stdio.h46例子:threadattr(续)intmain(intargc,char*argv[]){

pthread_tpt;intcount=10;intret_val;pthread_attr_tattr;structsched_paramsp;sp._sched_priority=2;

ret_val=pthread_attr_init(&attr);if(ret_val!=0){printf("pthread_attr_initerror!\n");exit(1);}例子:threadattr(续)intmain(inta47例子:threadattr(续)ret_val=pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);if(ret_val!=0){printf("pthread_attr_setdetachstateerror!\n");exit(1);}ret_val=pthread_attr_setschedpolicy(&attr,SCHED_RR);if(ret_val!=0){printf("pthread_attr_setschedpolicyerror!\n");exit(1);}ret_val=pthread_attr_setschedparam(&attr,&sp);if(ret_val!=0){printf("pthread_attr_setschedparamerror!\n");exit(1);}例子:threadattr(续)ret_val=pthr48例子:threadattr(续)ret_val=pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);if(ret_val!=0){printf("pthread_attr_setinheritschederror!\n");exit(1);}ret_val=pthread_attr_setscope(&attr,PTHREAD_SCOPE_SYSTEM);if(ret_val!=0){printf("pthread_attr_setscopeerror!\n");exit(1);}ret_val=pthread_create(&pt,NULL,sum_val,(void*)&count);if(ret_val!=0){printf("pthread_createerror!\n");exit(1);}pthread_attr_destroy(&attr);sleep(5);return0;}例子:threadattr(续)ret_val=pthr49Linux下线程的同步和互斥Linux下线程的同步和互斥50mutexMutex:互斥设备(MUTualExclusiondevice)mutex有如下特性:原子性:对mutex的加锁和解锁操作是原子的单一性:拥有mutex的线程除非释放mutex,否则其他线程不能拥有此mutex非忙等待:等待mutex的线程处于等待状态,直到要等待的mutex处于未加锁状态,这时操作系统负责唤醒等待此mutex的线程mutexMutex:互斥设备(MUTualExclusi51Mutex类型在POSIX线程库中,存在三中类型的mutex:快速(fast)mutex递归(recursive)mutex:获得锁的线程可以多次加锁错误检测(errorchecking)mutex:锁定时返回错误Mutex类型在POSIX线程库中,存在三中类型的mute52与mutex相关函数intpthread_mutex_init(pthread_mutex_t*mutex,constpthread_mutexattr_t*mutexattr);intpthread_mutex_lock(pthread_mutex_t*mutex);intpthread_mutex_trylock(pthread_mutex_t*mutex);intpthread_mutex_unlock(pthread_mutex_t*mutex);intpthread_mutex_destroy(pthread_mutex_t*mutex);与mutex相关函数intpthread_mutex_i53初始化互斥锁intpthread_mutex_init(pthread_mutex_t*mp,constpthread_mutexattr_t*mattr);成功完成之后会返回零。其他任何返回值都表示出现了错误。初始化互斥锁intpthread_mutex_init(54例子#include<pthread.h>pthread_mutex_tmp=PTHREAD_MUTEX_INITIALIZER;pthread_mutexattr_tmattr;intret;/*initializeamutextoitsdefaultvalue*/ret=pthread_mutex_init(&mp,NULL);例子#include<pthread.h>55锁定互斥锁intpthread_mutex_lock(pthread_mutex_t*mutex);在成功完成之后会返回零。其他任何返回值都表示出现了错误。当返回时,该互斥锁已被锁定。调用线程是该互斥锁的属主。互斥锁类型为快速锁:如果该互斥锁已被另一个线程锁定和拥有,则调用线程将阻塞,直到该互斥锁变为可用为止。互斥锁类型为错误锁:则会提供错误检查。如果某个线程尝试重新锁定的互斥锁已经由该线程锁定,则将返回错误。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。互斥锁类型为递归锁:则该互斥锁会保留锁定计数这一概念。线程首次成功获取互斥锁时,锁定计数会设置为1。线程每重新锁定该互斥锁一次,锁定计数就增加1。线程每解除锁定该互斥锁一次,锁定计数就减小1。锁定计数达到0时,该互斥锁即可供其他线程获取。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。锁定互斥锁intpthread_mutex_lock(pt56解除锁定互斥锁intpthread_mutex_unlock(pthread_mutex_t*mutex);释放引用的互斥锁对象。互斥锁的释放方式取决于互斥锁的类型属性。在成功完成之后会返回零。其他任何返回值都表示出现了错误。解除锁定互斥锁intpthread_mutex_unlo57使用非阻塞互斥锁锁定intpthread_mutex_trylock(pthread_mutex_t*mutex);在成功完成之后会返回零。其他任何返回值都表示出现了错误。

是pthread_mutex_tlock的非阻塞版本。如果所引用的互斥对象当前被任何线程(包括当前线程)锁定,则将立即返回该调用。否则,该互斥锁将处于锁定状态,调用线程是其属主。使用非阻塞互斥锁锁定intpthread_mutex_tr58销毁互斥锁intpthread_mutex_destroy(pthread_mutex_t*mp);在成功完成之后会返回零。其他任何返回值都表示出现了错误。销毁互斥锁intpthread_mutex_destroy59使用MUTEX的简单代码pthread_mutex_tmylock;mylock=PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock(&mylock);Dosomething….pthread_mutex_unlock(&mylock);使用MUTEX的简单代码pthread_mutex_tmy60Mutex例子#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<errno.h>#defineTHREAD_NUMBER10staticpthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER;intsum=0;void*inc(void*arg){inti=(*(int*)arg);pthread_mutex_lock(&mutex);sum=sum+i;pthread_mutex_unlock(&mutex);returnNULL;}Mutex例子#include<stdio.h>61Mutex例子(续)intmain(intargc,char*argv[]){pthread_tpt[THREAD_NUMBER];inti;intarg[THREAD_NUMBER]; for(i=0;i<THREAD_NUMBER;i++){arg[i]=i; if(pthread_create(&pt[i],NULL,inc,(void*)&arg[i])!=0){printf("pthread_createerror\n");exit(1);}}Mutex例子(续)intmain(intargc,c62Mutex例子(续)for(i=0;i<THREAD_NUMBER;i++)if(pthread_join(pt[i],NULL)!=0){printf("pthread_joinerror\n");exit(1);}printf("sumis%d\n",sum);pthread_mutex_destroy(&mutex);return0;}Mutex例子(续)for(i=0;i<THREAD_NU63条件变量可以使得一个线程在执行过程中,因满足某个条件而发出信号通知另一个线程。而另一个线程可以处于挂起状态,等待某个条件的满足后,才继续执行。条件变量必须和mutex一起使用来避免竞争。条件变量可以使得一个线程在执行过程中,因满足某个条件而发出64条件变量相关的操作函数pthread_cond_tcond=PTHREAD_COND_INITIALIZER;intpthread_cond_init(pthread_cond_t*cond,pthread_condattr_t*cond_attr);intpthread_cond_signal(pthread_cond_t*cond);intpthread_cond_broadcast(pthread_cond_t*cond);intpthread_cond_wait(pthread_cond_t*cond,pthread_mutex_t*mutex);intpthread_cond_timedwait(pthread_cond_t*cond,pthread_mutex_t*mutex,conststructtimespec*abstime);intpthread_cond_destroy(pthread_cond_t*cond);在成功完成之后会返回零。其他任何返回值都表示出现了错误。

条件变量相关的操作函数pthread_cond_tcon65初始化条件变量intpthread_cond_init(pthread_cond_t*cv,constpthread_condattr_t*cattr);使用PTHREAD_COND_INITIALIZER宏可以将以静态方式定义的条件变量初始化为其缺省属性。PTHREAD_COND_INITIALIZER宏与动态分配具有null属性的pthread_cond_init等效,但是不进行错误检查。多个线程决不能同时初始化或重新初始化同一个条件变量。如果要重新初始化或销毁某个条件变量,则应用程序必须确保该条件变量未被使用。初始化条件变量intpthread_cond_init(p66基于条件变量阻塞intpthread_cond_wait(pthread_cond_t*cond,pthread_mutex_t*mutex);阻塞的线程可以通过pthread_cond_signal或pthread_cond_broadcast唤醒,也可以在信号传送将其中断时唤醒。该条件获得信号之前,该函数一直被阻塞。该函数会在被阻塞之前以原子方式释放相关的互斥锁,并在返回之前以原子方式再次获取该互斥锁。通常,对条件表达式的评估是在互斥锁的保护下进行的。如果条件表达式为假,线程会基于条件变量阻塞。然后,当该线程更改条件值时,另一个线程会针对条件变量发出信号。这种变化会导致所有等待该条件的线程解除阻塞并尝试再次获取互斥锁。必须重新测试导致等待的条件,然后才能从pthread_cond_wait处继续执行。唤醒的线程重新获取互斥锁并从pthread_cond_wait返回之前,条件可能会发生变化。等待线程可能并未真正唤醒。建议使用的测试方法是,将条件检查编写为调用pthread_cond_wait的while循环:pthread_mutex_lock();while(condition_is_false)pthread_cond_wait();pthread_mutex_unlock();基于条件变量阻塞intpthread_cond_wait67解除阻塞一个线程intpthread_cond_signal(pthread_cond_t*cond);应在互斥锁的保护下修改相关条件,该互斥锁用于获得信号的条件变量中。否则,可能在条件变量的测试和pthread_cond_wait阻塞之间修改该变量,这会导致无限期等待。解除阻塞一个线程intpthread_cond_sign68在指定的时间之前阻塞intpthread_cond_timedwait(pthread_cond_t*cond,pthread_mutex_t*mutex,conststructtimespec*abstime);

每次返回时调用线程都会锁定并且拥有互斥锁,即使返回错误时也是如此。函数会一直阻塞,直到该条件获得信号,或者最后一个参数所指定的时间已过为止。在指定的时间之前阻塞intpthread_cond_69解除阻塞所有线程intpthread_cond_broadcast(pthread_cond_t*cond);使用pthread_cond_broadcast()可以解除阻塞所有这些线程。解除阻塞所有线程intpthread_cond_broa70销毁条件变量状态intpthread_cond_destroy(pthread_cond_t*cond);用pthread_cond_destroy()可以销毁与cond所指向的条件变量相关联的任何状态。销毁条件变量状态intpthread_cond_dest71例子1#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<pthread.h>#defineTHREAD_NUMBER2staticpthread_cond_tcond=PTHREAD_COND_INITIALIZER;staticpthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER;void*thread1(void*arg){pthread_mutex_lock(&mutex);printf("thread1lockedthemutex\n");printf("thread1iswaitingforconditionsignal...\n");pthread_cond_wait(&cond,&mutex);printf("thread1receivedconditionsignal!\n");pthread_mutex_unlock(&mutex);printf("thread1unlockedthemutex\n");pthread_exit(0);}例子1#include<stdio.h>72例子1(续)void*thread2(void*arg){inti=0;structtimevalold,new;gettimeofday(&old);new=old;pthread_mutex_lock(&mutex);printf("thread2lockedthemutex\n");while(new.tv_sec-old.tv_sec<5){sleep(1);gettimeofday(&new);i++;printf("thread2sleep%dseconds\n",i);}printf("thread1callspthread_cond_signal...\n");pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);printf("thread2unlockedthemutex\n");pthread_exit(0);}例子1(续)void*thread2(void*arg)73例子1(续)intmain(intargc,char*argv[]){inti;intret_val;pthread_tpt[THREAD_NUMBER];ret_val=pthread_create(&pt[0],NULL,thread1,NULL);if(ret_val!=0){printf("pthread_createerror!\n");exit(1);}ret_val=pthread_create(&pt[1],NULL,thread2,NULL);if(ret_val!=0){printf("pthread_createerror!\n");exit(1);}

例子1(续)intmain(intargc,char74例子1(续)for(i=0;i<THREAD_NUMBER;i++){ret_val=pthread_join(pt[i],NULL);if(ret_val!=0){printf("pthread_joinerror!\n"); exit(1);}}pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return0;}例子1(续)for(i=0;i<THREAD_N75例子2……#defineTHREAD_NUMBER2staticpthread_cond_tcond=PTHREAD_COND_INITIALIZER;staticpthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER;staticintx=0,y=0;void*thread1(void*arg){structtimevalnow;structtimespectimeout;intretcode;pthread_mutex_lock(&mutex);gettimeofday(&now);timeout.tv_sec=now.tv_sec+5;timeout.tv_nsec=now.tv_usec*1000;retcode=0;

例子2……76例子2(续)while(x>=y&&retcode!=ETIMEDOUT){retcode=pthread_cond_timedwait(&cond,&mutex,&timeout);}if(retcode==ETIMEDOUT){printf("pthread_cond_timedwaittimeout!\n");}else{printf("thread1gotconditionsignal!\n");}pthread_mutex_unlock(&mutex);pthread_exit(0);}例子2(续)while(x>=y&&retcode77例子2(续)void*thread2(void*arg){inti;for(i=0;i<5;i++){x=rand()%5;y=rand()%5;sleep(1);printf("xis%d,yis%d\n",x,y);if(x<y) break;}pthread_mutex_lock(&mutex);if(x<y){pthread_cond_broadcast(&cond);}pthread_mutex_unlock(&mutex); pthread_exit(0);}例子2(续)void*thread2(void*arg)78例子2(续)intmain(intargc,char*argv[]){inti;intret_val;pthread_tpt[THREAD_NUMBER];ret_val=pthread_create(&pt[0],NULL,thread1,NULL);if(ret_val!=0){printf("pthread_createerror!\n");exit(1);}ret_val=pthread_create(&pt[1],NULL,thread2,NULL);if(ret_val!=0){printf("pthread_createerror!\n");exit(1);}

例子2(续)intmain(intargc,char79例子2(续)for(i=0;i<THREAD_NUMBER;i++){ret_val=pthread_join(pt[i],NULL);if(ret_val!=0){printf("pthread_joinerror!\n"); exit(1);}}pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return0;}例子2(续)for(i=0;i<THREAD_N80操作系统-Linux环境下C语言编程课件81Linux环境下C语言编程2007年10月Linux环境下C语言编程2007年10月82GCC简介GCC简介83C语言和LinuxLINUX中包含了很多软件开发工具。它们中的很多是用于C和C++应用程序开发的。C是一种能在UNIX的早期就被广泛使用的通用编程语言。它最早是由Bell实验室的DennisRitchie为了UNIX的辅助开发而写的,从此C就成为世界上使用最广泛的计算机语言。C能在编程领域里得到如此广泛支持的原因有:(1)它是一种非常通用的语言,并且它的语法和函数库在不同的平台上都是统一的,对开发者非常有吸引力;(2)用C写的程序执行速度很快;(3)C是所有版本UNIX上的系统语言;C语言和LinuxLINUX中包含了很多软件开发工具。它们中84GNUC编译器-gccLINUX上可用的C编译器是GNUC编译器,它建立在自由软件基金会编程许可证的基础上,因此可以自由发布。LINUX上的GNUC编译器(gcc)是一个全功能的ANCIC兼容编译器,而一般UNIX(如SCOUNIX)用的编译器是CC。GNUC编译器-gccLINUX上可用的C编译器是GNU85文件后缀文件后缀86Gcc编译文件gcc指令的一般格式为:gcc[选项]要编译的文件[选项][目标文件]其中,目标文件可缺省,gcc默认生成可执行的文件为:编译文件.out如:gcc–ohellohello.cGcc编译文件gcc指令的一般格式为:87GCC编译流程预处理(Preprocessing)编译(Compiling)汇编(Assembling)连接(Linking)GCC编译流程预处理(Preprocessing)88hello.c#include<stdio.h>intmain(){printf("Hello!Thisisourworld!\n");return0;}hello.c#include<stdio.h>89预处理阶段

在该阶段,编译器将上述代码中的stdio.h编译进来,并且用户可以使用Gcc的选项“-E”进行查看,该选项的作用是让Gcc在预处理结束后停止编译过程。编译命令:

gcc–Ehello.c–ohello.igcc进行了预处理,它把“stdio.h”的内容插入到hello.i文件中。预处理阶段

在该阶段,编译器将上述代码中的stdio.h编译90编译阶段在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成汇编语言。用户可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。编译命令: gcc–Shello.i–ohello.s编译阶段在这个阶段中,Gcc首先要检查代码的规范性、是否有91汇编阶段汇编阶段是把编译阶段生成的“.s”文件转成目标文件,在此可使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码了。编译命令:gcc–chello.s–ohello.o汇编阶段汇编阶段是把编译阶段生成的“.s”文件转成目标文件,92链接阶段系统把”stdio.h”中的实现都在libc.so.6库文件中,在没有特别指定时,gcc会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数“printf”了。编译命令: gcchello.o–ohello链接阶段系统把”stdio.h”中的实现都在libc.so.93运行程序[root@localhost]#./hello Hello!Thisisourworld!运行程序[root@localhost]#./hello94Gcc常用编译选项Gcc常用编译选项95Gdb调试器Gdb调试器是一款GNU开发组织并发布的UNIX/Linux下的程序调试工具。虽然,它没有图形化的友好界面,但是它强大的功能也足以与微软的VC工具等媲美。能监视程序中变量的值能设置断点以使程序在指定的代码行上停止执行能一行行执行代码Gdb调试器Gdb调试器是一款GNU开发组织并发布的U96基本gdb命令命令 描述file 装入想要调试的可执行文件.kill 终止正在调试的程序.list 列出产生执行文件的源代码的一部分.next 执行一行源代码但不进入函数内部.step 执行一行源代码而且进入函数内部.run 执行当前被调试的程序quit 终止gdbwatch 使你能监视一个变量的值而不管它何时被改变.print 显示表达式的值break 在代码里设置断点,这将使程序执行到这里时被挂起.make 能不退出gdb就可以重新产生可执行文件.shell 能不离开gdb就执行UNIXshell命令.基本gdb命令命令 描述97一个调试的例子-gdbtest.c#include<stdio.h>intsum(intm);intmain(){inti,n=0;sum(50);for(i=1;i<=50;i++){n+=i;}printf("Thesumof1-50is%d\n",n);}intsum(intm){inti,n=0;for(i=1;i<=m;i++)n+=i;printf("Thesumof1-mis%d\n",n);}一个调试的例子-gdbtest.c#include<std98编译程序使用Gcc对test.c进行编译,注意一定要加上选项“-g”,这样编译出的可执行代码中才包含调试信息,否则之后Gdb无法载入该可执行文件。gcc-ggdbtest.c-ogdbtest编译程序使用Gcc对test.c进行编译,注意一定要加上选项99用Gdb调试1开始调试:gdbgdbtest……(gdb)2查看文件命令:l列出带有行号的代码用Gdb调试1开始调试:100用Gdb调试(续)3设置断点设置断点是调试程序中是一个非常重要的手段,它可以使程序到一定位置暂停它的运行。因此,程序员在该位置处可以方便地查看变量的值、堆栈情况等,从而找出代码的症结所在。在Gdb中设置断点非常简单,只需在“b”后加入对应的行号即可。如:(Gdb)b6Breakpoint1at0x804846d:filegdbtest.c,line6.用Gdb调试(续)3设置断点101用Gdb调试(续)查看断点情况:键入“infob”来查看设置断点情况:(Gdb)infobNumTypeDispEnbAddressWhat1breakpointkeepy0x0804846dinmainattest.c:64运行代码:Gdb默认从首行开始运行代码,可键入“r”(run)即可(若想从程序中指定行开始运行,可在r后面加上行号)。(Gdb)rStartingprogram:/root/workplace/Gdb/testReadingsymbolsfromsharedobjectreadfromtargetmemory...done.LoadedsystemsuppliedDSOat0x5fb000Breakpoint1,main()attest.c:66sum(50);用Gdb调试(续)查看断点情况:键入“infob”来查看102用Gdb调试(续)5查看变量值在Gdb中只需键入“p”+变量值即可,如下所示:(Gdb)pn$1=0(Gdb)pi$2=134518440用Gdb调试(续)5查看变量值103用Gdb调试(续)6单步运行单步运行可以使用命令“n”(next)或“s”(step),它们之间的区别在于:若有函数调用的时候,“s”会进入该函数而“n”不会进入该函数。(Gdb)nThesumof1-mis12757for(i=1;i<=50;i++)(Gdb)ssum(m=50)attest.c:1616inti,n=0;用Gdb调试(续)6单步运行104用Gdb调试(续)7恢复程序运行在查看完所需变量及堆栈情况后,就可以使用命令“c”(continue)恢复程序的正常运行了。这时,它会把剩余还未执行的程序执行完,并显示剩余程序中的执行结果。(Gdb)cContinuing.Thesumof1-50is:1275Programexitedwithcode031.用Gdb调试(续)7恢复程序运行105Linux多线程技术Linux多线程技术106POSIX线程库Pthreads使用fork()创建进程代价昂贵进程间通信方式较复杂操作系统在实现进程间的切换比线程切换更费时

使用pthreads库创建线程创建进程比创建线程更快线程间的通信方式更容操作系统对线程的切换比对进程的切换更容易和快速

POSIX线程库Pthreads使用fork()创建进107线程的创建#include<pthread.h>intpthread_create(pthread_t*thread,pthread_attr_t*attr,void*(*start_routine)(void*),void*arg );第一个参数为指向线程标识符的指针。第二个参数用来设置线程属性。第三个参数是线程运行函数的起始地址。最后一个参数是运行函数的参数。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。线程的创建#include<pthread.h>108一个简单例子#include<stdio.h>

#include<pthread.h>

#include<string.h>

#include<sys/types.h>

#include<unistd.h>

pthread_tntid;

void*thr_fn(void*arg){

printids("newthread:");

return((void*)0);

}

intmain(){

interr;

err=pthread_create(&ntid,NULL,thr_fn,NULL);

if(err!=0){

printf("can'tcreatethread:%s\n",strerror(err));

return1;

}

sleep(1);

return0;

}一个简单例子#include<stdio.h>

#inclu109编译多线程程序gcc-omypthread-lpthreadmypthread.c

编译多线程程序gcc-omypthread-lpthr110线程的退出调用pthread_exit()结束线程执行voidpthread_exit(void*retval);让线程处理程序返回使用pthread_cancel()函数终止其他线程的执行intpthread_cancel(pthread_tthread);向线程t发送取消请求,默认情况下线程thread自己调用pthread_exi

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论