第6章 嵌入式Linux进程和线程编程 LinuxC语言开发

上传人:紫** 文档编号:153955690 上传时间:2022-09-19 格式:PPT 页数:81 大小:614KB
收藏 版权申诉 举报 下载
第6章 嵌入式Linux进程和线程编程 LinuxC语言开发_第1页
第1页 / 共81页
第6章 嵌入式Linux进程和线程编程 LinuxC语言开发_第2页
第2页 / 共81页
第6章 嵌入式Linux进程和线程编程 LinuxC语言开发_第3页
第3页 / 共81页
资源描述:

《第6章 嵌入式Linux进程和线程编程 LinuxC语言开发》由会员分享,可在线阅读,更多相关《第6章 嵌入式Linux进程和线程编程 LinuxC语言开发(81页珍藏版)》请在装配图网上搜索。

1、嵌入式Linux进程和线程编程www.embedu.org课程目标进程相关的基本概念Linux进程的创建Linux进程的管理和守护进程Linux进程间通信的方式Linux中线程创建和退出Linux中线程间的同步和互斥 www.embedu.org本章内容6.1 Linux进程概述 6.2 Linux进程控制相关API 6.3 ARM Linux进程间通信 6.4 ARM Linux线程相关API 6.5 Linux守护进程 本章小结www.embedu.org6.1 Linux进程概述6.1.1 进程描述符及任务结构 6.1.2 进程的调度 6.1.3 Linux中的线程www.embedu.

2、org6.1.1 进程描述符及任务结构进程概念 进程的概念首先在20世纪60年代初期由MIT的Multics系统和IBM的TSS/360系统中引入的。(1)进程是一个独立的可调度的活动(E.Cohen,D.Jofferson)。(2)进程是一个抽象实体,当它执行某个任务时,将要分配和释放各种资源(P.Denning)。(3)进程是可以并行执行的计算部分(S.E.Madnick,J.T.Donovan)。进程和程序有本质的区别:程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念;而进程是一个动态的概念,它是程序执行的过程,包括了动态创建、调度和消亡的整个过程,它是程序执行和资

3、源管理的最小单位。www.embedu.org6.1.1 进程描述符及任务结构Linux中进程描述符进程不但包括程序的指令和数据,而且包括程序计数器和CPU的所有寄存器以及存储临时数据的进程堆栈。内核把进程存放在任务队列(task list)的双向循环链表中,其中链表的每一项都是类型为task_struct,称为进程描述符的结构,该结构定义在中。www.embedu.org6.1.1 进程描述符及任务结构Linux中进程描述符Linux通过slab分配器分配task_struct结构,它实际上是一个栈,其栈顶(向上增长的栈)或栈底(向下增长的栈)中有一个thread info结构,其中的tas

4、k指针指向task_struct。下面详细讲解task_struct结构中最为重要的两个域:state和pid。www.embedu.org6.1.1 进程描述符及任务结构Linux中进程描述符(1)进程状态运行(TASK_RUNNING)可中断(TASK_INTERUPTIBLE)不可中断(TASK_UNINTERUPTIBLE)僵死(TASK_ZOMBIE)停止(TASK_STOPPED)现在的任务调用 fork()函数并且创建 一个新进程 TASK _ RUNNING(准备就绪但还未 运行)TASK _ RUNNING(正在运行)TASK _ INTERRUPTIBLE 或 TASK _

5、 UNINTERRUPTIBLE TASK _ ZOMBIE(任务被终止)调度程序将 任务投入运行 任务被更高 的优先级抢占 为等待特定事件 任务睡眠 等待的特定时间 发生后任务被唤醒 www.embedu.org6.1.1 进程描述符及任务结构Linux中进程描述符(2)任务标识Linux内核通过惟一的进程标识值PID来标识每个进程。PID是一个非负数,它实际上是一个短整型数据,也就是说它最大值为32767。读者可以查看/proc/sys/kernel/pid_max来确定该系统的进程数上限。一般来说,32767对于很多桌面系统已经足够,但是对于大型服务器,就必须修改这个上限。www.emb

6、edu.org6.1.1 进程描述符及任务结构进程的创建、执行和终止(1)进程的创建和执行许多操作系统都提供的是产生进程的机制,也就是首先在新的地址空间里创建进程、读入可执行文件,最后再开始执行。首先,fork()通过拷贝当前进程的内容创建一个子进程,子进程与父进程的区别仅仅在于不同的PID、PPID和其他一些资源。exec函数负责读取可执行文件并将其载入地址空间开始运行。www.embedu.org6.1.1 进程描述符及任务结构进程的创建、执行和终止(2)进程的终止进程终结也需要做很多繁琐的收尾工作,系统必须保证进程所占用的资源回收,并通知父进程。Linux首先把终止的进程设置为僵死状态,

7、这个时候,进程无法投入运行了。它的存在只为父进程提供信息,申请死亡。父进程得到信息后,开始调用wait(),子进程占用的资源被全部释放。www.embedu.org6.1.2 进程的调度Linux中进程调度概述 Linux中进程调度算法 www.embedu.orgLinux中进程调度概述进程调度是指确定CPU当前执行哪个进程。Linux进程调度策略是以优先级调度为基础的,即优先运行优先级最高的进程。根据优先级的范围,可以把进程分为实时进程(这里的实时是软实时)和普通进程。实时进程优先级高于普通进程,并由特定的调度策略来保证它们的(软)实时性。内核中的默认配置是:进程优先级在0139,其中实时

8、进程占用099,一般进程占用100139。在调度时,系统总是首先选取具有最高优先级的并且拥有活跃进程的进程组,然后进行相同优先级下的进程调度。www.embedu.orgLinux中进程调度算法Linux 2.6内核中实现了一个O(1)的调度算法,也就是说每一次调度所需要的时间与该CPU内的总进程数无关。Linux中每个运行队列都有两个优先级数组,一个活跃的和一个过期的。优先级数组在kernel/sched.c中被定义,它是prio_array类型的结构体。struct prio_array unsigned int nr_active;/*当前活跃的进程总数*/unsigned long b

9、itmapBITMAP_SIZE;/*活跃进程的位图*/struct list_head queueMAX_PRIO;/*各个优先级队列的头指针组成的数组*/;www.embedu.org6.1.3 Linux中的线程线程机制是现代编程技术中常用的一种抽象,该机制提供了在同一程序内共享内存地址空间运行的一组线程。这些线程可以共享打开的文件和其他资源等。Linux中实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux把线程都当作进程来实现,仅仅将其视为使用某些共享资源的进程。每个线程都用有惟一隶属于自己的task_struct,所以在内核中,它看起来就像一个普通的进程。ww

10、w.embedu.org6.2 Linux进程控制相关API进程的创 建fork(1)fork函数说明执行一次却返回两个值,父进程的返回值是子进程的进程号,而子进程则返回0。(2)fork函数语法fork函数的语法格式如下所示。头文件#include /提供类型pid_t的定义#include 函数原型pid_t fork(void);函数返回值0:子进程子进程ID(大于0的整数):父进程-1:出错www.embedu.org6.2 Linux进程控制相关API进程的创 建(3)fork函数调用实例/*调用fork函数,其返回值赋给result*/int result=fork();/*通过r

11、esult的值来判断fork函数的返回情况,首先进行出错处理*/if(result=-1)perror(fork);exit(-1);/*返回值为0代表子进程*/else if(result=0)/*子进程相关语句*/*返回值大于0代表父进程*/else /*父进程相关语句*/www.embedu.org6.2 Linux进程控制相关API进程的创 建exec函数族(1)exec函数族说明fork函数是用于创建一个子进程,该子进程几乎拷贝了父进程的全部内容。exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的路径和文件名找到可执行文件,并用它来取代原调用进程的数据段、代

12、码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的内容替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行的脚本文件。www.embedu.org6.2 Linux进程控制相关API进程的创 建exec函数族(2)exec函数族语法实际上,在Linux中并没有exec()函数,而是以exec开头的函数族,它们之间语法有细微差别。exec函数族语法格式如下所示。头文件#include 函数原型int execl(const char*path,const char*arg,.);int execv(const char*path,char*con

13、st 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*const argv);函数返回值出错1:-1www.embedu.org6.2 Linux进程控制相关API进程的创 建exec函数族(2)exec函数族语法www.embedu.or

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

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

16、,NULL;char*envp=PATH=/tmp,USER=sunq,NULL;if(execve(/bin/env,arg,envp)0)perror(execve error!);www.embedu.org6.2 Linux进程控制相关API进程的创 建exit和_exit(1)exit和_exit函数说明exit和_exit函数都是用来终止进程的。当程序执行到exit或_exit时,进程会无条件地停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。exit()函数与_exit()函数的区别就在于exit()函数在调用exit系统调用之前要检查进程中文件的打开情况

17、,把文件缓冲区中的内容写回文件。如果用_exit()函数直接将进程关闭,缓冲区中的数据就会丢失。因此,若想保证数据的完整性,就要使用exit()函数。www.embedu.org6.2 Linux进程控制相关API进程的创 建exit和_exit(2)exit和_exit函数语法exit和_exit函数的语法如下所示。头文件exit:#include _exit:#include 函数原型void exit/_exit(int status);/*利用该参数传递进程结束时的状态。一般来说,0表示正常结束;其他的数值表示出现了错误,进程非正常结束*/(3)exit和_exit使用实例exit和_

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

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

20、;status若不为空,则代表指定状态结束的子进程。www.embedu.org6.2 Linux进程控制相关API进程的创 建wait和waitpid(2)wait和waitpid函数格式说明 函数返回值成功:子进程的进程号或0(调用成功子但进程还未退出)失败:-1www.embedu.org6.2 Linux进程控制相关API进程的创 建wait和waitpid(3)waitpid使用实例wait函数的使用非常简单,只需要在父进程处调用即可。调用wait后父进程就会阻塞自己,直到有相应的子进程退出为止。waitpid函数支持不同的参数,可以通过指定WNOHANG使父进程以非阻塞的方式检查子

21、进程是否结束,其调用过程如下所示:/*调用waitpid,且父进程不阻塞*/pr=waitpid(pid,NULL,WNOHANG);www.embedu.org6.2 Linux进程控制相关API进程的创 建避免僵死进程实例当一个进程已经终止,但是其父进程尚未对其进行回收(获得终止子进程的有关信息,释放它占用的资源)的进程被称为僵死进程。为了避免僵死进程的出现,一种办法是父进程调用wait/waitpid等待子进程结束,但这样做有一个弊端就是在子进程结束前父进程会一直阻塞,不能做任何事情。另外一种更好的方法就是调用两次fork函数。www.embedu.orgint main()pid_t

22、pid;if(pid=fork()0)/创建子进程1 perror(fork);else if(pid=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 www.embedu.org6.3 ARM Linux进程间通信Linux下的进程通信方式基本上是从Unix平台上继承而来的。而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室

23、及BSD在进程间的通信方面的侧重点有所不同。前者是对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“System V IPC”,其通信进程主要局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。最初Unix的进程间通信 基于 Socket 进程间通信 基于 System V 进程间通信 POSIX 进程间通信 Linux 进程间通信 www.embedu.org6.3 ARM Linux进程间通信 Unix 进程间通信(IPC)方式包括管道、FIFO、信号。System V进程间通信(IPC)包括System V消息队列、System V信号灯

24、、System V共享内存区。Posix 进程间通信(IPC)包括Posix消息队列、Posix信号灯、Posix共享内存区。现在在Linux中使用较多的进程间通信方式主要有以下几种。(1)管道(Pipe)及有名管道(named pipe)(2)信号(Signal)(3)消息队列(4)共享内存(5)信号量(6)套接字(Socket)www.embedu.org6.3.1 管道通信管道概述 管道是Linux中进程间通信的一种方式,它把一个程序的输出直接连接到另一个程序的输入。Linux的管道主要包括两种:无名管道有名管道。www.embedu.org无名管道 无名管道是Linux中管道通信的一种

25、原始方法,如图6.4所示,它具有如下特点。它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。它是一个半双工的通信模式,具有固定的读端和写端。管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。fd0 子进程 父进程 fd1 内核 无名管道 www.embedu.org有名管道(FIFO)有名管道是对无名管道的一种改进,如图6.5所示,它具有如下特点:它可以使互不相关的两个进程实现彼此通信。该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程

26、就可以把它当作普通文件一样进行读写操作,使用非常方便。FIFO严格地遵循先进先出规则,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持如lseek()等文件定位操作。fd0 进程 2 进程 1 fd1 内核 有名管道 www.embedu.org6.3.1 管道通信有名管道的创建(1)函数说明有名管道的创建可以使用函数mkfifo,该函数类似文件中的open操作,可以指定管道的路径和读写权限。普通文件在读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能。这里的非阻塞标志可以在open函数中设定为O_NONBLOCK。对于读进程 若该管道是以阻塞方式打开,若

27、当前FIFO内没有数据,则读进程将一直阻塞,直到有数据写入。若该管道是以非阻塞方式打开,则不论FIFO内是否有数据,读进程都会立即执行读操作。对于写进程 若该管道是以阻塞方式打开,则写进程将一直阻塞直到有读进程读出数据。若该管道是以非阻塞方式打开,则无论当前FIFO内有没有读操作,写进程都会立即执行写操作 www.embedu.org6.3.1 管道通信有名管道的创建(2)函数格式定义mkfifo函数格式如下所示:头文件#include#include 函数原型int mkfifo(const char*filename,/*要创建的管道*/mode_t mode)/*管道文件的读写权限*/函

28、数返回值成功:0出错:-1www.embedu.org6.3.1 管道通信有名管道的创建(3)函数调用实例若要使用有名管道的方式来进行进程间通信,则必须首先调用mkfifo函数创建管道,创建后用户可以调用函数open、read、write来实现对管道的读写,如下所示:/*创建有名管道,并设置相应的权限*/mkfifo(FIFO,0666);/*打开有名管道,并设置非阻塞标志*/fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);www.embedu.org6.3.2 信号通信信号概述信号是进程间通信机制中惟一的异步通信方式,可以看作是异步通知,通知接收信号的进程有哪些事情发

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

30、 信号通信信号概述信号的处理包括信号的发送、捕获以及信号的处理,它们各自相对应的常见函数有发送信号的函数:kill()、raise()。捕获信号的函数:alarm()、pause()。设置信号处理的函数:signal()。www.embedu.org6.3.2 信号通信kill()和raise()(1)函数说明kill函数可以发送信号给进程或进程组。它不仅可以中止进程,也可以向进程发送其他信号。与kill函数所不同的是,raise函数只允许进程向自身发送信号。(2)函数格式kill和raise函数的语法要点如下所示。头文件#include#include 函数原型int kill(pid_t

31、pid,/*指明要接收信号的进程号*/int sig)/*信号,表7.4中的数值*/int raise(int sig)/*信号,表7.4中的数值*/www.embedu.org6.3.2 信号通信kill()和raise()函数返回值成功:0出错:-1(3)函数调用实例这两个函数的调用都比较简单,如下所示:raise(SIGSTOP);kill(pid,SIGKILL);www.embedu.org6.3.2 信号通信alarm()和pause()(1)函数说明alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送SIGALARM信号。pause函数是

32、用于将调用进程挂起直至捕捉到信号为止。(2)函数格式alarm和pause函数的语法要点如下所示。头文件#include 函数原型unsigned int alarm(unsigned int seconds)/*指定秒数*/int pause(void)www.embedu.org6.3.2 信号通信alarm()和pause()函数返回值成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。出错:-1,并且把error值设为EINTR(3)函数调用实例这两个函数的调用很简单,如下所示:ret=alarm(5);pause();由于SIGAL

33、ARM默认的系统动作为终止该进程,因此在收到该信号之后程序就终止了。www.embedu.org6.3.2 信号通信signal()(1)函数说明一个进程可以决定在该进程中需要对哪些信号进行什么样的处理。使用signal函数时,只需把要处理的信号和处理函数列出即可。它主要是用于前32种非实时信号的处理,不支持信号传递信息。(2)函数格式signal函数的语法要点如下所示。头文件#include 函数原型void(*signal(int signum,/*指定信号*/void(*handler)(int)(int)/*对信号的处理*/www.embedu.org6.3.2 信号通信signal(

34、)函数返回值成功:以前的信号处理配置 出错:-1(3)使用实例使用signal函数时通常用于自定义信号处理函数(handler的第3种情况)。首先自定义了信号处理函数,接着再使用signal函数处理相应的信号。/*这里的my_func是自定义信号处理函数*/signal(SIGINT,my_func);signal(SIGQUIT,my_func);www.embedu.org6.3.2 信号通信具有超时限制的read调用 通常的read函数并没有超时限制的功能。如果读取的设备是一个低速设备,可能需要等待一段时间才会读取成功。这里通过使用alarm定时函数来给read函数设置超时时限(10s)

35、。若定时器到时,就会向进程发送SIGALRM信号,从而调用函数sig_alrm。int main(void)int n;char lineMAXLINE;/*设定超时时限*/alarm(10);/*信号注册函数*/if(signal(SIGALRM,sig_alrm)=SIG_ERR)perror(signal);exit(-1);www.embedu.org6.3.2 信号通信if(n=read(STDIN_FILENO,line,MAXLINE)0)perror(read);write(STDOUT_FILENO,line,n);exit(0);static void sig_alrm(i

36、nt signo)printf(in here alarmn);exit(0);www.embedu.org6.3.3 共享内存共享内存概述共享内存允许两个或更多进程共享一给定的内存区域。因为数据不需要在各个进程之间复制,所以这是最高效的一种进程间通信方式。使用共享内存的关键在于如何在多个进程之间对一给定的存储区进行同步访问。www.embedu.org6.3.3 共享内存函数说明 共享内存的实现分为3个步骤。第一步是创建共享内存,这里用到的函数是shmget,也就是从内存中获得一段共享内存区域。第二步映射共享内存,也就是把这段创建的共享内存映射到具体的进程空间去,这里使用的函数是shmat。

37、接着就可以使用这段共享内存了,即可以使用不带缓冲的I/O读写命令对其进行操作。第三步是撤销映射的操作,其函数为shmdt。www.embedu.org6.3.3 共享内存函数格式 函数的头文件如下所示。#include#include#include shmget函数的语法要点如下所示。函数原型int shmget(key_t key,/*IPC_PRIVATE*/int size,/*共享内存区大小*/int shmflg)/*同open函数的权限位,也可以用8进制表示法*/函数返回值成功:共享内存段标识符出错:-1www.embedu.org6.3.3 共享内存函数格式 shmat函数的语

38、法要点如下所示。函数原型char*shmat(int shmid,/*要映射的共享内存区标识符*/const void*shmaddr,/*将共享内存映射到指定位置(若为0则表示把该段共享内存映射到调用进程的地址空间)*/int shmflg)/*SHM_RDONLY:共享内存只读。默认0,共享内存可读写*/函数返回值成功:被映射的段地址出错:-1shmdt函数的语法如下所示。函数原型int shmdt(const void*shmaddr)/*被映射的共享内存段地址*/函数返回值成功:0出错:-1www.embedu.org6.3.3 共享内存使用实例 这里要介绍的一个命令是ipcs,这是用

39、于显示进程间通信状态的命令。它可以查看共享内存、消息队列等各种进程间通信机制对象的情况,这里使用了system函数来调用命令ipcs,函数,如下所示:/*创建共享内存*/shmget(IPC_PRIVATE,BUFSZ,0666);/*映射共享内存*/shmat(shmid,0,0);/*删除共享内存*/shmdt(shmadd);www.embedu.org6.3.4 消息队列消息队列概述 消息队列就是一个消息的链表。用户可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息,消息队

40、列是随内核持续的。www.embedu.org6.3.4 消息队列消息队列实现说明 消息队列的实现包括创建或打开消息队列、添加消息、读取消息和控制消息队列这4种操作。创建或打开消息队列使用函数msgget,这里创建的消息队列的数量会受到系统消息队列数量的限制。添加到消息队列使用函数msgsnd,它把消息添加到已打开的消息队列末尾。读取消息队列内容使用函数msgrcv,它把消息从消息队列中取走,与FIFO不同的是,这里可以指定取走某一类型的消息。控制消息队列使用函数msgctl,它可以完成多项功能。www.embedu.org6.3.4 消息队列函数格式 用到的头文件如下所示:#include#

41、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,/*消息的字节数*/int flag)/*有两种取值情况:IPC_NOWAIT若消息并没有立即发送而调用进程会立即返回。0:msgsnd调用阻塞直到条件满足为止*/www.e

42、mbedu.org6.3.4 消息队列这里prt消息的结构如下所示:struct msgbuflong mtype;/消息类型char mtextLEN;/消息正文,LEN由用户指定 函数返回值成功:0出错:-1msgrcv函数的语法要点如下所示。函数原型int msgrcv(int msgid,/*消息队列的队列ID*/struct msgbuf*msgp,/*消息缓冲区*/int size,/*消息的字节数*/long msgtype,/*接收的消息类型*/int flag)/*类型符*/函数返回值成功:0出错:-1flag含 义MSG_NOERROR若返回的消息比size字节多,则消息就

43、会截短到size字节,且不通知消息发送进程IPC_NOWAIT若消息并没有立即发送而调用进程会立即返回0msgsnd调用阻塞直到条件满足为止www.embedu.org6.3.4 消息队列msgctl函数的语法要点如下所示。函数原型int msgctl(int msgqid,/*消息队列的队列ID*/int cmd,/*消息队列控制选项*/struct msqid_ds*buf)/*消息队列缓冲区*/函数返回值成功:0出错:-1cmd含 义IPC_STAT读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中IPC_SET设置消息队列的数据结构msqid_ds中的ipc_per

44、m元素的值,这个值取自buf参数IPC_RMID从系统内核中移走消息队列www.embedu.org6.3.4 消息队列使用实例在使用消息队列前,可以先使用函数ftok,根据不同的路径和关键表示产生标准的key。之后使用msgget等函数对消息队列进行操作,如下所示:/*自定义消息格式*/struct message msg;/*创建消息队列*/msgget(key,IPC_CREAT|0666);/*添加消息到消息队列*/msgsnd(qid,&msg,len,0);/*读取消息队列*/msgrcv(qid,&msg,BUFSZ,0,0);/*从系统内核中移走消息队列。*/msgctl(qi

45、d,IPC_RMID,NULL);www.embedu.org6.4 ARM Linux线程相关API 线程创建和退出(1)函数说明使用线程主要包括以下几个步骤。创建线程 调用相关线程函数 线程退出 线程资源回收www.embedu.org6.4 ARM Linux线程相关API 线程创建和退出(2)函数格式#include pthread_create函数的语法要点如下所示。函数原型int pthread_create(pthread_t*thread,/*线程标识符*/pthread_attr_t*attr,/*线程属性设置,默认为NULL,可以使用其他函数来设置*/void*(*star

46、t_routine)(void*),/*线程函数的起始地址*/void*arg)/*传递给start_routine的参数*/函数返回值成功:0出错:-1www.embedu.org6.4 ARM Linux线程相关API线程创建和退出(2)函数格式pthread_exit函数的语法要点如下所示。函数原型void pthread_exit(void*retval)/*线程的返回值,可由其他函数如pthread_join来检索获取*/pthread_join函数的语法要点如下所示。函数原型int pthread_join(pthread_t thread,/*等待线程的标识符*/void*thr

47、ead_return)/*用户定义的指针,用来存储被等待线程的返回值(不为NULL时)*/函数返回值成功:0出错:-1www.embedu.org6.4 ARM Linux线程相关API线程创建和退出(3)函数调用实例在使用pthread_create函数时,通常可以将所要传递给线程函数的参数写成一个结构体,传入到该函数中。pthread_join函数则使用pthread_create函数的id等待线程退出,该函数调用源码如下所示:void thread(void)/*具体线程函数*/*主函数中创建线程*/ret=pthread_create(&id,NULL,(void*)thread,NU

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

49、加调用线程在互斥上加锁的次数。检错互斥锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。www.embedu.org6.4 ARM Linux线程相关APImutex线程访问控制(1)mutex互斥锁函数说明互斥锁的操作主要包括以下几个步骤。互斥锁初始化:pthread_mutex_init。互斥锁上锁:pthread_mutex_lock。互斥锁判断上锁:pthread_mutex_trylock。互斥锁接锁:pthread_mutex_unlock。消除互斥锁:pthread_mutex_destroy。www.embedu.org6.4 ARM Linux线程相关APImut

50、ex线程访问控制(2)函数格式这几个函数都需要包含同样的头文件,如下所示:#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_m

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

52、/pthread_mutex_unlock(&mutex);www.embedu.org6.4 ARM Linux线程相关API 信号量线程控制(1)信号量说明信号量也就是操作系统中所用到的PV原语,它广泛用于进程或线程间的同步与互斥。PV原语是对整数计数器信号量sem的操作。一次P操作使sem减一,而一次V操作使sem加一。进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限。当信号量sem的值大于等于0时,该进程(或线程)具有公共资源的访问权限;相反,当信号量sem的值小于0时,该进程(或线程)就将阻塞直到信号量sem的值大于等于0为止。www.embedu.org6.4 ARM

53、Linux线程相关API信号量线程控制(1)信号量说明PV原语主要用于进程或线程间的同步和互斥这两种典型情况。若用于互斥,几个进程(或线程)往往只设置一个信号量sem,它们的操作流程如图6.8所示。www.embedu.org6.4 ARM Linux线程相关API信号量线程控制(1)信号量说明当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现它们之间的顺序执行,它们的操作流程如图6.9所示。www.embedu.org6.4 ARM Linux线程相关API信号量线程控制(2)函数说明Linux实现了POSIX的无名信号量,主要用于线程间的互斥同步。这里主要介绍几个常见函

54、数。sem_init用于创建一个信号量,并能初始化它的值。sem_wait和sem_trywait相当于P操作,它们都能将信号量的值减一,两者的区别在于若信号量小于0时,sem_wait将会阻塞进程,而sem_trywait则会立即返回。sem_post相当于V操作,它将信号量的值加一同时发出信号唤醒等待的进程。sem_getvalue用于得到信号量的值。sem_destroy用于删除信号量。www.embedu.org6.4 ARM Linux线程相关API信号量线程控制(3)函数格式sem_init函数的语法要点如下所示。头文件#include 函数原型int sem_init(sem_t

55、*sem,/*信号量*/int pshared,/*决定信号量能否在几个进程间共享。由于目前Linux还没有实现进程间共享信号量,所以这个值只能够取0*/unsigned int value)/*信号量初始化值*/sem_wait等函数的语法要点如下所示。头文件#include 函数原型int sem_wait(sem_t*sem)/*信号量*/int sem_trywait(sem_t*sem)/*信号量*/int sem_post(sem_t*sem)/*信号量*/int sem_getvalue(sem_t*sem)/*信号量*/int sem_destroy(sem_t*sem)/*信

56、号量*/函数返回值成功:0出错:-1www.embedu.org6.4 ARM Linux线程相关API信号量线程控制(4)使用实例/*信号量减一,P操作*/sem_wait(&sem);/*需要互斥的代码*/*信号量加一,V操作*/sem_post(&sem);www.embedu.org6.5 Linux守护进程6.5.1 守护进程概述 6.5.2 编写规则 6.5.3 守护进程实例 www.embedu.org6.5.1 守护进程概述 守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某

57、些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才会退出。如果想让某个进程不因为用户或终端或其他的变化而受到影响,那么就必须把这个进程变成一个守护进程。www.embedu.org6.5.2 编写规则创建子进程,父进程退出 在子进程中创建新会话 进程组进程组是一个或多个进程的集合。进程组由进程组ID来惟一标识 会话期会话组是一个或多个进程组的集合。改变当前目录为根目录 重设文件权限掩码 关闭文件描述符 www.embedu.org6.5.3 守护进程实例/*dameon.c创建守护进程实例*/#includ

58、e#include#include#include#include#include#include#define MAXFILE 65535int main()pid_t pc;int i,fd,len;char*buf=This is a Dameonn;len=strlen(buf);/*父进程退出*/pc=fork();if(pc0)www.embedu.org6.5.3 守护进程实例 exit(0);/*在子进程中创建*/setsid();/*改变当前目录为根目录*/chdir(/);/*重设文件权限掩码*/umask(0);/*关闭文件描述符*/for(i=0;iMAXFILE;i+

59、)close(i);/*这时创建完守护进程,以下开始正式进入守护进程工作*/while(1)if(fd=open(/tmp/dameon.log,O_CREAT|O_WRONLY|O_APPEND,0600)0)perror(open);exit(1);write(fd,buf,len+1);close(fd);sleep(5);www.embedu.org本章小结了解ARM Linux中进程线程的处理机制 掌握fork、exec函数族、exit、wait和waitpid函数的使用,尤其要理解fork函数和以往常见函数的区别 掌握各种通信方式的异同点,包括管道通信、信号通信、共享内存以及消息队列。掌握的是线程间的互斥访问 守护进程的编写方式 www.embedu.org

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

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!