《嵌入式Linux编程基础.ppt》由会员分享,可在线阅读,更多相关《嵌入式Linux编程基础.ppt(53页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第第4章章嵌入式嵌入式Linux编程基础编程基础4.1Linux中系统调用的基本概念中系统调用的基本概念4.2Linux中用户编程接口(中用户编程接口(API)及系统命)及系统命令的相互关系令的相互关系4.3Linux下设备文件读写方法下设备文件读写方法4.4Linux中标准文件中标准文件I/O函数的使用函数的使用 4.1Linux中系统调用的基本概念中系统调用的基本概念系统调用系统调用所谓系统调用是指操作系统提供给用户程序调用所谓系统调用是指操作系统提供给用户程序调用的一组的一组“特殊特殊”接口,用户程序可以通过这组接口,用户程序可以通过这组“特殊特殊”接口来获得操作系统内核提供的服务。例接
2、口来获得操作系统内核提供的服务。例如用户可以通过进程控制相关的系统调用来创建如用户可以通过进程控制相关的系统调用来创建进程、实现进程调度、进程管理等。进程、实现进程调度、进程管理等。为了更好地保护内核空间,将程序的运行空间分为了更好地保护内核空间,将程序的运行空间分为内核空间和用户空间(也就是常称的内核态和为内核空间和用户空间(也就是常称的内核态和用户态),它们分别运行在不同的级别上,在逻用户态),它们分别运行在不同的级别上,在逻辑上是相互隔离的。因此,用户进程在通常情况辑上是相互隔离的。因此,用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,下不允许访问内核数据,也无法使用内核函数
3、,它们只能在用户空间操作用户数据,调用用户空它们只能在用户空间操作用户数据,调用用户空间的函数。间的函数。在有些情况下,用户空间的进程需要获得一定的在有些情况下,用户空间的进程需要获得一定的系统服务(调用内核空间程序),这时操作系统系统服务(调用内核空间程序),这时操作系统就必须利用系统提供给用户的就必须利用系统提供给用户的“特殊接口特殊接口”系统调用规定用户进程进入内核空间的具体位置。系统调用规定用户进程进入内核空间的具体位置。进行系统调用时,程序运行空间需要从用户空间进行系统调用时,程序运行空间需要从用户空间进入内核空间,处理完后再返回到用户空间。进入内核空间,处理完后再返回到用户空间。4
4、.2Linux中用户编程接口(中用户编程接口(API)及系统命令的相互关系及系统命令的相互关系API前面讲到的系统调用并不是直接与程序员进行交互的,它前面讲到的系统调用并不是直接与程序员进行交互的,它仅仅是一个通过软中断机制向内核提交请求,以获取内核仅仅是一个通过软中断机制向内核提交请求,以获取内核服务的接口。在实际使用中程序员调用的通常是用户编程服务的接口。在实际使用中程序员调用的通常是用户编程接口接口API。比如比如open,close,read ,write,ioctl等都是等都是API接口函接口函数数。open read write open read write ioctlioctl
5、 调用其他库函数调用其他库函数 称为系统调用称为系统调用 其他库函数的实现其他库函数的实现 执行执行swiswi指令进入内核指令进入内核 系统调用的异常处理系统调用的异常处理 其他功能其他功能open read write open read write ioctlioctl 硬件设备硬件设备 应用程序应用程序库库内核内核驱动程序驱动程序LinuxLinux软件系统的层次关系(软件系统的层次关系(swiswi是是ARMARM指令)指令)系统命令系统命令系统命令:系统命令:ls,cd,mkdir,cp,chmod,rm系统命令相对系统命令相对API更高了一层,它实际上一个可执行程序,更高了一层,
6、它实际上一个可执行程序,它的内部引用了用户编程接口(它的内部引用了用户编程接口(API)来实现相应的功能。)来实现相应的功能。4.3Linux下设备文件读写方法下设备文件读写方法文件描述符文件描述符Linux中对目录和设备的操作都等同于文件的操作,中对目录和设备的操作都等同于文件的操作,因此,大大简化了系统对不同设备的处理,提高因此,大大简化了系统对不同设备的处理,提高了效率。了效率。Linux中的文件分为中的文件分为4种:普通文件、目种:普通文件、目录文件、链接文件和设备文件。录文件、链接文件和设备文件。内核如何区分和引用特定的文件呢?这里用到了内核如何区分和引用特定的文件呢?这里用到了一个
7、重要的概念一个重要的概念文件描述符。文件描述符。文件描述符文件描述符内核如何区分和引用特定的文件呢?这里用到了内核如何区分和引用特定的文件呢?这里用到了一个重要的概念一个重要的概念文件描述符文件描述符。对于。对于Linux而言,而言,所有对设备和文件的操作都是使用文件描述符来所有对设备和文件的操作都是使用文件描述符来进行的。进行的。文件描述符是一个文件描述符是一个非负的整数非负的整数,它是一个索引值,它是一个索引值,并指向在内核中每个进程打开文件的记录表。当并指向在内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就打开一个现存文件或创建一个新文件时,内核就向进程返回一
8、个文件描述符向进程返回一个文件描述符(fd);当需要读写文;当需要读写文件时,也需要把文件描述符作为参数传递给相应件时,也需要把文件描述符作为参数传递给相应的函数的函数.通常一个进程启动时,都会打开通常一个进程启动时,都会打开3 3个文件:标准输个文件:标准输入、标准输出和标准出错处理。这入、标准输出和标准出错处理。这3 3个文件对应的个文件对应的文件描述符为文件描述符为0 0、1 1和和2 2(也就是宏替换(也就是宏替换STDIN_FILENOSTDIN_FILENO、STDOUT_FILENOSTDOUT_FILENO和和STDERR_FILENOSTDERR_FILENO)基于文件描述符
9、的基于文件描述符的I/OI/O操作是操作是LinuxLinux系统所特有的系统所特有的文件操作模式。文件操作模式。函数说明函数说明 open()函数是用于打开,在打开时可以指定文件的属性及用户的权限等各种参数。close()函数是用于关闭一个被打开的文件。当一个进程终止时,所有被它打开的文件都由内核自动关闭,很多程序都使用这一功能而不显示地关闭一个文件。read()函数是用于将从指定的文件描述符中读出的数据放到缓存区中,并返回实际读入的字节数。若返回0,则表示没有数据可读,即已达到文件尾。读操作从文件的当前指针位置开始。当从终端设备文件中读出数据时,通常一次最多读一行。write()函数是用于
10、向打开的文件写数据,写操作从文件的函数是用于向打开的文件写数据,写操作从文件的当前指针位置开始。对磁盘文件进行写操作,若磁盘已满当前指针位置开始。对磁盘文件进行写操作,若磁盘已满或超出该文件的长度,则或超出该文件的长度,则write()函数返回失败。函数返回失败。ioctl()函数是设备驱动程序中对设备的函数是设备驱动程序中对设备的I/O通道进行管理的通道进行管理的函数。所谓对函数。所谓对I/O通道进行管理,就是对设备的一些特性进通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。也可行控制,例如串口的传输波特率、马达的转速等等。也可以通过它实现对简单以通过它实现
11、对简单I/O设备进行控制。设备进行控制。上述函数格式如下:上述函数格式如下:4.4Linux中标准文件中标准文件I/O函数的使用函数的使用数码管驱动程序中的函数数码管驱动程序中的函数驱动程序驱动程序s3c2440-led.c#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#defineDEVICE_NAMEs3c2440_led#define
12、DEVICE_MAJOR251#defineDEVICE_MINOR0structcdev*mycdev;structclass_simple*myclass;dev_tdevno;#defineLED_TUBE_IOCTRL0 x11#defineLED_DIG_IOCTRL0 x12#defineLED_BASE0 x08000100staticintled_base;staticints3c2440_led_ioctl(structinode*inode,structfile*filp,unsignedintcmd,unsignedintarg)printk(DOTbufferis%xn
13、,arg8);printk(DOTbufferis%xn,arg);switch(cmd)caseLED_DIG_IOCTRL:writel(arg8,led_base+0 x10);writel(arg8|arg16,led_base+0 x11);returnreadl(led_base+0 x10);default:returnprintk(yourcommandisnotexist);return0;staticints3c2440_led_open(structinode*inode,structfile*filp)printk(leddeviceopensucess!n);retu
14、rn0;staticints3c2440_led_release(structinode*inode,structfile*filp)printk(leddevicereleasen);return0;staticstructfile_operationss3c2440_led_fops=owner:THIS_MODULE,open:s3c2440_led_open,ioctl:s3c2440_led_ioctl,write:s3c2440_led_write,release:s3c2440_led_release,;int_inits3c2440_led_init(void)intret;l
15、ed_base=ioremap(LED_BASE,0 x20);writel(readl(S3C2410_BWSCON)&(S3C2410_BWSCON_ST1|S3C2410_BWSCON_WS1|S3C2410_BWSCON_DW1_8)|(S3C2410_BWSCON_ST1|S3C2410_BWSCON_WS1|S3C2410_BWSCON_DW1_16),S3C2410_BWSCON);writel(S3C2410_BANKCON_Tacs4|S3C2410_BANKCON_Tcos4|S3C2410_BANKCON_Tacc14|S3C2410_BANKCON_Tcoh4|S3C2
16、410_BANKCON_Tcah4|S3C2410_BANKCON_Tacp6|S3C2410_BANKCON_PMC8),S3C2410_BANKCON1);writel(readl(S3C2410_GPACON)|(0 x112),S3C2410_GPACON);interr;devno=MKDEV(DEVICE_MAJOR,DEVICE_MINOR);mycdev=cdev_alloc();cdev_init(mycdev,&s3c2440_led_fops);err=cdev_add(mycdev,devno,1);if(err!=0)printk(s3c2440motordevice
17、registerfailed!n);myclass=class_create(THIS_MODULE,s3c2440-led);if(IS_ERR(myclass)printk(Err:failedincreatingclass.n);return-1;class_device_create(myclass,NULL,MKDEV(DEVICE_MAJOR,DEVICE_MINOR),NULL,DEVICE_NAME%d,DEVICE_MINOR);printk(DEVICE_NAMEtdeviceinitializedn);return0;void_exits3c2440_led_exit(v
18、oid)cdev_del(mycdev);class_device_destroy(myclass,devno);class_destroy(myclass);module_init(s3c2440_led_init);module_exit(s3c2440_led_exit);数码管驱动程序中的函数数码管驱动程序中的函数测试程序测试程序test_led.c#include#include#include#include#include#include#defineTUBE_IOCTROL0 x11#defineDOT_IOCTROL0 x12voidjmdelay(intn)inti,j,k
19、;for(i=0;in;i+)for(j=0;j100;j+)for(k=0;k100;k+);intmain()intfd;inti,j,k;unsignedintLEDWORD;unsignedcharLEDCODE10=0 xc0,0 xf9,0 xa4,0 xb0,0 x99,0 x92,0 x82,0 xf8,0 x80,0 x90;fd=open(/dev/s3c2440_led0,O_RDWR);if(fd0)printf(#Leddeviceopenfail#n);return(-1);LEDWORD=0 xff00;printf(willenterTUBELED,please
20、waiting.n);LEDWORD=0 xff00;ioctl(fd,0 x12,LEDWORD);sleep(1);for(j=0;j2;j+)for(i=0;i10;i+)LEDWORD=(LEDCODEi 0和和TIME=0:read()函数会被阻塞直到函数会被阻塞直到MIN个字节数据可被读个字节数据可被读取。取。MIN=0和和TIME 0:只要有数据可读或者经过:只要有数据可读或者经过TIME个十分之一秒的个十分之一秒的时间,时间,read()函数则立即返回,返回值为被读取的字节数。如果超时并函数则立即返回,返回值为被读取的字节数。如果超时并且未读到数据,则且未读到数据,则read(
21、)函数返回函数返回0。MIN 0和和TIME 0:当有:当有MIN个字节可读或者两个输入字符之间的个字节可读或者两个输入字符之间的时间间隔超过时间间隔超过TIME个十分之一秒时,个十分之一秒时,read()函数才返回。因为在输入函数才返回。因为在输入第一个字符之后系统才会启动定时器,所以在这种情况下,第一个字符之后系统才会启动定时器,所以在这种情况下,read()函数函数至少读取一个字节之后才返回。至少读取一个字节之后才返回。终端模式(终端模式(3)-原始模式原始模式按照严格意义来讲原始模式是按照严格意义来讲原始模式是一种特殊的非规范模式一种特殊的非规范模式。在。在原始模式下,所有的输入数据以
22、字节为单位被处理。在这原始模式下,所有的输入数据以字节为单位被处理。在这个模式下,终端是不可回显的,而且所有特定的终端输入个模式下,终端是不可回显的,而且所有特定的终端输入/输出控制处理不可用。通过调用输出控制处理不可用。通过调用cfmakeraw()函数可以将终函数可以将终端设置为原始模式,而且该函数通过以下代码可以得到实端设置为原始模式,而且该函数通过以下代码可以得到实现。现。termios_p-c_iflag&=(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);termios_p-c_oflag&=OPOST;termios_p-c
23、_lflag&=(ECHO|ECHONL|ICANON|ISIG|IEXTEN);termios_p-c_cflag&=(CSIZE|PARENB);termios_p-c_cflag|=CS8;设置串口属性的基本流程设置串口属性的基本流程(1)保存原先串口配置保存原先串口配置 首先,为了安全起见和以后调试程序方便,可以先保存原先首先,为了安全起见和以后调试程序方便,可以先保存原先串口的配置,在这里可以使用函数串口的配置,在这里可以使用函数tcgetattr(fd,&old_cfg)。该函数得到该函数得到fd指向的终端的配置参数,并将它们保存于指向的终端的配置参数,并将它们保存于termios
24、结构变量结构变量old_cfg中。该函数还可以测试配置是否正中。该函数还可以测试配置是否正确、该串口是否可用等。若调用成功,函数返回值为确、该串口是否可用等。若调用成功,函数返回值为0,若,若调用失败,函数返回值为调用失败,函数返回值为 1 if(tcgetattr(fd,&old_cfg)!=0)perror(tcgetattr);return-1;设置串口属性的基本流程设置串口属性的基本流程(2)激活选项激活选项 CLOCAL和和CREAD分别用于本地连接和接收使能,因此,分别用于本地连接和接收使能,因此,首先要通过位掩码的方式激活这两个选项。首先要通过位掩码的方式激活这两个选项。调用调用
25、cfmakeraw()函数可以将终端设置为原始模式,在后面函数可以将终端设置为原始模式,在后面的实例中,采用原始模式进行串口数据通信。的实例中,采用原始模式进行串口数据通信。newtio.c_cflag|=CLOCAL|CREAD;cfmakeraw(&new_cfg);设置串口属性的基本流程设置串口属性的基本流程(3)设置波特率设置波特率设置波特率有专门的函数,用户不能直接通过位掩码来操作。设置波特率有专门的函数,用户不能直接通过位掩码来操作。设置波特率的主要函数有:设置波特率的主要函数有:cfsetispeed()和和cfsetospeed()。设置字符大小设置字符大小 与设置波特率不同,
26、设置字符大小并没有现成可用的函数,与设置波特率不同,设置字符大小并没有现成可用的函数,需要用位掩码。需要用位掩码。cfsetispeed(&new_cfg,B115200);cfsetospeed(&new_cfg,B115200);new_cfg.c_cflag&=CSIZE;/*用数据位掩码清空数据位设置用数据位掩码清空数据位设置*/new_cfg.c_cflag|=CS8;设置串口属性的基本流程设置串口属性的基本流程(4)设置奇偶校验位设置奇偶校验位 设置奇偶校验位需要用到设置奇偶校验位需要用到termios中的两个成员:中的两个成员:c_cflag和和c_iflag。首先要激活。首先要
27、激活c_cflag中的校验位使能标志中的校验位使能标志PARENB和是否要进行偶校验,同时还要激活和是否要进行偶校验,同时还要激活c_iflag中的对于输入中的对于输入数据的奇偶校验使能(数据的奇偶校验使能(INPCK)。)。偶校验偶校验奇校验奇校验new_cfg.c_cflag|=(PARODD|PARENB);new_cfg.c_iflag|=INPCK;/启用输入奇偶检测启用输入奇偶检测。new_cfg.c_cflag|=PARENB;new_cfg.c_cflag&=PARODD;/*清除奇校验标志,则配置为偶校验清除奇校验标志,则配置为偶校验*/new_cfg.c_iflag|=IN
28、PCK;设置串口属性的基本流程设置串口属性的基本流程(5)设置停止位设置停止位 设置停止位是通过激活设置停止位是通过激活c_cflag中的中的CSTOPB而实现的。若而实现的。若停止位为一个,则清除停止位为一个,则清除CSTOPB,若停止位为两个,则激,若停止位为两个,则激活活CSTOPB。以下分别是停止位为一个和两个时的代码:。以下分别是停止位为一个和两个时的代码:new_cfg.c_cflag&=CSTOPB;/*将停止位设置为一个比特将停止位设置为一个比特*/new_cfg.c_cflag|=CSTOPB;/*将停止位设置为两个比特将停止位设置为两个比特*/设置最少字符和等待时间设置最少
29、字符和等待时间 在对接收字符和等待时间没有特别要求的情况下,可以将在对接收字符和等待时间没有特别要求的情况下,可以将其设置为其设置为0,则在任何情况下,则在任何情况下read()函数立即返回函数立即返回 new_cfg.c_ccVTIME=0;new_cfg.c_ccVMIN=0;设置串口属性的基本流程设置串口属性的基本流程(6)清除串口缓冲清除串口缓冲 由于串口在重新设置之后,需要对当前的串口设备进行适由于串口在重新设置之后,需要对当前的串口设备进行适当的处理,这时就可调用在当的处理,这时就可调用在中声明的中声明的tcdrain()、tcflow()、tcflush()等函数来处理目前串口缓
30、冲中的数据等函数来处理目前串口缓冲中的数据 inttcdrain(intfd);/*使程序阻塞,直到输出缓冲区的数据全部发送完毕使程序阻塞,直到输出缓冲区的数据全部发送完毕*/inttcflow(intfd,intaction);/*用于暂停或重新开始输出用于暂停或重新开始输出*/inttcflush(intfd,intqueue_selector);/*用于清空输入用于清空输入/输出缓冲区输出缓冲区*/设置串口属性的基本流程设置串口属性的基本流程(7)在本实例中使用在本实例中使用tcflush()函数,对于在缓冲区中的尚未传函数,对于在缓冲区中的尚未传输的数据,或者收到的但是尚未读取的数据进
31、行处理。其输的数据,或者收到的但是尚未读取的数据进行处理。其处理方法取决于处理方法取决于queue_selector的值,它可能的取值有以下的值,它可能的取值有以下几种。几种。TCIFLUSH:对接收到而未被读取的数据进行清空处理。:对接收到而未被读取的数据进行清空处理。TCOFLUSH:对尚未传送成功的输出数据进行清空处理。:对尚未传送成功的输出数据进行清空处理。TCIOFLUSH:包括前两种功能,即对尚未处理的输入输:包括前两种功能,即对尚未处理的输入输出数据进行清空处理。出数据进行清空处理。tcflush(fd,TCIFLUSH);设置串口属性的基本流程设置串口属性的基本流程(8)激活配
32、置激活配置 在完成全部串口配置之后,要激活刚才的配置并使配置生效。这里用在完成全部串口配置之后,要激活刚才的配置并使配置生效。这里用 到的函数是到的函数是tcsetattr()。tcsetattr(intfd,intoptional_actions,conststructtermios*termios_p);其中参数其中参数termios_p是是termios类型的新配置变量。类型的新配置变量。参数参数optional_actions可能的取值有以下三种:可能的取值有以下三种:TCSANOW:配置的修改立即生效。:配置的修改立即生效。TCSADRAIN:配置的修改在所有写入:配置的修改在所有写
33、入fd的输出都传输完毕之后生效。的输出都传输完毕之后生效。TCSAFLUSH:所有已接受但未读入的输入都将在修改生效之前被丢弃。:所有已接受但未读入的输入都将在修改生效之前被丢弃。该函数若调用成功则返回该函数若调用成功则返回0,若失败则返回,若失败则返回 1if(tcsetattr(fd,TCSANOW,&new_cfg)!=0)perror(tcsetattr);return-1;串口使用串口使用(1)打开串口打开串口 打开串口和打开普通文件一样,使用打开串口和打开普通文件一样,使用open()函数。函数。fd=open(/dev/ttyS0,O_RDWR|O_NOCTTY|O_NDELAY
34、);O_NOCTTY标志用于通知标志用于通知Linux系统,该参数不会使打系统,该参数不会使打开的文件成为这个进程的控制终端。如果没有指定这个标开的文件成为这个进程的控制终端。如果没有指定这个标志,那么任何一个输入(诸如键盘中止信号等)都将会影志,那么任何一个输入(诸如键盘中止信号等)都将会影响用户的进程。响用户的进程。O_NDELAY标志通知标志通知Linux系统,这个程序不关心系统,这个程序不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。信号线所处的状态(端口的另一端是否激活或者停止)。串口使用串口使用(2)读写串口读写串口 读写串口操作和读写普通文件一样,使用读写串口操作和
35、读写普通文件一样,使用read()和和write()函函数数:write(fd,buff,strlen(buff);read(fd,buff,BUFFER_SIZE);接下来可恢复串口的状态为阻塞状态,用于等待串口接下来可恢复串口的状态为阻塞状态,用于等待串口数据的读入数据的读入:接着可以测试打开文件描述符是否连接到一个终端设接着可以测试打开文件描述符是否连接到一个终端设备,以进一步确认串口是否正确打开备,以进一步确认串口是否正确打开:fcntl(fd,F_SETFL,0);isatty(STDIN_FILENO);示例示例串行端口程序设计串行端口程序设计#include#include#in
36、clude#include#include#include#defineBAUDRATEB115200#defineCOM1/dev/ttyS0#defineCOM2/dev/ttyS1#defineENDMINITERM27/*ESCtoquitminiterm*/#defineFALSE0#defineTRUE1volatileintSTOP=FALSE;volatileintfd;voidchild_handler(ints)printf(stop!n);STOP=TRUE;void*keyboard(void*data)intc;for(;)c=getchar();if(c=ENDMI
37、NITERM)STOP=TRUE;break;returnNULL;/*modeminputhandler*/void*receive(void*data)intc;printf(readmodemn);while(STOP=FALSE)read(fd,&c,1);/*comport*/write(1,&c,1);/*stdout*/printf(exitfromreadingmodemn);returnNULL;void*send(void*data)intc=0;printf(senddatan);while(STOP=FALSE)c+;c%=255;write(fd,&c,1);/*st
38、dout*/usleep(100000);returnNULL;/*waitforchildtodieoritwillbecomeazombie*/intmain(intargc,char*argv)structtermiosoldtio,newtio,oldstdtio,newstdtio;structsigactionsa;intok;pthread_tth_a,th_b,th_c;void*retval;if(argc1)fd=open(COM2,O_RDWR);elsefd=open(COM1,O_RDWR);/|O_NOCTTY|O_NONBLOCK);if(fd0)perror(C
39、OM1);exit(-1);tcgetattr(0,&oldstdtio);tcgetattr(fd,&oldtio);/*savecurrentmodemsettings*/tcgetattr(fd,&newstdtio);/*getworkingstdtio*/newtio.c_cflag=BAUDRATE|CRTSCTS|CS8|CLOCAL|CREAD;/*ctrolflag*/newtio.c_iflag=IGNPAR;/*inputflag,忽略幀和奇偶校验错忽略幀和奇偶校验错*/newtio.c_oflag=0;/*outputflag*/newtio.c_lflag=0;new
40、tio.c_ccVMIN=1;newtio.c_ccVTIME=0;/*nowcleanthemodemlineandactivatethesettingsformodem*/tcflush(fd,TCIFLUSH);/刷新数据线上的数据。刷新数据线上的数据。tcsetattr(fd,TCSANOW,&newtio);/串口属性设置立即生效。串口属性设置立即生效。sa.sa_handler=child_handler;sa.sa_flags=0;sigaction(SIGCHLD,&sa,NULL);/*handledyingchild*/pthread_create(&th_a,NULL,keyboard,0);pthread_create(&th_b,NULL,receive,0);pthread_create(&th_c,NULL,send,0);pthread_join(th_a,&retval);pthread_join(th_b,&retval);pthread_join(th_c,&retval);tcsetattr(fd,TCSANOW,&oldtio);/*restoreoldmodemsetings*/tcsetattr(0,TCSANOW,&oldstdtio);/*restoreoldttysetings*/close(fd);exit(0);