第6章 嵌入式Linux进程和线程编程 LinuxC语言开发_第1页
第6章 嵌入式Linux进程和线程编程 LinuxC语言开发_第2页
第6章 嵌入式Linux进程和线程编程 LinuxC语言开发_第3页
第6章 嵌入式Linux进程和线程编程 LinuxC语言开发_第4页
第6章 嵌入式Linux进程和线程编程 LinuxC语言开发_第5页
已阅读5页,还剩76页未读 继续免费阅读

下载本文档

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

文档简介

1、嵌入式Linux进程和线程编程课程目标进程相关的根本概念Linux进程的创立Linux进程的管理和守护进程Linux进程间通信的方式Linux中线程创立和退出Linux中线程间的同步和互斥 本章内容6.1 Linux进程概述 6.2 Linux进程控制相关API 6.3 ARM Linux进程间通信 6.4 ARM Linux线程相关API 6.5 Linux守护进程 本章小结6.1 Linux进程概述6.1.1 进程描述符及任务结构 6.1.2 进程的调度 6.1.3 Linux中的线程6.1.1 进程描述符及任务结构进程概念 进程的概念首先在20世纪60年代初期由MIT的Multics系统

2、和IBM的TSS/360系统中引入的。 1进程是一个独立的可调度的活动,。2进程是一个抽象实体,当它执行某个任务时,将要分配和释放各种资源。3进程是可以并行执行的计算局部,。进程和程序有本质的区别:程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念;而进程是一个动态的概念,它是程序执行的过程,包括了动态创立、调度和消亡的整个过程,它是程序执行和资源管理的最小单位。 6.1.1 进程描述符及任务结构Linux中进程描述符进程不但包括程序的指令和数据,而且包括程序计数器和CPU的所有存放器以及存储临时数据的进程堆栈。内核把进程存放在任务队列task list的双向循环链表中,其

3、中链表的每一项都是类型为task_struct,称为进程描述符的结构,该结构定义在中 。6.1.1 进程描述符及任务结构Linux中进程描述符Linux通过slab分配器分配task_struct结构,它实际上是一个栈,其栈顶向上增长的栈或栈底向下增长的栈中有一个thread info结构,其中的task指针指向task_struct。下面详细讲解task_struct结构中最为重要的两个域:state和pid。6.1.1 进程描述符及任务结构Linux中进程描述符1进程状态运行TASK_RUNNING)可中断TASK_INTERUPTIBLE)不可中断TASK_UNINTERUPTIBLE)

4、僵死TASK_ZOMBIE)停止TASK_STOPPED)6.1.1 进程描述符及任务结构Linux中进程描述符2任务标识Linux内核通过惟一的进程标识值PID来标识每个进程。PID是一个非负数,它实际上是一个短整型数据,也就是说它最大值为32767。读者可以查看/proc/sys/kernel/pid_max来确定该系统的进程数上限。一般来说,32767对于很多桌面系统已经足够,但是对于大型效劳器,就必须修改这个上限。6.1.1 进程描述符及任务结构进程的创立、执行和终止 1进程的创立和执行许多操作系统都提供的是产生进程的机制,也就是首先在新的地址空间里创立进程、读入可执行文件,最后再开始

5、执行。 首先,fork()通过拷贝当前进程的内容创立一个子进程,子进程与父进程的区别仅仅在于不同的PID、PPID和其他一些资源。exec函数负责读取可执行文件并将其载入地址空间开始运行。6.1.1 进程描述符及任务结构进程的创立、执行和终止 2进程的终止进程终结也需要做很多繁琐的收尾工作,系统必须保证进程所占用的资源回收,并通知父进程。Linux首先把终止的进程设置为僵死状态,这个时候,进程无法投入运行了。它的存在只为父进程提供信息,申请死亡。父进程得到信息后,开始调用wait (),子进程占用的资源被全部释放。6.1.2 进程的调度Linux中进程调度概述 Linux中进程调度算法 Lin

6、ux中进程调度概述进程调度是指确定CPU当前执行哪个进程。Linux进程调度策略是以优先级调度为根底的,即优先运行优先级最高的进程。根据优先级的范围,可以把进程分为实时进程这里的实时是软实时和普通进程。实时进程优先级高于普通进程,并由特定的调度策略来保证它们的软实时性。内核中的默认配置是:进程优先级在0139,其中实时进程占用099,一般进程占用100139。在调度时,系统总是首先选取具有最高优先级的并且拥有活泼进程的进程组,然后进行相同优先级下的进程调度。 Linux中进程调度算法内核中实现了一个O(1)的调度算法,也就是说每一次调度所需要的时间与该CPU内的总进程数无关 。Linux中每个

7、运行队列都有两个优先级数组,一个活泼的和一个过期的。优先级数组在中被定义,它是prio_array类型的结构体。struct prio_array unsigned int nr_active; /* 当前活泼的进程总数 */ unsigned long bitmapBITMAP_SIZE;/* 活泼进程的位图 */ struct list_head queueMAX_PRIO; /* 各个优先级队列的头指针组成的数组*/;6.1.3 Linux中的线程线程机制是现代编程技术中常用的一种抽象,该机制提供了在同一程序内共享内存地址空间运行的一组线程。这些线程可以共享翻开的文件和其他资源等。Lin

8、ux中实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux把线程都当作进程来实现,仅仅将其视为使用某些共享资源的进程。每个线程都用有惟一隶属于自己的task_struct,所以在内核中,它看起来就像一个普通的进程 。6.2 Linux进程控制相关API进程的创 建fork1fork函数说明执行一次却返回两个值,父进程的返回值是子进程的进程号,而子进程那么返回0 。 2fork函数语法fork函数的语法格式如下所示。头文件#include / 提供类型pid_t的定义#include 函数原型pid_t fork(void);函数返回值0:子进程子进程ID大于0的整数:父进

9、程-1:出错6.2 Linux进程控制相关API进程的创 建3fork函数调用实例/*调用fork函数,其返回值赋给result*/int result = fork();/*通过result的值来判断fork函数的返回情况,首先进行出错处理*/if(result = -1) perror(fork); exit(-1);/*返回值为0代表子进程*/else if(result = 0) /*子进程相关语句*/*返回值大于0代表父进程*/else /*父进程相关语句*/6.2 Linux进程控制相关API进程的创 建exec函数族1exec函数族说明fork函数是用于创立一个子进程,该子进程几

10、乎拷贝了父进程的全部内容。 exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的路径和文件名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的内容替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行的脚本文件。6.2 Linux进程控制相关API进程的创 建exec函数族2exec函数族语法实际上,在Linux中并没有exec函数,而是以exec开头的函数族,它们之间语法有细微差异。exec函数族语法格式如下所示。 头文件#include 函数原型int execl(co

11、nst char *path, const char *arg, .);int execv(const char *path, char *const argv);int execle(const char *path, const char *arg, ., char *const envp);int execve(const char *path, char *const argv, char *const envp);int execlp(const char *file, const char *arg, .);int execvp(const char *file, char *co

12、nst argv);函数返回值出错1:-16.2 Linux进程控制相关API进程的创 建exec函数族2exec函数族语法6.2 Linux进程控制相关API进程的创 建exec函数族3exec函数组调用实例使用execlp函数,采用逐个列举方式,并且使用系统默认的环境变量。使用execl函数时需要给出完整的文件路径来查找对应的可执行文件。if(fork()=0)/*调用execlp函数,这里相当于调用了“ls -l命令*/ if(execlp(ls,ls,-l,NULL)0) perror(execlp error!);/*调用execl函数,注意这里要给出ls程序所在的完整路径*/if(

13、execl(/bin/ls,ls,-l,NULL)0) perror(execl error!);6.2 Linux进程控制相关API进程的创 建exec函数族3exec函数组调用实例使用execle时可以将环境变量添加到新建的子进程中去 使用execve函数时,通过构造指针数组的方式来传递参数,注意参数列表一定要以NULL作为结尾标识符。/*命令参数列表,必须以NULL结尾*/char *envp=PATH=/tmp,USER=sunq,NULL;/*调用execle函数,注意这里也要指出env的完整路径*/if(execle(/bin/env,env,NULL,envp)0) perror

14、(execle error!);/*命令参数列表,必须以NULL结尾*/char *arg=env,NULL;char *envp=PATH=/tmp,USER=sunq,NULL;if(execve(/bin/env,arg,envp)0) perror(execve error!);6.2 Linux进程控制相关API进程的创 建exit和_exit1exit和_exit函数说明exit和_exit函数都是用来终止进程的。当程序执行到exit或_exit时,进程会无条件地停止剩下的所有操作,去除包括PCB在内的各种数据结构,并终止本进程的运行。exit()函数与_exit()函数的区别就在

15、于exit()函数在调用exit系统调用之前要检查进程中文件的翻开情况,把文件缓冲区中的内容写回文件。如果用_exit()函数直接将进程关闭,缓冲区中的数据就会丧失。因此,假设想保证数据的完整性,就要使用exit()函数。6.2 Linux进程控制相关API进程的创 建exit和_exit2exit和_exit函数语法exit和_exit函数的语法如下所示。头文件exit:#include _exit:#include 函数原型void exit/_exit(int status); /*利用该参数传递进程结束时的状态。一般来说,0表示正常结束;其他的数值表示出现了错误,进程非正常结束*/3e

16、xit和_exit使用实例exit和_exit函数的调用很简单,输入状态参数即可,如下所示:exit(0);_exit(-1);6.2 Linux进程控制相关API进程的创 建wait和waitpid 1wait和waitpid函数说明wait函数可以使父进程也就是调用wait的进程阻塞,直到任意一个子进程结束或者父进程接到了一个指定的信号为止。如果该父进程没有子进程或者他的子进程已经结束,那么wait函数会立即返回。waitpid的作用和wait一样,但它并不一定要等待第一个终止的子进程。它还有假设干选项,如可提供一个非阻塞版本的wait功能,也能支持作业控制。实际上wait函数只是wait

17、pid函数的一个特例,在Linux内部实现wait函数时直接调用的就是waitpid函数。6.2 Linux进程控制相关API进程的创 建wait和waitpid 2wait和waitpid函数格式说明wait函数的语法标准如下所示。 头文件#include #include 函数原型pid_t wait(int *status) /*等待子进程结束,同时接受子进程退出时的状态*/pid_t waitpid(pid_t pid, /*等待结束的进程类型*/ int *status, /*同wait*/ int options) /*选项*/这里的status假设为空,那么代表任意状态结束的子进

18、程;status假设不为空,那么代表指定状态结束的子进程。6.2 Linux进程控制相关API进程的创 建wait和waitpid 2wait和waitpid函数格式说明 函数返回值成功:子进程的进程号或0调用成功子但进程还未退出失败:-16.2 Linux进程控制相关API进程的创 建wait和waitpid 3waitpid使用实例wait函数的使用非常简单,只需要在父进程处调用即可。调用wait后父进程就会阻塞自己,直到有相应的子进程退出为止。waitpid函数支持不同的参数,可以通过指定WNOHANG使父进程以非阻塞的方式检查子进程是否结束,其调用过程如下所示:/*调用waitpid,

19、且父进程不阻塞*/pr=waitpid(pid,NULL,WNOHANG);6.2 Linux进程控制相关API进程的创 建防止僵死进程实例当一个进程已经终止,但是其父进程尚未对其进行回收获得终止子进程的有关信息,释放它占用的资源的进程被称为僵死进程。为了防止僵死进程的出现,一种方法是父进程调用wait/waitpid等待子进程结束,但这样做有一个弊端就是在子进程结束前父进程会一直阻塞,不能做任何事情。另外一种更好的方法就是调用两次fork函数。 int main() pid_t pid; if(pid = fork() 0) / 创立子进程1 perror(fork); else if(pi

20、d = 0) / 子进程1 if(pid = fork() 0) exit(0); / 子进程1结束 else / 子进程2 sleep(2); /*打印子进程2的父进程号*/ printf(second child, parent pid = %dn, getppid(); exit(0); else / 父进程 / do something else 6.3 ARM Linux进程间通信Linux下的进程通信方式根本上是从Unix平台上继承而来的。而对Unix开展做出重大奉献的两大主力AT&T的贝尔实验室及BSD在进程间的通信方面的侧重点有所不同。前者是对Unix早期的进程间通信手段进行了

21、系统的改进和扩充,形成了“System V IPC,其通信进程主要局限在单个计算机内;后者那么跳过了该限制,形成了基于套接口socket的进程间通信机制。6.3 ARM Linux进程间通信 Unix 进程间通信IPC方式包括管道、FIFO、信号。 System V进程间通信IPC包括System V消息队列、System V信号灯、System V共享内存区。 Posix 进程间通信IPC包括Posix消息队列、Posix信号灯、Posix共享内存区。 现在在Linux中使用较多的进程间通信方式主要有以下几种。1管道Pipe及有名管道named pipe2信号Signal3消息队列4共享内存

22、5信号量6套接字Socket6.3.1 管道通信管道概述 管道是Linux中进程间通信的一种方式,它把一个程序的输出直接连接到另一个程序的输入。Linux的管道主要包括两种:无名管道有名管道。无名管道 无名管道是Linux中管道通信的一种原始方法,如下图,它具有如下特点。它只能用于具有亲缘关系的进程之间的通信也就是父子进程或者兄弟进程之间。它是一个半双工的通信模式,具有固定的读端和写端。管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。有名管道FIFO 有名管道是对无名管道的一种改进,如下

23、图,它具有如下特点:它可以使互不相关的两个进程实现彼此通信。该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便。FIFO严格地遵循先进先出规那么,对管道及FIFO的读总是从开始处返回数据,对它们的写那么把数据添加到末尾,它们不支持如lseek()等文件定位操作。6.3.1 管道通信有名管道的创立1函数说明有名管道的创立可以使用函数mkfifo,该函数类似文件中的open操作,可以指定管道的路径和读写权限。普通文件在读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能。这里的非阻塞标志可以在open函数中设定为O

24、_NONBLOCK。 对于读进程 假设该管道是以阻塞方式翻开,假设当前FIFO内没有数据,那么读进程将一直阻塞,直到有数据写入。 假设该管道是以非阻塞方式翻开,那么不管FIFO内是否有数据,读进程都会立即执行读操作。 对于写进程 假设该管道是以阻塞方式翻开,那么写进程将一直阻塞直到有读进程读出数据。 假设该管道是以非阻塞方式翻开,那么无论当前FIFO内有没有读操作,写进程都会立即执行写操作 6.3.1 管道通信有名管道的创立2函数格式定义mkfifo函数格式如下所示: 头文件#include #include 函数原型int mkfifo(const char *filename, /* 要创

25、立的管道*/ mode_t mode) /*管道文件的读写权限*/ 函数返回值成功:0出错:-16.3.1 管道通信有名管道的创立3函数调用实例假设要使用有名管道的方式来进行进程间通信,那么必须首先调用mkfifo函数创立管道,创立后用户可以调用函数open、read、write来实现对管道的读写,如下所示:/*创立有名管道,并设置相应的权限*/mkfifo(FIFO,0666);/*翻开有名管道,并设置非阻塞标志*/fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);6.3.2 信号通信信号概述信号是进程间通信机制中惟一的异步通信方式,可以看作是异步通知,通知接收信号的进

26、程有哪些事情发生了。信号事件的发生有两个来源:硬件来源比方我们按下了键盘或者其他硬件故障;软件来源,最常用发送信号的系统函数是kill、raise、alarm、setitimer和sigqueue函数,软件来源还包括一些非法运算等操作。进程可以通过3种方式来响应一个信号。1忽略信号2捕捉信号3执行缺省操作6.3.2 信号通信信号概述一个完整的信号生命周期可以分为3个阶段。这3个阶段由4个重要事件来刻画的:信号产生、信号在进程中注册、信号在进程中注销、执行信号处理函数。 6.3.2 信号通信信号概述信号的处理包括信号的发送、捕获以及信号的处理,它们各自相对应的常见函数有发送信号的函数:kill(

27、)、raise()。捕获信号的函数:alarm()、pause()。设置信号处理的函数:signal()。6.3.2 信号通信kill()和raise() 1函数说明kill函数可以发送信号给进程或进程组。它不仅可以中止进程,也可以向进程发送其他信号。与kill函数所不同的是,raise函数只允许进程向自身发送信号。2函数格式kill和raise函数的语法要点如下所示。 头文件#include #include 函数原型int kill(pid_t pid, /*指明要接收信号的进程号*/ int sig) /*信号,表中的数值*/int raise(int sig) /*信号,表中的数值*/

28、6.3.2 信号通信kill()和raise() 函数返回值成功:0出错:-13函数调用实例这两个函数的调用都比较简单,如下所示:raise(SIGSTOP);kill(pid,SIGKILL);6.3.2 信号通信alarm()和pause() 1函数说明alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送SIGALARM信号。 pause函数是用于将调用进程挂起直至捕捉到信号为止。 2函数格式alarm和pause函数的语法要点如下所示。 头文件#include 函数原型unsigned int alarm(unsigned int seconds

29、) /*指定秒数*/int pause(void)6.3.2 信号通信alarm()和pause() 函数返回值成功:如果调用此alarm前,进程中已经设置了闹钟时间,那么返回上一个闹钟时间的剩余时间,否那么返回0。出错:-1,并且把error值设为EINTR3函数调用实例这两个函数的调用很简单,如下所示:ret=alarm(5);pause();由于SIGALARM默认的系统动作为终止该进程,因此在收到该信号之后程序就终止了。6.3.2 信号通信signal() 1函数说明一个进程可以决定在该进程中需要对哪些信号进行什么样的处理。使用signal函数时,只需把要处理的信号和处理函数列出即可。

30、它主要是用于前32种非实时信号的处理,不支持信号传递信息。 2函数格式signal函数的语法要点如下所示。 头文件#include 函数原型void (*signal(int signum, /*指定信号*/ void (*handler)(int)(int) /*对信号的处理*/6.3.2 信号通信signal() 函数返回值成功:以前的信号处理配置 出错:-13使用实例使用signal函数时通常用于自定义信号处理函数handler的第3种情况。首先自定义了信号处理函数,接着再使用signal函数处理相应的信号。/*这里的my_func是自定义信号处理函数*/signal(SIGINT, m

31、y_func);signal(SIGQUIT, my_func);6.3.2 信号通信具有超时限制的read调用 通常的read函数并没有超时限制的功能。如果读取的设备是一个低速设备,可能需要等待一段时间才会读取成功。这里通过使用alarm定时函数来给read函数设置超时时限10s。假设定时器到时,就会向进程发送SIGALRM信号,从而调用函数sig_alrm。int main(void) int n; char lineMAXLINE; /*设定超时时限*/ alarm(10); /*信号注册函数*/ if(signal(SIGALRM, sig_alrm) = SIG_ERR) perro

32、r(signal); exit(-1); 6.3.2 信号通信if(n = read(STDIN_FILENO, line, MAXLINE) 0) perror(read); write(STDOUT_FILENO, line, n); exit(0);static void sig_alrm(int signo) printf(in here alarmn); exit(0); 6.3.3 共享内存共享内存概述共享内存允许两个或更多进程共享一给定的内存区域。因为数据不需要在各个进程之间复制,所以这是最高效的一种进程间通信方式。使用共享内存的关键在于如何在多个进程之间对一给定的存储区进行同步

33、访问。 6.3.3 共享内存函数说明 共享内存的实现分为3个步骤。第一步是创立共享内存,这里用到的函数是shmget,也就是从内存中获得一段共享内存区域。第二步映射共享内存,也就是把这段创立的共享内存映射到具体的进程空间去,这里使用的函数是shmat。接着就可以使用这段共享内存了,即可以使用不带缓冲的I/O读写命令对其进行操作。第三步是撤销映射的操作,其函数为shmdt。6.3.3 共享内存函数格式 函数的头文件如下所示。#include #include #include shmget函数的语法要点如下所示。 函数原型int shmget(key_t key, /*IPC_PRIVATE *

34、/ int size, /*共享内存区大小*/ int shmflg) /*同open函数的权限位,也可以用8进制表示法*/ 函数返回值成功:共享内存段标识符出错:-16.3.3 共享内存函数格式 shmat函数的语法要点如下所示。 函数原型char *shmat(int shmid, /*要映射的共享内存区标识符*/ const void *shmaddr, /*将共享内存映射到指定位置假设为0那么表示把该段共享内存映射到调用进程的地址空间*/ int shmflg) /*SHM_RDONLY:共享内存只读。默认0,共享内存可读写*/ 函数返回值成功:被映射的段地址出错:-1shmdt函数的

35、语法如下所示。 函数原型int shmdt(const void *shmaddr) /*被映射的共享内存段地址*/ 函数返回值成功:0出错:-16.3.3 共享内存使用实例 这里要介绍的一个命令是ipcs,这是用于显示进程间通信状态的命令。它可以查看共享内存、消息队列等各种进程间通信机制对象的情况,这里使用了system函数来调用命令ipcs,函数,如下所示:/*创立共享内存*/shmget(IPC_PRIVATE,BUFSZ,0666);/*映射共享内存*/shmat(shmid,0,0);/*删除共享内存*/shmdt(shmadd);6.3.4 消息队列消息队列概述 消息队列就是一个消

36、息的链表。用户可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规那么添加新消息;对消息队列有读权限的进程那么可以从消息队列中读走消息,消息队列是随内核持续的。6.3.4 消息队列消息队列实现说明 消息队列的实现包括创立或翻开消息队列、添加消息、读取消息和控制消息队列这4种操作。 创立或翻开消息队列使用函数msgget,这里创立的消息队列的数量会受到系统消息队列数量的限制。 添加到消息队列使用函数msgsnd,它把消息添加到已翻开的消息队列末尾。 读取消息队列内容使用函数msgrcv,它把消息从消息队列中取走,与FIFO不同的是,这里可以指定取走

37、某一类型的消息。 控制消息队列使用函数msgctl,它可以完成多项功能。6.3.4 消息队列函数格式 用到的头文件如下所示:#include #include #include msgget函数的语法要点如下所示 函数原型int msgget(key_t key, /*返回新的或已有队列的队列ID、IPC_PRIVATE */ int flag) 函数返回值成功:消息队列ID出错:-1msgsnd函数的语法要点如下所示。 函数原型int msgsnd(int msqid, /*消息队列的队列ID */ const void *prt, /*指向消息结构的指针*/ size_t size, /*

38、消息的字节数*/ int flag)/*有两种取值情况:IPC_NOWAIT假设消息并没有立即发送而调用进程会立即返回。0:msgsnd调用阻塞直到条件满足为止*/6.3.4 消息队列这里prt消息的结构如下所示:struct msgbuflong mtype;/消息类型char mtextLEN;/消息正文, LEN由用户指定 函数返回值成功:0出错:-1msgrcv函数的语法要点如下所示。 函数原型int msgrcv(int msgid, /*消息队列的队列ID */ struct msgbuf *msgp,/* 消息缓冲区*/ int size, /*消息的字节数*/ long msg

39、type, /*接收的消息类型*/ int flag) /*类型符*/ 函数返回值成功:0出错:-1flag含 义MSG_NOERROR若返回的消息比size字节多,则消息就会截短到size字节,且不通知消息发送进程IPC_NOWAIT若消息并没有立即发送而调用进程会立即返回0msgsnd调用阻塞直到条件满足为止6.3.4 消息队列msgctl函数的语法要点如下所示。 函数原型int msgctl(int msgqid, /*消息队列的队列ID*/ int cmd, /*消息队列控制选项*/ struct msqid_ds *buf ) /*消息队列缓冲区*/ 函数返回值成功:0出错:-1cm

40、d含 义IPC_STAT读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中IPC_SET设置消息队列的数据结构msqid_ds中的ipc_perm元素的值,这个值取自buf参数IPC_RMID从系统内核中移走消息队列6.3.4 消息队列使用实例在使用消息队列前,可以先使用函数ftok,根据不同的路径和关键表示产生标准的key。之后使用msgget等函数对消息队列进行操作,如下所示:/*自定义消息格式*/struct message msg;/*创立消息队列*/msgget(key,IPC_CREAT|0666);/*添加消息到消息队列*/msgsnd(qid,&msg,le

41、n,0);/*读取消息队列*/msgrcv(qid,&msg,BUFSZ,0,0);/*从系统内核中移走消息队列。*/msgctl(qid,IPC_RMID,NULL); 6.4 ARM Linux线程相关API 线程创立和退出 1函数说明使用线程主要包括以下几个步骤。 创立线程 调用相关线程函数 线程退出 线程资源回收6.4 ARM Linux线程相关API 线程创立和退出 2函数格式#include pthread_create函数的语法要点如下所示。 函数原型int pthread_create( pthread_t *thread, /*线程标识符*/ pthread_attr_t *

42、attr, /*线程属性设置,默认为NULL,可以使用其他函数来设置*/ void *(*start_routine)(void *),/*线程函数的起始地址*/ void *arg) /*传递给start_routine的参数*/ 函数返回值成功:0出错:-16.4 ARM Linux线程相关API线程创立和退出 2函数格式pthread_exit函数的语法要点如下所示。 函数原型void pthread_exit(void *retval) /*线程的返回值,可由其他函数如pthread_join来检索获取*/pthread_join函数的语法要点如下所示。 函数原型int pthread

43、_join(pthread_t thread, /*等待线程的标识符*/ void *thread_return) /*用户定义的指针,用来存储被等待线程的返回值不为NULL时*/ 函数返回值成功:0出错:-16.4 ARM Linux线程相关API线程创立和退出 3函数调用实例在使用pthread_create函数时,通常可以将所要传递给线程函数的参数写成一个结构体,传入到该函数中。pthread_join函数那么使用pthread_create函数的id等待线程退出,该函数调用源码如下所示:void thread(void)/*具体线程函数*/*主函数中创立线程*/ret=pthread_

44、create(&id,NULL,(void *) thread,NULL);/*等待线程结束*/pthread_join(id,NULL); / 不处理子线程退出时返回的值6.4 ARM Linux线程相关API mutex线程访问控制 1mutex互斥锁函数说明mutex是一种简单的加锁的方法来控制对共享资源的存取。这个互斥锁只有两种状态,也就是上锁和解锁,可以把互斥锁看作某种意义上的全局变量。互斥锁可以分为快速互斥锁、递归互斥锁和检错互斥锁,这3种锁的区别主要在于其他未占有互斥锁的线程在希望得到互斥锁时的是否需要阻塞等待。快速锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。递归互斥锁能够

45、成功地返回并且增加调用线程在互斥上加锁的次数。检错互斥锁那么为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。6.4 ARM Linux线程相关APImutex线程访问控制 1mutex互斥锁函数说明互斥锁的操作主要包括以下几个步骤。 互斥锁初始化:pthread_mutex_init。 互斥锁上锁:pthread_mutex_lock。 互斥锁判断上锁:pthread_mutex_trylock。 互斥锁接锁:pthread_mutex_unlock。 消除互斥锁:pthread_mutex_destroy。6.4 ARM Linux线程相关APImutex线程访问控制 2函数格式这

46、几个函数都需要包含同样的头文件,如下所示:#include pthread_mutex_init函数的语法要点如下所示。 函数原型int pthread_mutex_init(pthread_mutex_t *mutex,/*互斥锁*/ const pthread_mutexattr_t*mutexattr) /*创立互斥锁的方法*/int pthread_mutex_lock(pthread_mutex_t *mutex)/*互斥锁*/int pthread_mutex_trylock(pthread_mutex_t *mutex)/*互斥锁*/int pthread_mutex_unloc

47、k(pthread_mutex_t *mutex)/*互斥锁*/int pthread_mutex_destroy(pthread_mutex_t *mutex)/*互斥锁*/6.4 ARM Linux线程相关APImutex线程访问控制 2函数格式 函数返回值成功:0出错:-13使用实例在使用互斥锁时,通常首先pthread_mutex_lock上锁,然后执行需要原子操作的代码,最后再使用pthread_mutex_unlock解锁。/*互斥锁上锁*/pthread_mutex_lock(&mutex);/*需原子操作的代码*/*互斥锁接锁*/pthread_mutex_unlock(&mu

48、tex);6.4 ARM Linux线程相关API 信号量线程控制 1信号量说明信号量也就是操作系统中所用到的PV原语,它广泛用于进程或线程间的同步与互斥。 PV原语是对整数计数器信号量sem的操作。一次P操作使sem减一,而一次V操作使sem加一。进程或线程根据信号量的值来判断是否对公共资源具有访问权限。当信号量sem的值大于等于0时,该进程或线程具有公共资源的访问权限;相反,当信号量sem的值小于0时,该进程或线程就将阻塞直到信号量sem的值大于等于0为止。6.4 ARM Linux线程相关API信号量线程控制 1信号量说明PV原语主要用于进程或线程间的同步和互斥这两种典型情况。假设用于互

49、斥,几个进程或线程往往只设置一个信号量sem,它们的操作流程如下图。 6.4 ARM Linux线程相关API信号量线程控制 1信号量说明当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现它们之间的顺序执行,它们的操作流程如下图。6.4 ARM Linux线程相关API信号量线程控制2函数说明Linux实现了POSIX的无名信号量,主要用于线程间的互斥同步。这里主要介绍几个常见函数。 sem_init用于创立一个信号量,并能初始化它的值。 sem_wait和sem_trywait相当于P操作,它们都能将信号量的值减一,两者的区别在于假设信号量小于0时,sem_wait将会阻塞进程,而sem_trywait那么会立即返回。

温馨提示

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

最新文档

评论

0/150

提交评论