第五讲系统调用new

上传人:hs****ma 文档编号:578679486 上传时间:2024-08-24 格式:PPT 页数:120 大小:1.45MB
返回 下载 相关 举报
第五讲系统调用new_第1页
第1页 / 共120页
第五讲系统调用new_第2页
第2页 / 共120页
第五讲系统调用new_第3页
第3页 / 共120页
第五讲系统调用new_第4页
第4页 / 共120页
第五讲系统调用new_第5页
第5页 / 共120页
点击查看更多>>
资源描述

《第五讲系统调用new》由会员分享,可在线阅读,更多相关《第五讲系统调用new(120页珍藏版)》请在金锄头文库上搜索。

1、第五讲 系统调用系统调用简介系统调用简介文件管理文件管理进程管理进程管理信号信号进程间通信进程间通信15.1系统调用简介n系统调用概念系统调用概念l为了利用创建文件、进程创建和复制及进程间通为了利用创建文件、进程创建和复制及进程间通信这些操作系统提供的服务,应用程序必须和操作信这些操作系统提供的服务,应用程序必须和操作系统之间交互。系统之间交互。这种交互通过这种交互通过“系统调用系统调用”来实现。来实现。l系统调用系统调用是程序员和是程序员和linux内核的函数接口。内核的函数接口。l对程序员来说,对程序员来说,系统调用系统调用就象就象库函数库函数(事实上有(事实上有些就是库函数,由这些库函数

2、再进行真正的系统调些就是库函数,由这些库函数再进行真正的系统调用),只是前者直接在用),只是前者直接在linux的核心执行子程序调的核心执行子程序调用。用。2系统调用类别系统调用类别l文件管理文件管理l进程管理进程管理l错误管理错误管理3文件管理系统调用的层次结构文件管理系统调用的层次结构文件特殊文件目录文件openclosereadwrite lseekunlinkchown dup2 fcntl fstat ftruncate stattruncate sync dup link 套接字Internet套接字mknod ioctl pipegetdentsaccept bind conne

3、ct listen socketgethostbyname gethostname htonl htons inet_addr inet_ntoa4进程管理系统调用的层次结构进程管理系统调用的层次结构进程信号nice chdir wait fork exec exit alarm singnal kill pause5错误处理系统调用的层次结构错误处理系统调用的层次结构错误处理perror65.2 文件管理文件管理系统调用能够操作所有的文件管理系统调用能够操作所有的普通文件普通文件、特殊文件特殊文件和和目录文件目录文件,包括:,包括:l基于磁盘的文件基于磁盘的文件l终端终端l打印机等设备打印机

4、等设备l进程间通信功能,如管道和套接字进程间通信功能,如管道和套接字多数情况下,最初使用多数情况下,最初使用open()访问或创建文件,如果系统访问或创建文件,如果系统调用成功,则返回一个称为调用成功,则返回一个称为“文件描述符文件描述符”的小整数,用的小整数,用于对该文件的于对该文件的I/O操作。如果操作。如果open()失败,返回失败,返回-1。7int fd; /file descriptorfd=open(fileName,); /*open file, return file descriptor*/If(fd=-1) /*deal with error condition */fc

5、ntl (fd,); /*set some I/O flags if necessary */典型的文件操作事件序列典型的文件操作事件序列8read(fd,); /* read from file */write(fd,); /write to file lseek(fd,); /seek within fileclose(fd); /close the file,freeing file descriptor典型的文件操作事件序列典型的文件操作事件序列9l文件描述符文件描述符从从0开始按顺序编号,前三个描开始按顺序编号,前三个描述符具有特定含义:述符具有特定含义:0 标准输入(标准输入(st

6、din)- - -键盘键盘1 标准输出(标准输出(stdout)- - -显示器显示器2 标准错误(标准错误(stderr)l单个文件能打开几次,因此它可以有多个文单个文件能打开几次,因此它可以有多个文件描述符件描述符文件fd1fd2fd35.2.1文件描述符文件描述符10 每个文件描述符有其专有的属性集,与它每个文件描述符有其专有的属性集,与它所关联的文件无关。所关联的文件无关。这些属性包括:这些属性包括:l 记录文件中进行读写操作的偏移量的文记录文件中进行读写操作的偏移量的文件指针;件指针;l 指示如果进程调用指示如果进程调用exec(),文件描述符,文件描述符是否应自动关闭的标志;是否应

7、自动关闭的标志;l 指示对文件的所有输出是否应追加到文指示对文件的所有输出是否应追加到文件末尾的标志;件末尾的标志;5.2.1文件描述符文件描述符11 以下属性只在文件是以下属性只在文件是管道和套接字管道和套接字这样的特这样的特殊文件时才有意义殊文件时才有意义:l 指示如果文件当前不包含任何输入,进程指示如果文件当前不包含任何输入,进程是否应阻塞来自该文件的输入的标志;是否应阻塞来自该文件的输入的标志;l 指示如果文件输入变为可用,应发送一个指示如果文件输入变为可用,应发送一个SIGIO信号的进程信号的进程ID或进程组的数字;或进程组的数字;系统调用系统调用open()和和fcntl()能操作

8、这些标志。能操作这些标志。5.2.1文件描述符文件描述符12l打开文件打开文件open()l读文件读文件read()l写文件写文件write()l移动文件指针移动文件指针lseek()l关闭文件关闭文件close()l删除文件删除文件unlink()5.2.2 文件管理系统调用文件管理系统调用13l函数定义函数定义int open (char *fileName, int mode ,int permissions)lopen只能以只能以只读或读写方式只读或读写方式打开或创建文件,打开或创建文件,fileName是一个是一个绝对绝对/相对路径相对路径的文件名,的文件名,mode打开打开模式,模

9、式,permissions是代表文件权限标志的一个编码是代表文件权限标志的一个编码值,该值只在创建文件时提供。值,该值只在创建文件时提供。l预定义的读预定义的读/写标志和混合标志的值写标志和混合标志的值在在”/usr/include/fcntl.h”中定义:中定义:O_RDONLY以只读方式打开;以只读方式打开; O_WRONLY以以只写方式打开;只写方式打开; O_RDWR以读写方式打开以读写方式打开5.2.2.1 打开文件打开文件open()14l创建文件创建文件/.开始的文件名使其成为隐藏文件开始的文件名使其成为隐藏文件sprintf ( tmpName,”. rev.%d”, getp

10、id(); /创建临时文件,可读写,设置文件权限创建临时文件,可读写,设置文件权限tmpfd=open (tmpName, O_CREAT | O_RDWR,0600)if(tmpfd=-1)fatalError();l打开已经存在的文件打开已经存在的文件fd=open (fileName, O_RDONLY);if (fd=-1) fatalError();5.2.2.1打开文件打开文件open()15l函数定义函数定义ssize_t read(int fd, void* buf, size_t count)lread()从从文件描述符文件描述符fd引用的文件引用的文件读取读取count个字

11、节到个字节到缓冲区缓冲区buf。l调用成功,调用成功,read()返回它读取的字节数,否则返回返回它读取的字节数,否则返回-1;如果在已经读取了最后一个字节后调用;如果在已经读取了最后一个字节后调用read(),它返,它返回回0,表示到达了文件末尾。,表示到达了文件末尾。l例:例:charsRead=read (fd, buffer, BUFFER_SIZE);if(charRead=0) break; / end of fileIf(charRead=-1) fatalError(); /error5.2.2.2读文件读文件read()16l函数定义函数定义ssize_t write(int

12、 fd, void* buf, size_t count)lwrite()从缓冲区从缓冲区buf向向文件描述符文件描述符fd引用的文件引用的文件复复制制count个字节。个字节。l字节从字节从当前文件位置当前文件位置开始写入,然后更新当前文件开始写入,然后更新当前文件位置。如果设置位置。如果设置fd的的O_APPEND标志,则每次写操标志,则每次写操作前文件位置被置为文件末尾。作前文件位置被置为文件末尾。l调用成功,调用成功,write()返回它复制的字节数,否则返回返回它复制的字节数,否则返回-1;进程应该总是检查返回值,如果返回值不是;进程应该总是检查返回值,如果返回值不是count,可能

13、磁盘已写满。,可能磁盘已写满。5.2.2.3写文件写文件write()17l例:例: /* Copy line to temporary file if reading from stdin */ if (standardInput) charsWritten = write (tmpfd, buffer, charsRead); if(charsWritten != charsRead) fatalError (); 5.2.2.3写文件写文件write()18l函数定义函数定义off_t lseek(int fd, off_t offset, int mode)lfd是文件描述符,是文件描

14、述符,offset是一个长整数,是一个长整数,mode描述描述该如何解释该如何解释offset。l“/usr/include/stdio.h”中定义了中定义了mode的三个取的三个取值:值:SEEK_SET offset相对于文件的开始相对于文件的开始SEEK_CUR offset相对于当前文件位置相对于当前文件位置SEEK_END offset相对于文件末尾相对于文件末尾5.2.2.4移动文件指针移动文件指针lseek()19如果试图移动文件指针到文件头之前,如果试图移动文件指针到文件头之前,lseek()会失败。会失败。如果成功,如果成功,lseek()返回当前文件指针位置,否则返回返回当

15、前文件指针位置,否则返回-1l例:例:/* Find line and read */lseek (fd, lineStarti, SEEK_SET); charsRead = read (fd, buffer, lineStarti+1 - lineStarti);5.2.2.4 移动文件指针移动文件指针lseek()20l函数定义函数定义int close(int fd)l如果如果fd是和特定打开文件关联的最后一个文件描述符,是和特定打开文件关联的最后一个文件描述符,给该文件分配的内核资源将被收回。给该文件分配的内核资源将被收回。l当一个进程结束时,它所有的文件描述符都会自动关闭,当一个进

16、程结束时,它所有的文件描述符都会自动关闭,但最好当完成文件操作时关闭文件。但最好当完成文件操作时关闭文件。l如果关闭一个已经关闭的文件,会发生错误。如果关闭一个已经关闭的文件,会发生错误。l调用成功,调用成功,close()返回返回0,否则返回,否则返回-1。l例:例: close (fd); /* Close input file */5.2.2.5 关闭文件关闭文件close()21l函数定义函数定义int unlink(const char* fileName)l unlink()删除从文件名删除从文件名fileName指向文件的硬指向文件的硬链接。如果链接。如果fileName是指向文

17、件的最后一个链接,是指向文件的最后一个链接,文件的资源被收回。文件的资源被收回。l如果调用成功,如果调用成功,unlink()返回返回0,否则返回,否则返回-1。l例:例: /* Remove temp file */ if (standardInput) unlink (tmpName); 5.2.2.6 删除文件删除文件unlink()22例:例:reverse -c fileName reverse将输入文件的各行将输入文件的各行逆序显示在标准输出上逆序显示在标准输出上。如果不指。如果不指定文件名,则逆序显示标准输入的内容。定文件名,则逆序显示标准输入的内容。 当使用当使用-c选项时,选

18、项时,reverse还颠倒每一行中的字符。还颠倒每一行中的字符。$gcc reverse.c -o reverse$cat testChirstmas is coming,The days that grow shorter.$./reverse c test.retrohs worg that syad ehT,gnimoc si samtsrihC5.2.3文件管理实例文件管理实例23例:例:reverse -c fileName$cat test | ./reverseThe days grow shorter.Christmas is coming.5.2.3文件管理实例文件管理实例2

19、4nreverse的工作过程的工作过程步骤步骤 操作操作 函数函数 系统调用系统调用1 分析命令行分析命令行 ParseCommmandLine, open processOptions 2 打开输入文件或打开输入文件或 读取标准输入,创读取标准输入,创 pass1 open 建存储输入的临时文件建存储输入的临时文件3 读取文件,把各行的起始读取文件,把各行的起始 pass1,trackLines read 偏移量保存在数组中偏移量保存在数组中4反向读取文件,把每行复制反向读取文件,把每行复制 pass2,processLine, lseek 到标准输出,如果选择到标准输出,如果选择-c, r

20、everseLine 字符也会逆序输出。字符也会逆序输出。5关闭文件,如果是临时文件,关闭文件,如果是临时文件, pass2 close 删除临时文件删除临时文件25名称名称 功能功能stat() 获得文件信息获得文件信息 opendir() 打开目录打开目录readdir() 读目录读目录closedir() 关闭目录关闭目录chown() 改变文件所有者和文件组改变文件所有者和文件组chmod() 改变文件权限改变文件权限dup() 复制文件描述符复制文件描述符fcntl() 对各种文件特性的访问对各种文件特性的访问5.2.4其他文件管理系统调用其他文件管理系统调用26名称名称 功能功能t

21、runcate 截短文件截短文件sync 调度所有文件缓冲区清空到磁盘调度所有文件缓冲区清空到磁盘mknod 创建特殊文件创建特殊文件link 创建硬链接创建硬链接ioctl 控制设备控制设备fchown 同同chownfchmod 同同chmoddup2 同同dupftruncate 同同truncate27例例1:把文件:把文件test.txt的组改为的组改为cs,该组的组,该组的组ID为为62/*mychown.c*/main () int flag; flag = chown (test.txt, -1, 62); /* Leave user ID unchanged */ if (f

22、lag = -1) perror(chown.c);$ls -l test.txt-rw-r-r- 1 glass music 3 May 25 11:42 test.txt$ls -l test.txt-rw-r-r- 1 glass cs 3 May 25 11:42 test.txt5.2.4其他文件管理系统调用其他文件管理系统调用28例例2:把文件:把文件test.txt的权限标志改为的权限标志改为600,即只有文件所有者拥有,即只有文件所有者拥有读写权限。读写权限。/*mychomod.c*/main () int flag; flag = chmod (test.txt, 0600

23、); /* Use an octal encoding */ if (flag = -1) perror (chmod.c);$ls -l test.txt-rw-r-r- 1 glass music 3 May 25 11:42 test.txt$ls -l test.txt-rw- 1 glass cs 3 May 25 11:42 test.txt5.2.4其他文件管理系统调用其他文件管理系统调用29例例3:创建一个文件:创建一个文件test.txt,然后通过,然后通过4个不同的个不同的文件描述符写这个文件。文件描述符写这个文件。/*mydup.c*/#include #include

24、main () int fd1, fd2, fd3; fd1 = open (test.txt, O_RDWR | O_TRUNC); printf (fd1 = %dn, fd1); write (fd1, whats, 6); 5.2.4其他文件管理系统调用其他文件管理系统调用30例例3:续:续fd2 = dup (fd1); /* Make a copy of fd1 */ printf (fd2 = %dn, fd2); write (fd2, up, 3); close (0); /* Close standard input */fd3 = dup (fd1); /* Make a

25、nother copy of fd1 */ printf (fd3 = %dn, fd3); write (0, doc, 4); dup2 (fd3, 2); /* Duplicate channel 3 to channel 2 */ write (2, ?n, 2);5.2.4其他文件管理系统调用其他文件管理系统调用31读取文件属性 32struct stat 33读取文件属性代码if(stat(argv1,&buff)!=-1)for(i=3;i;-i)printf(%3s,perm(buff.st_mode&mask)(i-1)*N_BITS);mask=N_BITS;34打开/关闭

26、目录文件 35读写目录内容 36示例37定位目录位置 38添加删除目录 39返回/修改当前工作路径操作 40示例 41进程相关的定义:进程相关的定义:1 进程进程是一个是一个独立的可调度的活动独立的可调度的活动。2进程进程是一个是一个抽象实体抽象实体,当它执行某个任务时,要分配,当它执行某个任务时,要分配和释放各种资源。和释放各种资源。3进程进程是可以是可以并行执行并行执行的计算部分。的计算部分。4进程进程是一个是一个具有独立功能的程序具有独立功能的程序,关于某个数据集合,关于某个数据集合的一次可以并发执行的活动,是处于活动状态的计算的一次可以并发执行的活动,是处于活动状态的计算机程序。机程序

27、。是是Linux系统基本调度单位系统基本调度单位。进程和程序的区别:进程和程序的区别:n几个进程可以并发的执行一个程序几个进程可以并发的执行一个程序n一个进程可以顺序的执行几个程序一个进程可以顺序的执行几个程序5.3 进程管理42Linux进程是一个正在运行或可运行的程序的唯一实例。进程是一个正在运行或可运行的程序的唯一实例。Linux系统中的每个进程都具有下列属性:系统中的每个进程都具有下列属性:l一些代码一些代码l一些数据一些数据l一个堆栈一个堆栈l一个唯一的进程号一个唯一的进程号PID Init进程进程:linux系统启动时,系统中只有一个可见进系统启动时,系统中只有一个可见进程,叫程,

28、叫init,其,其PID为为1。在。在linux创建进程的唯一方法创建进程的唯一方法是复制现有进程,所以是复制现有进程,所以init进程进程是所有随后进程的是所有随后进程的祖先祖先进程。进程。5.3 进程管理43 n 当一个进程复制时,父进程和子进程实际当一个进程复制时,父进程和子进程实际上是完全相同的(除了上是完全相同的(除了PID,PPID和运行库和运行库等。)等。)n子进程的代码、数据和堆栈子进程的代码、数据和堆栈是是父进程的副本父进程的副本。但是子进程也许会把代码替换为另一个可执但是子进程也许会把代码替换为另一个可执行文件的代码,将自己和父进程区分开来。行文件的代码,将自己和父进程区分

29、开来。n子进程终止时,系统将它的死亡消息传递给子进程终止时,系统将它的死亡消息传递给父进程,以便父进程采取适当的操作。父进程,以便父进程采取适当的操作。5.3.1 父进程和子进程父进程和子进程445.3.2 进程的状态进程的状态一个既在的进程调用fork创建一个新进程TASK_ZOMBIE(进程被终止)TASK_RUNNING(就绪,但是没有在运行)TASK_RUNNING(正在运行)进程等待中Task forks进程调度选择一个task进程被高优先级的进程抢占进程调用do_exit()终止执行进程睡眠,在等待特定事件或资源的等待队列上事件发生或资源可用,进程被唤醒,并被放到运行队列中45执行

30、等待就绪时间片到调度因等待事件发生而唤醒等待某个事件发生而睡眠5.3.2 进程的状态进程的状态46lfork 复制进程复制进程lgetpid 获得进程号获得进程号PIDlgetppid 获得父进程号获得父进程号PPIDlexit 终止进程终止进程lwait 等待子进程等待子进程lexec 替换进程的代码、数据和堆栈替换进程的代码、数据和堆栈5.3.3 进程管理的系统调用进程管理的系统调用47l函数定义函数定义 pid_t fork(void)lfork()使进程复制自己使进程复制自己。子进程。子进程继承父进程的代继承父进程的代码、数据、堆栈、打开的文件描述符和信号表码、数据、堆栈、打开的文件描

31、述符和信号表的的副本副本。子进程的。子进程的PID和父进程的和父进程的PPID不同。不同。l调用成功,调用成功,返回返回子进程的子进程的PID给父进程给父进程,返回返回0给子进程给子进程。调用失败,返回。调用失败,返回-1给父进程,没有创给父进程,没有创建子进程。建子进程。5.3.3.1 fork()48例例1:fork()的使用。的使用。/*myfork.c*/#include main () int pid; printf (Im the original process with PID %d and PPID %d.n, getpid (), getppid (); pid = for

32、k (); /* Duplicate. Child and parent continue from here */ if (pid != 0) /* pid is non-zero, so I must be the parent */ printf (Im the parent process with PID %d and PPID %d.n, getpid (), getppid (); printf (My childs PID is %dn, pid); else /* pid is zero, so I must be the child */ printf (Im the ch

33、ild process with PID %d and PPID %d.n, getpid (), getppid (); printf (PID %d terminates.n, getpid () ); /* Both processes execute this */ 49说明:说明:1,父进程的,父进程的PPID指向执行指向执行myfork.c 程序的程序的shell的的PID; 2,父进程不等待子进程的死亡就终止是非常,父进程不等待子进程的死亡就终止是非常危险的。危险的。 3,孤儿进程。如果父进程在子进程之前死亡,孤儿进程。如果父进程在子进程之前死亡,子进程就会自动被初始进程子进程就

34、会自动被初始进程init收养。收养。init子进程仍未终止父进程首先死亡收养子进程5.3.3.1 fork()50例例2:避免孤儿进程。:避免孤儿进程。/*orphan.c*/#include main () int pid; printf (Im the original process with PID %d and PPID %d.n, getpid (), getppid (); pid = fork (); /* Duplicate. Child and parent continue from here */ if (pid != 0) /* Branch based on ret

35、urn value from fork () */ printf (Im the parent process with PID %d and PPID %d.n, getpid (), getppid (); printf (My childs PID is %dn, pid); else sleep (5); /* Make sure that the parent terminates first */ printf (Im the child process with PID %d and PPID %d.n, getpid (), getppid (); printf (PID %d

36、 terminates.n, getpid () );515.3.3.2 getpid()和和getppid()函数定义函数定义pid_t getpid(void)和和pid_t getppid(void)getpid()和和geppid()分别返回分别返回 一个进程的一个进程的ID和和父进程的父进程的ID。它们总能成功执行。它们总能成功执行。52l函数定义函数定义 void exit(int status)l exit()关闭一个进程的所有文件描述符,收回进程关闭一个进程的所有文件描述符,收回进程的代码、数据和堆栈,然后终止进程。的代码、数据和堆栈,然后终止进程。 l当进程终止时,它向父进程

37、发送一个当进程终止时,它向父进程发送一个SIGCHLD信信号,并等待它的终止码号,并等待它的终止码status被接受。被接受。status值在值在0255间。间。l等待父进程接受它的终止码的进程叫等待父进程接受它的终止码的进程叫僵尸进程僵尸进程。父。父进程通用执行进程通用执行wait()接受子进程的终止码。接受子进程的终止码。linit进程总是会接受其子进程的终止码。进程总是会接受其子进程的终止码。lexit()不返回不返回5.3.3.3 exit()53例:例:/*myexit.c*/#include Main() Printf(“Im going to exit with return c

38、ode 42n”);exit(42);运行:运行:$./myexit Im going to exit with return code 42 $echo $status 425.3.3.3 exit()54n僵尸进程僵尸进程 终止的进程在父进程接受它的返回码之前不终止的进程在父进程接受它的返回码之前不能离开系统。能离开系统。如果它的父进程已经死亡,它将如果它的父进程已经死亡,它将成为孤儿被成为孤儿被init收养。如果它的父进程还活着,收养。如果它的父进程还活着,但一直都没执行但一直都没执行wait()接受子进程的返回码,该接受子进程的返回码,该进程就成为进程就成为僵尸进程僵尸进程。 僵尸进程

39、僵尸进程没有任何代码、数据或堆栈,占用没有任何代码、数据或堆栈,占用不了多少资源,但它存在于系统的任务列表中。不了多少资源,但它存在于系统的任务列表中。5.3.3.4 僵尸进程僵尸进程55例:僵尸进程例:僵尸进程/*zombie.c*/#include main () int pid; pid = fork (); /* Duplicate */ if (pid != 0) /*Be parent process */ while (1) /* Never terminate, and never execute a wait () */ sleep (1000); else /*Be chi

40、ld process*/ exit (42); /* Exit with a silly number */ 5.3.3.4 僵尸进程僵尸进程56$./zombie & 后台执行后台执行$ps 显示进程状态显示进程状态PID TTY TIME CMD15870 pts2 00:00:00 bash15896 pts2 00:00:00 zombie15897 pts2 00:00:00 zombie15898 pts2 00:00:00 ps$kill 15896 杀死父进程杀死父进程$ps15870 pts2 00:00:00 bash15898 pts2 00:00:00 ps5.3.3.

41、4 僵尸进程僵尸进程57l函数定义函数定义 pid_t wait(int* status)lwait()使一个进程挂起,直到该进程的一个子进使一个进程挂起,直到该进程的一个子进程终止。程终止。lwait()调用成功,返回终止子进程的调用成功,返回终止子进程的PID,并把,并把状态码放入状态码放入status中。中。l如果一个进程执行如果一个进程执行wait() ,而它没有子进程而它没有子进程,wait()调用失败,立即返回调用失败,立即返回-1。如果进程执行了。如果进程执行了wait(),而它的一个或几个进程已经是僵尸进程,则,而它的一个或几个进程已经是僵尸进程,则立即返回一个僵尸进程的状态码

42、。立即返回一个僵尸进程的状态码。5.3.3.5 等待子进程等待子进程wait()58例:系统调用例:系统调用wait()wait() /*mywait.c*/#include main () int pid, status, childPid; printf (Im the parent process and my PID is %dn, getpid ();pid = fork (); /* Duplicate */ if (pid != 0) /* Branch based on return value from fork () */ printf (Im the parent pro

43、cess with PID %d and PPID %dn, getpid (), getppid (); childPid = wait (&status); /* Wait for a child to terminate. */ printf (A child with PID %d terminated with exit code %dn, childPid, status 8); else printf (Im the child process with PID %d and PPID %dn, getpid (), getppid (); exit (42); /* Exit

44、with a silly number */ printf (PID %d terminatesn, getpid () ); 59l函数定义函数定义 int execl(const char*path, const char* arg0, const char*arg1, , const char* argn, NULL) int execv(const char*path, const char* argv ) int execlp(const char* path, const char* arg0, const char*arg1, , const char* argn, NULL)

45、int execvp(const char*path, const char* argv )lexecl和和execlp完全相同,完全相同,execv和和execvp完全相同。完全相同。execl()和和execv()要求提供可执行文件的绝对或相对路径名,而要求提供可执行文件的绝对或相对路径名,而execlp()和和execvp()使用使用$PATH环境变量查找环境变量查找path。l如果没有找到可执行文件,系统调用返回如果没有找到可执行文件,系统调用返回-1;否则进程用可执;否则进程用可执行文件替换它的代码、数据和堆栈,行文件替换它的代码、数据和堆栈,lexec的成功调用不返回任何值。的成功

46、调用不返回任何值。5.3.3.6区分进程区分进程exec()60例:例:/*myexec.c*/#include main () printf (Im process %d and Im about to exec an ls -ln, getpid (); execl (/bin/ls, ls, -l, NULL); /* Execute ls */ printf (This line should never be executedn);运行:运行:$./myexec Im process %d and Im about to exec an ls l total 125 -rw-r-r-

47、 1 glass cs 277 Feb 15 00:47 myex.c 5.3.3.6区分进程区分进程exec()61n后台处理后台处理例:例:/*background.c*/#include main (argc, argv)int argc;char* argv ; if (fork () = 0) /* Child */ execvp (argv1, &argv1); /* Execute other program */ fprintf (stderr, Could not execute %sn, argv1); 执行:执行:$background sleep 60 $ps PID

48、TTY TIME CMD 16073 pts0 00:00:00 sleep 6062l函数定义函数定义 int nice(int delta)lnice()在进程的当前优先级中添加在进程的当前优先级中添加delta。合法。合法的优先级值在的优先级值在-20+19之间。只有超级用户之间。只有超级用户(root)才能指定导致负值的)才能指定导致负值的delta。l如果调用成功,返回新的如果调用成功,返回新的nice值,调用失败返值,调用失败返回回-1。注意:这可能会导致问题,因为注意:这可能会导致问题,因为-1这个这个值是完全合法的。值是完全合法的。5.3.3.7 改变优先级改变优先级nice(

49、)63l例:例:/*mynice.c*/#include main () printf (original priorityn); system (ps -l); /* Execute a ps */ nice (0); /* Add 0 to my priority */ printf (running at priority 0n); system (ps -l); /* Execute another ps */ nice (10); /* Add 10 to my priority */ printf (running at priority 10n); system (ps -l);

50、 /* Execute the last ps */5.3.3.7 改变优先级改变优先级nice()64luid_t getuid() 返回调用进程的真正用户返回调用进程的真正用户IDluid_t geteuid() 返回调用进程的有效用户返回调用进程的有效用户IDlgid_t getgid() 返回调用进程的真正用户组返回调用进程的真正用户组IDlgid_t getegid() 返回调用进程的有效用户组返回调用进程的有效用户组IDID号对应在号对应在”/etc/passwd”和和”/etc/group”文件中列出文件中列出的用户的用户ID和用户组和用户组ID,这些调用总是成功的。,这些调用总

51、是成功的。lint setuid(uid_t id)lint seteuid(uid_t id)lint setgid(uid_t id)lint setegid(uid_t id)这些调用只有被超级用户执行或被调用进程的真正用户和用这些调用只有被超级用户执行或被调用进程的真正用户和用户组执行时才会成功。如果成功,返回户组执行时才会成功。如果成功,返回0,否则返回,否则返回-1。5.3.3.8其它进程管理系统调用其它进程管理系统调用655.4 信号程序必须处理程序必须处理没有预期的没有预期的或或不可预知的不可预知的事件,如:事件,如:l浮点错误浮点错误l掉电掉电l闹钟闹钟“响铃响铃”l子进程的

52、死亡子进程的死亡l用户的终止请求(即用户的终止请求(即ctrl-c)l用户的挂起请求(即用户的挂起请求(即ctrl-z) 这些事件发生时,必须中断正常的程序流进行处理。这些事件发生时,必须中断正常的程序流进行处理。当当linux意识到发生这样的一个事件时,它会给相应进意识到发生这样的一个事件时,它会给相应进程发送一个程发送一个信号信号。每个可能的事件对应一个每个可能的事件对应一个唯一的用数唯一的用数字表示的信号字表示的信号。665.4.1 信号简介例:如果进程中出现浮点错误,内核给出错的例:如果进程中出现浮点错误,内核给出错的进程发送信号进程发送信号8内核进程信号#8浮点错误信号浮点错误信号6

53、7 不是只有不是只有内核内核才能发送信号,任何进程都可以才能发送信号,任何进程都可以给其他进程发送信号,只要它有权限。给其他进程发送信号,只要它有权限。 一段特殊的一段特殊的用于处理或忽略特定信号的程序用于处理或忽略特定信号的程序叫叫做做“信号处理程序信号处理程序”。处理信号过程中,收到信号的。处理信号过程中,收到信号的进进程程挂起它当前的控制流挂起它当前的控制流,执行信号处理程序执行信号处理程序,处理结,处理结束时恢复原来的控制流。束时恢复原来的控制流。5.4.1 信号简介68lLinux支持两种信号类型:支持两种信号类型: 1.标准信号标准信号-传统的传统的UNIX信号信号 2.实时信号实

54、时信号(或排队信号)(或排队信号)l传统信号传统信号传递给进程是传递给进程是通过设置位图中的一位来完成通过设置位图中的一位来完成的的,位图中每一位对应一个信号。因此,无法表示同,位图中每一位对应一个信号。因此,无法表示同一个信号的多个实例。因为位图只能表示一个信号的多个实例。因为位图只能表示1(有信号)(有信号)和和0(无信号)(无信号)lPOSIX1003.1b为实时进程定义了为实时进程定义了排队信号排队信号。同一个信。同一个信号的连续实例是有效的,需要被正确地传递。为了使号的连续实例是有效的,需要被正确地传递。为了使用排队信号,必须使用系统调用用排队信号,必须使用系统调用sigaction

55、(),而不是,而不是signal()。5.4.2 信号类型69l信号在信号在”/usr/include/signal.h”或或”/usr/include/asm/signal.h”和其他平台特定的和其他平台特定的头文件中定义。头文件中定义。l程序员可以选择程序员可以选择特定的信号特定的信号触发触发用户提供的信号处理用户提供的信号处理程序程序或或内核提供的处理程序内核提供的处理程序,或者,或者忽略忽略。l默认的处理程序通常执行下面的一个操作:默认的处理程序通常执行下面的一个操作:(1)终止进程并在一个核心文件中生成内存的转储终止进程并在一个核心文件中生成内存的转储(core)(2)终止进程,不生

56、成核心映像文件(终止进程,不生成核心映像文件(quit)(3)忽略并放弃信号(忽略并放弃信号(ingnore)(4)挂起进程(挂起进程(stop)(5)恢复进程恢复进程(restore)5.4.3 信号定义70lLinux中定义的标准中定义的标准POSIX信号信号 宏名宏名 编号编号 默认操作默认操作 描述描述SIGHUP 1 quit 控制进程的挂起或死亡控制进程的挂起或死亡 SIGINT 2 quit 键盘中断键盘中断SIGQUIT 3 core 退出退出SIGILL 4 core 非法指令非法指令SIGABRT 6 core 中止中止SIGTSTP 20 stop 停止停止(挂起挂起)进

57、程进程SIGTTIN 21 stop 后台读取后台读取tty设备设备SIGTTOU 22 stop 后台写入后台写入tty设备设备5.4.3 信号定义71lKill -l 命令可以查看所有的信号命令可以查看所有的信号 kill -ll其它信号的定义及信息,参见帮助手册第其它信号的定义及信息,参见帮助手册第7部分的信号内容。部分的信号内容。(“man 7 signal”)l终端信号终端信号 向前台进程发送信号的最简单的方法是按下键盘的向前台进程发送信号的最简单的方法是按下键盘的ctrl-c键键或或ctrl-z键。键。(1)当终端识别出当终端识别出ctrl-c时,它向当前前台作业中的所有进程发时,

58、它向当前前台作业中的所有进程发送一个送一个SIGINT信号。信号。(2)与此类似,与此类似,ctrl-z使终端向当前前台作业中的所有进程发送使终端向当前前台作业中的所有进程发送一个一个SIGTSTP信号。信号。默认情况下,默认情况下,SIGINT 信号终止进程,信号终止进程,SIGTSTP信号挂起进程。信号挂起进程。5.4.3 信号定义72l函数定义:函数定义:unsinged int alarm(unsigned int count)lalarm()指示内核在经过指示内核在经过count指定的秒数后指定的秒数后发送发送SIGALARM信号信号(编号编号14)给调用进程。给调用进程。如果已经设

59、置了闹钟,覆盖已设置的闹钟。如果已经设置了闹钟,覆盖已设置的闹钟。如果如果count为为0,表示取消以前的闹钟。,表示取消以前的闹钟。l返回值为发送闹钟信号前所剩余的秒数返回值为发送闹钟信号前所剩余的秒数lSIGALARM的默认处理程序显示信息的默认处理程序显示信息“Alarm clock”并终止进程。并终止进程。5.4.4 信号的工作过程 -请求闹钟信号请求闹钟信号alarm()73l例:例:/*alarm.c*/#include main () alarm (3); /* Schedule an alarm signal in three seconds */ printf (Loopin

60、g forever.n); while (1); printf (This line should never be executedn);执行:执行: $./alarm Looping forever Alarm clock $5.4.4 信号的工作过程 -请求闹钟信号请求闹钟信号alarm()74l除了以默认方式处理信号,还可以使用除了以默认方式处理信号,还可以使用signal()系统调用覆盖默认系统调用覆盖默认的操作。的操作。lvoid (*signal (int sigCode, void (*func) (int) ) (int) signal允许进程指定在收到特定信号时所执行的操作

61、。参数允许进程指定在收到特定信号时所执行的操作。参数sigCode指定要重新编程的信号值,指定要重新编程的信号值,func的取值可以是如下几种:的取值可以是如下几种: (1)SIG_IGN 表示指定的信号应该被忽略并放弃表示指定的信号应该被忽略并放弃 (2) SIG_DEL 表示应该使用内核默认的处理程序表示应该使用内核默认的处理程序 (3)用户定义函数的地址用户定义函数的地址 表示当收到指定信号时应执行这个函数表示当收到指定信号时应执行这个函数 lint pause(void)pause挂起调用进程,当调用进程收到信号时返回。常用于高效地等待挂起调用进程,当调用进程收到信号时返回。常用于高效

62、地等待闹钟信号。不返回任何有用信息。闹钟信号。不返回任何有用信息。5.4.4 信号的工作过程 -请求闹钟信号请求闹钟信号alarm()75例:修改后的闹钟信号处理例程例:修改后的闹钟信号处理例程/*handler.c*/#include #include int alarmFlag = 0; /* Global alarm flag */void alarmHandler (); /* Forward declaration of alarm handler */main () signal (SIGALRM, alarmHandler); /* Install signal handler

63、*/ alarm (3); /* Schedule an alarm signal in three seconds */ printf (Looping.n); while (!alarmFlag) /* Loop until flag set */ pause (); /* Wait for a signal */ printf (Loop ends due to alarm signaln);void alarmHandler () printf (An alarm clock signal was receivedn); alarmFlag = 1;76说明:说明: 1 SIGKILL

64、和和SIGSTP信号不能被重新编程信号不能被重新编程 2 子进程在子进程在fork()执行期间继承父进程的信号执行期间继承父进程的信号设置。当进程执行设置。当进程执行exec()时,以前被除忽略的信时,以前被除忽略的信号仍然被忽略,但安装的处理程序设回为默认的号仍然被忽略,但安装的处理程序设回为默认的处理程序。处理程序。 3 除了除了SIGCHLD外,信号不进行堆栈。即,外,信号不进行堆栈。即,如果一个进程正在休眠,有三个完全相同的信号如果一个进程正在休眠,有三个完全相同的信号发送给它,实际处理的只有一个信号。发送给它,实际处理的只有一个信号。 4 signal调用成功返回和调用成功返回和si

65、gCode关联的前一关联的前一个个func值,否则返回值,否则返回-1。5.4.4 信号的工作过程 -请求闹钟信号请求闹钟信号alarm()77例:例:/*critical.c*/#include #include main () void (*oldHandler) (); /* To hold old handler value */ printf (I can be Control-Cedn); sleep (3); oldHandler = signal (SIGINT, SIG_IGN); /* Ignore Control-C */ printf (Im protected fro

66、m Control-C nown); sleep (3); signal (SIGINT, oldHandler); /* Restore old handler */ printf (I can be Control-Ced againn); sleep (3); printf (Bye!n);5.4.5 信号的处理 -保护关键代码,束缚中断处理程序保护关键代码,束缚中断处理程序78说明:说明: 1 通用通用signal系统调用忽略系统调用忽略SIGINT(即(即ctrl-c),可以用来保护关键代码不受),可以用来保护关键代码不受ctrl-c和其他和其他类似信号的影响。类似信号的影响。 2

67、保存处理程序的前一个值,当执行关键代保存处理程序的前一个值,当执行关键代码之后,又恢复原来的处理程序。码之后,又恢复原来的处理程序。5.4.5 信号的处理 -保护关键代码,束缚中断处理程序保护关键代码,束缚中断处理程序79 一个进程可以使用系统调用一个进程可以使用系统调用kill()给其他进程发送信号。()给其他进程发送信号。kill发送的很多信号并不终止进程,叫它发送的很多信号并不终止进程,叫它kill是因为最初设计是因为最初设计UNIX时,信号的主要用途就是终止进程。时,信号的主要用途就是终止进程。l函数定义函数定义: int kill (pid_t pid, int sigCode)lk

68、ill()发送值为发送值为sigCode的信号给的信号给PID为为pid的进程。只要至的进程。只要至少满足下面的一个条件,少满足下面的一个条件,kill()调用就能成功:调用就能成功: (1)发送进程和接收进程具有相同的所有者发送进程和接收进程具有相同的所有者 (2)发送进程的所有者是超级用户发送进程的所有者是超级用户l如果如果kill()至少成功发送一个信号,返回至少成功发送一个信号,返回0,否则返回,否则返回-15.4.6 发送信号发送信号kill()80lkill()的运行方式的运行方式 (1)如果如果pid是是0,信号发送给发送者的进程组,信号发送给发送者的进程组中的所有进程中的所有进

69、程 (2)如果如果pid是是-1并且发送者由超级用户拥有,并且发送者由超级用户拥有,信号发送给所有进程,也包括发送进程信号发送给所有进程,也包括发送进程 (3)如果如果如果如果pid是是-1并且发送者不是由超级用并且发送者不是由超级用户拥有,信号发送给发送进程所有者拥有的全户拥有,信号发送给发送进程所有者拥有的全部进程,不包括发送进程部进程,不包括发送进程 (4)如果如果pid是负值但不是是负值但不是-1,信号发送给进程,信号发送给进程组中的所有进程。组中的所有进程。5.4.6 发送信号发送信号kill()81n发送信号发送信号kill()-子进程的死亡子进程的死亡l当父进程的一个子进程终止时

70、,子进程给父进程当父进程的一个子进程终止时,子进程给父进程发送一个发送一个SIGCHLD信号。父进程的处理程序经常信号。父进程的处理程序经常执行一个执行一个wait()调用接受子进程的终止码,让子进调用接受子进程的终止码,让子进程脱离僵尸状态。程脱离僵尸状态。l父进程也可以选择忽略父进程也可以选择忽略SIGCHLD信号,在这种情信号,在这种情况下,子进程自动脱离僵尸状态。况下,子进程自动脱离僵尸状态。5.4.6 发送信号发送信号kill()82例:例:SIGCHLD信号的处理信号的处理-子进程的死亡子进程的死亡 程序执行步骤:程序执行步骤: 1)父进程安装一个)父进程安装一个SIGCHLD处理

71、程序,在子处理程序,在子进程终止时执行。进程终止时执行。 2)父进程创建一个子进程。)父进程创建一个子进程。 3)父进程休眠指定的秒数。当它被唤醒时,它)父进程休眠指定的秒数。当它被唤醒时,它给子进程发送一个给子进程发送一个SIGINT信号杀死子进程。信号杀死子进程。 4)如果子进程在父进程结束休眠之前终止,执)如果子进程在父进程结束休眠之前终止,执行父进程的行父进程的SIGCHLD处理程序,使父进程立即终处理程序,使父进程立即终止。止。5.4.6 发送信号发送信号kill()83例:例:SIGCHLD信号的处理信号的处理-子进程的死亡子进程的死亡 /*limit.c*/#include #i

72、nclude int delay;void childHandler ();/*/main (argc, argv)int argc;char* argv; int pid; signal (SIGCHLD, childHandler); /* Install death-of-child handler */ pid = fork (); /* Duplicate */ if (pid = 0) /* Child */ execvp (argv2, &argv2); /* Execute command */ perror (limit); /* Should never execute *

73、/ 84例:例:SIGCHLD信号的处理信号的处理-子进程的死亡子进程的死亡 (续)(续)else /* Parent */ sscanf (argv1, %d, &delay); /* Read delay from command line */ sleep (delay); /* Sleep for the specified number of seconds */ printf (Child %d exceeded limit and is being killedn, pid); kill (pid, SIGINT); /* Kill the child */ /*/void ch

74、ildHandler () /* Executed if the child dies before the parent */ int childPid, childStatus; childPid = wait (&childStatus); /* Accept childs termination code */ printf (Child %d terminated within %d secondsn, childPid, delay); exit (/* EXITSUCCESS */ 0);85SIGSTOP和和SIGCONT信号信号分别分别挂起和恢复挂起和恢复进程。进程。Linu

75、x shell使用它们支持作业控制,使用它们支持作业控制,实现实现stop, bg和和fg之类的命令。之类的命令。 5.4.7挂起和恢复进程挂起和恢复进程-SIGSTOP和和SIGCONT信号信号86例:主程序创建两个子进程,它们都进入无限循环并每秒显示一个例:主程序创建两个子进程,它们都进入无限循环并每秒显示一个消息。主程序等待消息。主程序等待3秒钟,然后挂起第一个子进程。第二个继续秒钟,然后挂起第一个子进程。第二个继续执行。在又一个执行。在又一个3秒钟之后,父进程重新启动第一个子进程,再秒钟之后,父进程重新启动第一个子进程,再3秒钟后,终止两个进程。秒钟后,终止两个进程。/*pulse.c

76、*/#include #include main () int pid1; int pid2; pid1 = fork (); if (pid1 = 0) /* First child */ while (1) /* Infinite loop */ 5.4.7挂起和恢复进程挂起和恢复进程-SIGSTOP和和SIGCONT信号信号87 printf (pid1 is aliven); sleep (1); pid2 = fork (); /* Second child */ if (pid2 = 0) while (1) /* Infinite loop */ printf (pid2 is

77、aliven); sleep (1); sleep (3); kill (pid1, SIGSTOP); /* Suspend first child */ sleep (3); kill (pid1, SIGCONT); /* Resume first child */ sleep (3); kill (pid1, SIGINT); /* Kill first child */ kill (pid2, SIGINT); /* Kill second child */88 当在当在shell中执行一个程序,创建了几个子进程时,键盘中执行一个程序,创建了几个子进程时,键盘的一个的一个ctrl-c

78、命令就能终止程序和它的子进程,并返回到命令就能终止程序和它的子进程,并返回到shell。这种特性依赖于以下几点:。这种特性依赖于以下几点:l每个进程除了有一个唯一的每个进程除了有一个唯一的PID外,它还是一个外,它还是一个“进程组进程组”的成员。的成员。一个进程组可以有好几个成员。当进程复制时,一个进程组可以有好几个成员。当进程复制时,子进程从它的父进程继承进程组。进程也可以使用子进程从它的父进程继承进程组。进程也可以使用setpgid()设置自己的进程组。当进程执行()设置自己的进程组。当进程执行exec()时,所属进程时,所属进程组不会发生改变。组不会发生改变。l每个进程可以有一个关联的控

79、制终端。每个进程可以有一个关联的控制终端。这一般是进程启动这一般是进程启动的终端。当进程复制时,子进程从父进程继承终端。当进的终端。当进程复制时,子进程从父进程继承终端。当进程执行程执行exec()时,它的控制终端不变。时,它的控制终端不变。l每个终端可以关联一个控制进程。每个终端可以关联一个控制进程。当检测到元字符(如当检测到元字符(如ctrl-c)时,终端发送适当的信号给它的控制进程所在的进)时,终端发送适当的信号给它的控制进程所在的进程组的所有进程。程组的所有进程。5.4.8进程组和控制终端进程组和控制终端89l如果一个进程试图读取控制终端,而且它不是终端的控制进程所如果一个进程试图读取

80、控制终端,而且它不是终端的控制进程所在的进程组的成员,该进程会被发送一个在的进程组的成员,该进程会被发送一个SIGTTIN信号,挂起进信号,挂起进程。程。Shell的进程组特性:的进程组特性:l当启动互动的当启动互动的shell时,它是一个终端的控制进程,并且以此终时,它是一个终端的控制进程,并且以此终端作为它的控制终端。端作为它的控制终端。l当当shell执行前台进程时,子执行前台进程时,子shell在执行命令前把自己放入一个在执行命令前把自己放入一个不同的进程组中,并取得终端的控制权。因此,从终端生成的不同的进程组中,并取得终端的控制权。因此,从终端生成的任何信号都传递给前台命令,而不是原

81、来的父任何信号都传递给前台命令,而不是原来的父shell。当前台命。当前台命令终止时,原来的父令终止时,原来的父shell取回终端的控制权。取回终端的控制权。l当当shell执行后台进程时,子执行后台进程时,子shell在执行命令前把自己放入一个在执行命令前把自己放入一个不同的进程组中,但不取得终端的控制权。从终端生成的任何不同的进程组中,但不取得终端的控制权。从终端生成的任何信号继续传递给这个信号继续传递给这个shell。如果该后台进程试图读取控制终端,。如果该后台进程试图读取控制终端, SIGTTIN信号将其挂起。信号将其挂起。5.4.8进程组和控制终端进程组和控制终端90进程组145进程

82、组230进程组171148145150174171176231230233进程组成部分145、171、230中的进程共享同一个控制终端信号假设进程假设进程145和和进程进程230是后台是后台作业的领导进程,作业的领导进程,进程进程171是前台是前台作业的领导进程作业的领导进程5.4.8进程组和控制终端进程组和控制终端91例:终端将信号传递给它的控制进程所在的进程组中的所有进程。例:终端将信号传递给它的控制进程所在的进程组中的所有进程。由于子进程继承了父进程的进程组,父进程和子进程都能捕获由于子进程继承了父进程的进程组,父进程和子进程都能捕获SIGINT信号。信号。/*pgrp1.c*/#inc

83、lude #include void sigintHandler ();main () signal (SIGINT, sigintHandler); /* Handle Control-C */ if (fork () = 0) /*child*/ printf (Child PID %d PGRP %d waitsn, getpid (),getpgid (0); else /*parent*/ printf (Parent PID %d PGRP %d waitsn, getpid (), getpgid (0); pause (); /* Wait for asignal */void

84、 sigintHandler () printf (Process %d got a SIGINTn,getpid ();92执行:执行:$./pgrp1 parent PID 24583 PGRP 24583 waits child PID 24584 PGRP 24584 waits c 按下按下ctrl-c process 24584 got a SIGINT process 24583 got a SIGINT93例:如果进程把自己放入不同的进程组,它和终端的控制进程不再关例:如果进程把自己放入不同的进程组,它和终端的控制进程不再关联,将不会从终端接收信号。联,将不会从终端接收信号。/

85、*pgrp2.c*/#include #include void sigintHandler ();main() int i; signal (SIGINT, sigintHandler); /* Install signal handler */ if (fork () = 0) setpgid (0, getpid (); /* Place child in its own process group */ printf (Process PID %d PGRP %d waitsn, getpid (), getpgid (0); for (i = 1; i = 3; i+) /* Loo

86、p three times */ printf (Process %d is aliven, getpid (); sleep(1); 94例例 :(续):(续)void sigintHandler () printf (Process %d got a SIGINTn, getpid (); exit (1);95如果进程把在脱离终端的控制进程之后,试图读取控制终端,它将会如果进程把在脱离终端的控制进程之后,试图读取控制终端,它将会收到一个收到一个SIGTTIN信号,挂起该进程。信号,挂起该进程。例例 :系统有一个默认的:系统有一个默认的SIGTTIN的处理程序,该例中重写了该程序。的处理程

87、序,该例中重写了该程序。/*pgrp3.c*/#include #include #include void sigttinHandler ();main () int status; char str 100; if (fork () = 0) /* Child */ signal (SIGTTIN, sigttinHandler); /* Install handler */ setpgid (0, getpid (); /* Place myself in a new process group */ printf (Enter a string: ); scanf (%s, str);

88、 /* Try to read from control terminal */ printf (You entered %sn, str); 96例例 : (续)(续)else /* Parent */ wait (&status); /* Wait for child to terminate */ void sigttinHandler () printf (Attempted inappropriate read from control terminaln); exit (1);97n概述概述 进程间通信(进程间通信(IPC)是描述两个进程如何彼此交是描述两个进程如何彼此交换信息的一

89、个通用术语。一般地,通信的两个进程可换信息的一个通用术语。一般地,通信的两个进程可以运行于同一台机器也可以运行于不同的机器,但有以运行于同一台机器也可以运行于不同的机器,但有些些IPC机制只支持本地使用(如信号和管道)。机制只支持本地使用(如信号和管道)。 进程间通信可以是数据的交换,两个或多个进程进程间通信可以是数据的交换,两个或多个进程合作处理数据或同步信息,以帮助两个彼此独立但互合作处理数据或同步信息,以帮助两个彼此独立但互相关联的进程调度工作,避免重叠。相关联的进程调度工作,避免重叠。 进程间通信的主要方式:进程间通信的主要方式: (1) 信号信号 (2)管道管道 (3)套接字套接字5

90、.5进程间通信(IPC)98 管道是允许两个或多个进程彼此发送信息的进程间管道是允许两个或多个进程彼此发送信息的进程间通信机制。常用于通信机制。常用于shell中,连接一个程序的标准输出和中,连接一个程序的标准输出和另一个程序的标准输入。另一个程序的标准输入。例如:例如:who | wc l who程序为每位用户生成一行输出,这个输出通过程序为每位用户生成一行输出,这个输出通过管道传递给管道传递给wc程序,程序,wc -l 命令输出它的输入的总行数。命令输出它的输入的总行数。因此,该命令行的执行结果是统计出系统用户数量。因此,该命令行的执行结果是统计出系统用户数量。 who的输出通过管道流入的

91、输出通过管道流入wc管道whowc5.5.1管道99管道的读进程和写进程是同时执行的:管道的读进程和写进程是同时执行的:管道自动缓冲写管道自动缓冲写进程的输出,如果管道太满,管道挂起写进程;如果管进程的输出,如果管道太满,管道挂起写进程;如果管道清空了,管道挂起读进程,直到写进程再生成一些输道清空了,管道挂起读进程,直到写进程再生成一些输出。出。Linux 提供两种管道:提供两种管道: 匿名管道(未命名管道)匿名管道(未命名管道) -单向管道单向管道 命名管道命名管道 -双向管道双向管道 shell使用使用未命名管道未命名管道在进程之间传递数据。在进程之间传递数据。5.5.1管道100 未命名

92、管道未命名管道是自动缓冲其输入的一个单向通信链路是自动缓冲其输入的一个单向通信链路。可以用。可以用pipe()系统调用来创建系统调用来创建. 管道的每一端都有一个关联的文件描述符。可以用管道的每一端都有一个关联的文件描述符。可以用write()写管写管道的写端,用道的写端,用read()读管道的读端。读管道的读端。 当一个进程使用完管道的文件描述符后,它应该使用当一个进程使用完管道的文件描述符后,它应该使用close()关闭该文件描述符。关闭该文件描述符。 函数定义:函数定义:int pipe(int fd2)调用成功返回调用成功返回0,否则返回,否则返回-1。未命名管道写端读端5.5.2未命

93、名管道pipe()101函数定义:函数定义:int pipe(int fd2)lpipe()创建一个未命名管道并返回两个文件描述符:创建一个未命名管道并返回两个文件描述符:和读端关联的文件描述符保存在和读端关联的文件描述符保存在fd0中,和写端关联中,和写端关联的文件描述符保存在的文件描述符保存在fd1中。中。例:例: int fd2; pipe fd;执行代码将创建以下结构:执行代码将创建以下结构:未命名管道写端读端fd0fd15.5.2未命名管道pipe()102l读管道进程的有关规则:读管道进程的有关规则: (1)如果进程读一个写端关闭的管道,如果进程读一个写端关闭的管道,read()返

94、回返回0,表示输入结束。,表示输入结束。 (2)如果进程读一个写端仍打开的空管道,该如果进程读一个写端仍打开的空管道,该进程休眠,直到管道中有新的输入。进程休眠,直到管道中有新的输入。 (3)如果进程试图从管道中读多于现有量的字如果进程试图从管道中读多于现有量的字节,返回当前的所有内容,节,返回当前的所有内容,read()返回实际读返回实际读取的字节。取的字节。5.5.2未命名管道pipe()103l写管道进程的有关规则:写管道进程的有关规则: (1)如果进程写一个读端关闭的管道,写操作如果进程写一个读端关闭的管道,写操作失败。内核将一个失败。内核将一个SIGPIPE信号发送给写进程。信号发送

95、给写进程。此信号的缺省操作是终止进程。此信号的缺省操作是终止进程。 (2)如果进程写入管道的字节数少于管道能保如果进程写入管道的字节数少于管道能保存的数,存的数,write()保证是原子操作:即,写进程保证是原子操作:即,写进程将完成它的系统调用,不会被另一个进程抢占。将完成它的系统调用,不会被另一个进程抢占。5.5.2未命名管道pipe()104l未命名管道的访问未命名管道的访问是通过是通过文件描述符机制文件描述符机制进行的,一般只进行的,一般只有创建管道的进程和它的后代可以使用该管道。有创建管道的进程和它的后代可以使用该管道。l未命名管道未命名管道通常用于父进程和子进程之间的通信。通常用于

96、父进程和子进程之间的通信。l未命名管道的典型事件序列:未命名管道的典型事件序列: 1)父进程调用)父进程调用pipe()创建未命名管道创建未命名管道 2)父进程创建子进程)父进程创建子进程 3)写进程关闭管道的读端,指定的读进程关闭管道的写)写进程关闭管道的读端,指定的读进程关闭管道的写端端 4)进程使用)进程使用write()和和read()两个系统调用进行通信两个系统调用进行通信 5)每个进程在使用完后关闭激活的文件描述符)每个进程在使用完后关闭激活的文件描述符 5.5.2未命名管道pipe()105例例1:父进程通过管道读取子进程的一个消息:父进程通过管道读取子进程的一个消息 /*tal

97、k.c*/#include #define READ 0 /* The index of the read end of the pipe */#define WRITE 1 /* The index of the write end of the pipe */char* phrase = Stuff this in your pipe and smoke it;main () int fd 2, bytesRead; char message 100; /* Parent process message buffer */ pipe (fd); /*Create an unnamed pi

98、pe */ if (fork () = 0) /* Child, writer */ close(fdREAD); /* Close unused end */ write (fdWRITE, phrase, strlen (phrase) + 1); /* include NULL*/ close (fdWRITE); /* Close used end*/ 106例:父进程通过管道读取子进程的一个消息例:父进程通过管道读取子进程的一个消息(续)(续)else /* Parent, reader*/ close (fdWRITE); /* Close unused end */ bytesR

99、ead = read (fdREAD, message, 100); printf (Read %d bytes: %sn, bytesRead, message); /* Send */ close (fdREAD); /* Close used end */ 5.5.2未命名管道pipe()107说明:说明: 1、双向通信要使用两个未命名管道、双向通信要使用两个未命名管道 2、子进程在消息中包括了短语的、子进程在消息中包括了短语的NULL终终止符,以使父进程能显示这个消息。当写进程止符,以使父进程能显示这个消息。当写进程向管道发送一个以上的变长消息时,它必须使向管道发送一个以上的变长消息时

100、,它必须使用协议向读进程指明消息的结束。方法如下:用协议向读进程指明消息的结束。方法如下:l在发送消息本身之前,先发送消息的字节长度在发送消息本身之前,先发送消息的字节长度l用特殊字符如:换行符或用特殊字符如:换行符或NULL字符结束消息字符结束消息5.5.2未命名管道pipe()108例例2:linux shell使用未命名管道构建管道线。与重定向机制类似,即使用未命名管道构建管道线。与重定向机制类似,即把一个的进程标准输出连接到另一个进程的标准输入。把一个的进程标准输出连接到另一个进程的标准输入。/*connect.c*/#include #define READ 0#define WRI

101、TE 1main (argc, argv)int argc;char* argv ; int fd 2; pipe (fd); /* Create an unamed pipe */ if (fork () != 0) /* Parent, writer */ close (fdREAD); /* Close unused end */ dup2 (fdWRITE, 1); /* Duplicate used end to stdout */ close (fdWRITE); /* Close original used end */ execlp (argv1, argv1, NULL);

102、/* Execute writer program */ perror (connect); /* Should never execute */ 109例例2:(续续)else /* Child, reader */ close (fdWRITE); /* Close unused end */ dup2 (fdREAD, 0); /* Duplicate used end to stdin */ close (fdREAD); /* Close original used end */ execlp (argv2, argv2, NULL); /* Execute reader progr

103、am */ perror (connect); /* Should never execute */ 运行运行: $who 运行运行who命令命令 glass pts/1 Feb 15 18:45 $./connect who wc 管道连接管道连接who和和wc命令命令 1 6 42 统计结果统计结果 $5.5.2未命名管道pipe()110l命名管道命名管道(在(在linux中叫做中叫做FIFO)不象未命名管道)不象未命名管道有那么多的限制,它具有以下优势:有那么多的限制,它具有以下优势: (1)它们有一个存在于文件系统中的名称)它们有一个存在于文件系统中的名称 (2)它们可以被不相关的进

104、程使用)它们可以被不相关的进程使用 (3)在显式删除之前它们一直存在)在显式删除之前它们一直存在l所有适用于未命名管道的规则也适用于命名管道。所有适用于未命名管道的规则也适用于命名管道。l因为命名管道在文件系统中以因为命名管道在文件系统中以特殊文件的形式特殊文件的形式存在,存在,使用命名管道的进程不必像使用未命名管道时必须使用命名管道的进程不必像使用未命名管道时必须具有相同的祖先进程。具有相同的祖先进程。5.5.3 命名管道111l创建命名管道(创建命名管道(FIFO)的方法有两个:)的方法有两个: 1、使用、使用linux实用程序实用程序mkfifo 2、使用系统调用、使用系统调用mkfif

105、o()lUNIX中使用中使用mknod和和mknod()创建命名管道,创建命名管道,linux中这个方法仍然可行,但中这个方法仍然可行,但mkfifo和和mkfifo () 是首选方法。是首选方法。例:例:mknod -m ug+rw myPipe p -m设置权限,设置权限,p指定类型是特殊文件(即管道),指定类型是特殊文件(即管道),myPipe是创建的管道名。是创建的管道名。5.5.3 命名管道112例:在例:在shell中创建管道,设置权限中创建管道,设置权限$mkfifo myPipe$chmod ug+rw myPipe$ls -l myPipeprw-rw- 1 glass cs

106、 . myPipe$说明:说明:1、ls列表中,列表中,p表示文件类型为命名管道表示文件类型为命名管道 2、使用、使用chmod设置命名管道的访问权限,便于其他用设置命名管道的访问权限,便于其他用户访问你创建的管道。户访问你创建的管道。 3、以上操作等价于、以上操作等价于C代码代码mkfifo(“myPipe”,0660); 4、不论以何种方式创建命名管道,结果都是在文件系、不论以何种方式创建命名管道,结果都是在文件系统中创建了一个特殊文件。统中创建了一个特殊文件。5.5.3 命名管道113 5、一旦用、一旦用open ()打开命名管道,打开命名管道,write()在在FIFO队列的头部添加数

107、据,队列的头部添加数据,read()从从FIFO队队列的末尾删除数据。当进程使用完管道时,应列的末尾删除数据。当进程使用完管道时,应使用使用close()关闭管道。不再需要命名管道时,关闭管道。不再需要命名管道时,应使用应使用unlink()从文件系统删除它。从文件系统删除它。 6、和未命名管道一样,命名管道只用于单向、和未命名管道一样,命名管道只用于单向链路。写进程应以只写模式打开管道,读进程链路。写进程应以只写模式打开管道,读进程应以只读模式打开管道。应以只读模式打开管道。5.5.3 命名管道114n使用命名管道的特殊规则:使用命名管道的特殊规则: 1、如果一个进程以只读模式打开命名管道,

108、并且目前没、如果一个进程以只读模式打开命名管道,并且目前没有进程打开管道进行写操作,读进程将一直等到有进程打有进程打开管道进行写操作,读进程将一直等到有进程打开管道进行写操作,除非设置了开管道进行写操作,除非设置了O_NONBLOCK或或O_NDELAY。 2、如果一个进程以只写模式打开命名管道,并且目前没、如果一个进程以只写模式打开命名管道,并且目前没有进程打开管道进行读操作,写进程有进程打开管道进行读操作,写进程 将一直等待到有进程将一直等待到有进程打开管道进行读取操作。除非设置了打开管道进行读取操作。除非设置了O_NONBLOCK或或O_NDELAY。 3、命名管道不能跨管道使用。、命名

109、管道不能跨管道使用。5.5.3 命名管道115例例2:两个程序之间建立命名管道。工作方式:两个程序之间建立命名管道。工作方式: 执行一个读进程,它创建一个叫做执行一个读进程,它创建一个叫做“aPipe”的命名管道。然后它从管道读并显示以的命名管道。然后它从管道读并显示以NULL结结束的行,直到所有管道被所有的写进程关闭。束的行,直到所有管道被所有的写进程关闭。 执行一个或几个写进程,每个进程都执行下面执行一个或几个写进程,每个进程都执行下面的操作:打开这个叫做的操作:打开这个叫做“aPipe”的命名管道,的命名管道,向管道发送三个消息。如果写进程试图打开管道向管道发送三个消息。如果写进程试图打

110、开管道时管道不存在,写进程则每时管道不存在,写进程则每1秒钟重试一次,直秒钟重试一次,直到成功。当一个写进程的所有消息都发送后,该到成功。当一个写进程的所有消息都发送后,该写进程关闭管道并退出。写进程关闭管道并退出。5.5.3 命名管道116/*reader.c*/#include #include #include /*/main () int fd; char str100; mkfifo (aPipe, 0660); /* Create named pipe */ fd = open (aPipe, O_RDONLY); /* Open it for reading */ while (

111、readLine (fd, str) /* Display received messages */ printf (%sn, str); close (fd); /* Close pipe */*/117(续续1)readLine (fd, str)int fd;char* str;/* Read a single NULL-terminated line into str from fd */* Return 0 when the end-of-input is reached and 1 otherwise */ int n; do /* Read characters until NU

112、LL or end-of-input */ n = read (fd, str, 1); /* Read one character */ while (n 0 & *str+ != 0); return (n 0); /* Return false if end-of-input */5.5.3 命名管道118(续续2)/* writer */#include #include /*/main () int fd, messageLen, i; char message 100; /* Prepare message */ sprintf (message, Hello from PID %

113、d, getpid (); messageLen = strlen (message) + 1; do /* Keep trying to open the file until successful */ fd = open (aPipe, O_WRONLY); /* Open named pipe for writing */ if (fd = -1) sleep (1); /* Try again in 1 second */ 5.5.3 命名管道119(续续3)while (fd = -1); for (i = 1; i = 3; i+) /* Send three messages */ write (fd, message, messageLen); /* Write message down pipe */ sleep (3); /* Pause a while */ close (fd); /* Close pipe descriptor */运行:运行:$./reader & ./writer & ./writer & 运行运行1个个reader,两个,两个writer。 5.5.3 命名管道120

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 办公文档 > 工作计划

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号