• 1. Linux环境高级编程
  • 2. 第四讲 进程
  • 3. 进程进程环境(第七章) 进程控制(第八章) 进程关系(第九章)
  • 4. 进程进程环境(第七章) 进程控制(第八章) 进程关系(第九章)
  • 5. 进程环境main函数和命令行参数 进程的启动和终止 环境表和环境变量 存储空间布局 存储器分配 setjmp和longjmp函数
  • 6. 进程环境main函数和命令行参数 进程的启动和终止 环境表和环境变量 存储空间布局 存储器分配 setjmp和longjmp函数
  • 7. main函数和命令行参数通常,C程序总是从main函数开始执行 main函数原型 int main(int argc, char *argv[]); 参数 argc:命令行参数的个数 argv:指向命令行参数的各个指针所构成的数组 命令行 $./test Hello World程序中怎么样 获得命令行参数
  • 8. main函数和命令行参数参数agrv:argv[1]argv[0]argv[2]...........argv[argc-1]argv
  • 9. main函数和命令行参数程序演示(4.1)
  • 10. 进程环境main函数和命令行参数 进程的启动和终止 环境表和环境变量 存储空间布局 存储器分配 setjmp和longjmp函数
  • 11. 进程的终止8种方式使进程终止 正常终止 从main返回 调用exit 调用_exit或_Exit 最后一个线程从其启动例程返回 最后一个线程调用pthread_exit 异常终止
  • 12. 进程的终止 异常终止 调用abort 接到一个信号并终止 最后一个线程对取消请求做出响应 有关信号、线程的终止方式,后面讨论或者自学
  • 13. 进程终止三个终止函数:exit、_Exit、_exit 函数原型: void exit(int status); void _Exit(int status); void _exit(int status); exit函数执行一个标准I/O库的清理关闭操作(为所有打开流调用fclose函数)后,进入内核 _Exit、_exit函数立即进入内核
  • 14. 进程终止(4.2)exit等函数的参数 status:进程的终止状态 程序演示查看进程终止状态 echo $?
  • 15. atexit函数当进程终止时,程序可能需要进行一些自身的清理工作,如资源释放等等 atexit函数提供了进行这样工作的机会 它允许用户注册若干终止处理函数,当进程终止时,这些终止处理函数将会被自动调用
  • 16. atexit函数用于注册用户提供的终止处理函数 函数原型 int atexit(void (*func)(void)); 参数 func:函数指针,返回值为void,无参 返回值 成功返回0,出错返回非0值
  • 17. atexit函数程序演示(4.3) 注意:先注册的函数,后被运行。 调用_exit函数并不会触发终止处理函数
  • 18. 进程的启动和终止Kernelexit func.用户函数main 函数C启动 例程终止处理函数终止处理函数标准I/O 清理函数_exit exit 不返回 execcallcallreturncallreturncallreturnUser process_exit_exit exit 不返回 exit 不返回call返回返回
  • 19. 进程环境main函数和命令行参数 进程的启动和终止 环境表和环境变量 存储空间布局 存储器分配 setjmp和longjmp函数
  • 20. 环境表和环境变量每个进程都会接收到一张环境表 通过environ找到环境表 extern char **environ; 环境字符串:name=value
  • 21. 环境表和环境变量访问环境变量的方法 直接使用environ 使用getenv和putenv等函数 getenv函数用于获取环境变量值 函数原型 char* getenv(const char *name); 返回与name关联的value的指针,若未找到则返回NULL 返回的指针是指向新分配的内存,还是环境表中存在的值?(程序4.4)
  • 22. 设置环境变量三种方法: putenv setenv unsetenv putenv函数将形式为name=value的字符串,放入环境表中;若name已经存在,则先删除其原来的定义。 函数原型: int putenv(char *str);
  • 23. 设置环境变量setenv函数原型: int setenv(const char* name, const char* value, int rewrite); setenv将环境变量name的值设置为value。 若name已经存在 rewrite != 0,则删除其原先的定义 rewrite == 0,则不删除其原先的定义
  • 24. 设置环境变量unsetenv函数用于删除某个环境变量 函数原型 int unsetenv(const char* name); 删除name的定义 疑问? 前两个设置环境变量的函数,都给出了自己的缓冲区存放环境变量。在环境表中是否直接使用这些缓冲区,还是环境表自己分配了缓冲区?(程序4.5)
  • 25. 进程环境main函数和命令行参数 进程的启动和终止 环境表和环境变量 存储空间布局 存储器分配 setjmp和longjmp函数
  • 26. C程序存储空间布局命令行参数和环境变量栈 堆未初始化的数据初始化的数据正文High addressLow address
  • 27. C程序存储空间布局命令行参数和环境变量栈 堆未初始化的数据初始化的数据正文High addressLow addressCPU执行的机器指令部分, 正文段通常是共享、只读的
  • 28. C程序存储空间布局命令行参数和环境变量栈 堆未初始化的数据初始化的数据正文High addressLow address包含了程序中需明确赋初值 的变量,如全局变量 int maxcount=99;
  • 29. C程序存储空间布局命令行参数和环境变量栈 堆未初始化的数据初始化的数据正文High addressLow address程序执行之前,将此段中的 数据初始化为0,如: long sum[1000];
  • 30. C程序存储空间布局命令行参数和环境变量栈 堆未初始化的数据初始化的数据正文High addressLow address 用于动态分配内存
  • 31. C程序存储空间布局命令行参数和环境变量栈 堆未初始化的数据初始化的数据正文High addressLow address 主要用于支撑函数调用 存放参数、局部变量等
  • 32. Windows95 进程地址空间32
  • 33. Windows2000 进程地址空间33
  • 34. Linux进程地址空间34
  • 35. Linux进程地址空间逻辑地址、线性地址、物理地址? 查看进程的地址空间(4.11) $cat /proc/进程ID/maps /proc目录中的文件并不是真正的磁盘文件,而是由内核虚拟出来的文件系统,当前系统中运行的每个进程在/proc下都有一个子目录,目录名就是进程的id,查看目录下的文件可以得到该进程的相关信息。 35
  • 36. 进程环境main函数和命令行参数 进程的启动和终止 环境表和环境变量 存储空间布局 存储器分配 setjmp和longjmp函数
  • 37. 内存分配三个用于内存分配的函数 malloc:分配指定字节数的存储区,此存储区中的初始值不确定 calloc:为指定数量指定长度的对象分配存储空间,该空间中的每一位都初始化为0 realloc:更改(增加或者减少)以前分配区的长度 C++中使用new
  • 38. 内存分配函数原型 void *malloc(size_t size); void *calloc(size_t nobj, size_t size); void *realloc(void *ptr, size_t newsize); 注意:三个函数返回的指针一定是适当对齐的 什么是对齐??? 例如:在一个特定的系统上,如果最严格的对齐要求是,double必须在8的倍数地址单元处开始,那么这三个函数返回的指针部分都应这样对齐
  • 39. 内存释放free函数用于释放已分配的内存,即将内存归还给堆 函数原型 void free(void *ptr); 分配了内存,但是没有释放,是内存泄露吗?
  • 40. 进程环境main函数和命令行参数 进程的启动和终止 环境表和环境变量 存储空间布局 存储器分配 setjmp和longjmp函数
  • 41. setjmp和longjmp函数void g(){} void f() { g(); } void main{ f(); }首先查看一个例子 在左侧的代码中,main函数调用了f函数,f函数中又调用了g函数 假设当g函数内部处理出错时,希望main函数能够感知到这一出错情况 如何实现?
  • 42. setjmp和longjmp函数void g(){} void f() { g(); } void main{ f(); }g函数内部处理出错,main函数能被通知到,可能的方法: 使用goto语句? g函数通过返回值通知f函数,f函数再通过返回值通知main函数?(考虑函数调用深度) 使用setjmp和longjmp 使用C++异常处理
  • 43. setjmp和longjmp函数setjmp和longjmp函数实现函数之间的跳转 函数原型 int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val); setjmp函数用于设置跳转的目的位置 longjmp函数进行跳转
  • 44. setjmp和longjmp函数参数与返回值: env:保留了需要返回的位置的堆栈情况 setjmp的返回值:直接调用该函数,则返回0;若由longjmp的调用,导致setjmp被调用,则返回val(longjmp的第二个参数) 根据演示程序,说明两个函数(程序演示4.6)
  • 45. 各类变量的情况当调用longjmp函数后,在main中的各类变量的值是否改变回原来的值呢? 程序演示(4.7) g++ -o test test.cpp g++ -O -o test test.cpp 全局变量、静态变量、易失变量不受优化的影响 在非优化的版本,所有变量都存储在内存中 在优化的版本,自动变量和寄存器变量存储在寄存器中
  • 46. 变量回滚  typedef struct           {                   unsigned j_sp;  // 堆栈指针寄存器                   unsigned j_ss;  // 堆栈段                   unsigned j_flag;  // 标志寄存器                   unsigned j_cs;  // 代码段                   unsigned j_ip;  // 指令指针寄存器                   unsigned j_bp; // 基址指针                   unsigned j_di;  // 目的指针                   unsigned j_es; // 附加段                   unsigned j_si;  // 源变址                   unsigned j_ds; // 数据段           } jmp_buf; 46
  • 47. 变量回滚问题保证局部变量在longjmp过程中一直保存它的值的方法:把它声明为volatile变量。(适合那些在setjmp执行和longjmp返回之间会改变的变量) 存放在内存中的变量,将具有调用longjmp时的值,而在CPU和浮点寄存器中的变量则恢复为调用setjmp函数时的值 优化编译时,register和auto变量都存放在寄存器中,而volatile变量仍存放在内存47
  • 48. 变量回滚问题volatile变量:一般在多线程中使用的比较多 例如有一个int x,有两个线程都要对其读写 有些编译器或CPU会将x保存在寄存器中,读的时候直接读取寄存器中的内容,而不是真实的x在内存中的内容 线程1,对x进行加1操作,此时内存中x的值为2 线程2想读x,结果从寄存器中读出1 给变量加上volatile,指示程序每次读写变量都必须从内存中读取,不要进行缓存(寄存器)48
  • 49. 自动变量的潜在问题#include #define DATAFILE "datafile" FILE * open_data(void) { FILE *fp; char databuf[BUFSIZ]; /* setvbuf makes this the stdio buffer */ if ( (fp = fopen(DATAFILE, "r")) == NULL) return(NULL); if (setvbuf(fp, databuf, BUFSIZ, _IOLBF) != 0) return(NULL); return(fp); /* error */ } 问题?
  • 50. 自动变量的潜在问题问题: open_data函数返回后,它在栈上所使用的空间将由下一个被调用函数所占用 但是标准I/O库仍使用位于栈上的databuf缓冲区 存在冲突和混乱 解决办法: 使用全局存储空间 使用静态存储空间 从堆中分配
  • 51. setjmp和longjmp函数void g(){} void f() { g(); } void main{ f(); }g函数内部处理出错,main函数能被通知到,可能的方法: 使用goto语句? g函数通过返回值通知f函数,f函数再通过返回值通知main函数?(考虑函数调用深度) 使用setjmp和longjmp 使用C++异常处理(4.12)
  • 52. Resource limits Every process has a set of resource limits, some of which can be quire and changed by following functions. #include #include int getrlimit ( int resource, struct rlimit *rlptr ); int setrlimit ( int resource, const struct rlimit *rlptr ); struct rlimit { rlim_t rlim_cur; /* soft limit: current limit */ rlim_t rlim_max; /* hard limit: maximum value for rlim_cur*/ }
  • 53. Resource limits (Cont.)resource must be one of: RLIMIT_CPU. CPU time limit in seconds. When the process reaches the soft limit, it is sent a SIGXCPU signal. RLIMIT_DATA . The maximum size of the process data segment (initialized data, uninitialized data, and heap). RLIMIT_FSIZE. The maximum size of files that the process may create. Attempts to extend a file beyond this limit result in delivery of a SIGXFSZ signal. RLIMIT_LOCKS. A limit on the combined number of flock() locks and fcntl() leases that this process may establish (Linux 2.4 and later).
  • 54. Resource limits (Cont.) RLIMIT_MEMLOCK. The maximum number of bytes of virtual memory that may be locked into RAM using mlock() and mlockall(). RLIMIT_NOFILE. Specifies a value one greater than the maximum file descriptor number that can be opened by this process. RLIMIT_NPROC. The maximum number of processes that can be created for the real user ID of the calling process. RLIMIT_STACK. The maximum size of the process stack, in bytes. Upon reaching this limit, a SIGSEGV signal is generated. Etc.
  • 55. Resource limitationThree rules govern the changing of the resource limits: A soft limit can be changed by any process to a value less than or equal to its hard limit. Any process can lower its hard limit to a value greater than or equal to its soft limits. Only superuser process can raise a hard limit.
  • 56. Getrlimit.cLinuxSolaris
  • 57. 进程环境main函数和命令行参数 进程的启动和终止 环境表和环境变量 存储空间布局 存储器分配 setjmp和longjmp函数
  • 58. 进程进程环境(第七章) 进程控制(第八章) 进程关系(第九章)
  • 59. 进程控制进程标识符 fork等函数 exit函数 wait等函数 exec等函数 更改用户ID和组ID system函数 进程会计 用户标识和进程时间
  • 60. 进程标识符每个进程都有一个非负整型表示的唯一进程ID 进程ID总是唯一的 当进程终止后,其ID值可以重用 在unix中 ID为0的进程:调度进程,称为swapper ID为1的进程:init进程,自举过程结束时由内核调用 ID为2的进程:页守护进程,负责支持虚拟存储系统的分页操作
  • 61. 进程标识符获取进程常见标识符 调用进程的进程ID:pid_t getpid(); 调用进程的父进程ID:pid_t getppid(); 调用进程的实际用户ID:uid_t getuid(); 调用进程的有效用户ID:uid_t geteuid(); 调用进程的实际组ID:gid_t getgid(); 调用进程的有效组ID:gid_t getegid(); 查看进程情况(4.8) $ps -ef | less
  • 62. 进程控制进程标识符 fork等函数 exit函数 wait等函数 exec等函数 更改用户ID和组ID system函数 进程会计 用户标识和进程时间
  • 63. fork函数一个进程可以调用fork函数创建一个新进程 新进程被称为子进程 函数原型 pid_t fork(void); 返回值 fork函数调用一次,但是返回两次 在子进程中返回0,在父进程中返回子进程ID,出错返回-1 通过返回值,可以确定是在父进程还是子进程中
  • 64. fork函数子进程和父进程继续执行fork调用之后的指令 子进程是父进程的副本 子进程获得父进程数据空间、堆和栈的副本 父子进程并不共享这些存储空间 父子进程共享正文段(只读的) 为了提高效率,fork后不并立即复制父进程空间,采用了COW(Copy-On-Write) 当父子进程任意之一,要修改数据段、堆、栈时,进行复制操作,但仅复制修改区域
  • 65. fork函数程序演示(4.9) 为什么write调用的输出只有一次,而printf调用的输出出现了两次? 子进程中,变量的值改变了;而父进程中,变量的值没有改变。原因?
  • 66. fork函数为什么write调用的输出只有一次,而printf调用的输出出现了两次? write调用是不带用户空间缓冲的。在fork之前调用write,其数据直接写到了标准输出上 标准I/O库是带缓冲的,当标准输出连接到终端设备时,它是行缓冲,否则为全缓冲。 当printf输出到终端设备时,由于遇到换行符,因此缓冲被刷。子进程的数据空间中无缓冲内容 当重定向到文件时,变为全缓冲。fork后,子进程的数据空间中也有内容。所以输出两次
  • 67. 文件共享在上例中:在重定向父进程的标准输出时,子进程的标准输出也被重定向了 fork的一个特性:父进程的所有打开文件描述符,都被复制到子进程中。
  • 68. 父子进程共享文件对象task_struct.........files.......................files_structfd[0]fd[1]fd[2]...........files_structfilef_posf_dentry文件标志索引节点号文件各信息inodetask_struct.........files.......................files_structfd[0]fd[1]fd[2]files_struct父进程子进程...........filef_posf_dentry文件标志...........f_posf_dentry文件标志...........f_posf_dentry文件标志索引节点号文件各信息inode索引节点号文件各信息inode
  • 69. 父子进程文件共享父子进程对同一文件使用了一个文件偏移量 上例中,父进程等待了子进程两秒钟,所以他们的输出才没有混乱;否则有可能出现乱序 文件描述符的常见处理方式 父进程等待子进程完成。父进程无需对描述符做任何处理,当子进程终止后,文件偏移量已经得到了相应的更新 父子进程各自执行不同的程序段,各自关闭文件描述符
  • 70. Difference between parent and child after forkProperties inherited from parent: Real user/group ID, effective user/group ID Supplementary group ID Process group ID Session ID; Control terminal. Set-user/group-ID current work directory File mode mask Signal mask; environment; Resource limitsDifference between parent and child: return value from fork Process ID Parent process ID; The child’s value for tms_utime, tms_stime ,tms_cutime,tms_ustime are set to 0; File locks do not be inherited by child Pending alarm are cleared for child
  • 71. fork函数常见用法一个父进程希望复制自己,使父子进程同时执行不同的代码段 网络服务程序中,父进程等待客户端的服务请求,当请求达到时,父进程调用fork,使子进程处理该次请求,而父进程继续等待下一个服务请求到达 一个进程要执行一个不同的程序 子进程从fork返回后,立即调用exec执行另外一个程序
  • 72. fork程序演示(4.10)72
  • 73. vfork函数vfork与fork的函数原型相同,但两者的语义不同 vfork用于创建新进程,而该新进程的目的是exec一个新程序(执行一个可执行的文件) 由于新程序将有自己的地址空间,因此vfork函数并不将父进程的地址空间完全复制到子进程中。 子进程在调用exec或exit之前,在父进程的地址空间中运行
  • 74. vfork函数vfork函数保证子进程先执行,在它调用exec或者exit之后,父进程才可能被调度执行 程序演示(4.13) 子进程中对glob、var加1操作,结果改变了父进程中的变量值。原因?
  • 75. vfork函数调用_exit,还是exit? exit被调用时,将冲刷所有的标准I/O流。在传统实现中,同时也将关闭标准I/O流。即表示标准输出FILE对象的相关存储区将被清0 由于父子进程共享同一地址空间,当父进程恢复运行并调用printf时,不会产生任何输出,它返回-1 实际上,大多数exit的现代实现不再关闭流。因为进程即将终止,内核将关闭所有已打开的文件描述符
  • 76. 进程控制进程标识符 fork等函数 exit函数 wait等函数 exec等函数 更改用户ID和组ID system函数 进程会计 用户标识和进程时间
  • 77. exit函数正常终止 从main返回 调用exit:ISO C定义 调用_exit或_Exit:前者由ISO C定义,后者由POSIX.1定义 最后一个线程从其启动例程返回 最后一个线程调用pthread_exit 异常终止 调用abort:产生SIGABRT信号 接到某些信号 最后一个线程对取消请求做出响应
  • 78. exit函数不管进程如何终止,最后都会执行内核中的同一段代码:为相应进程关闭所有打开描述符,释放内存等等 若父进程在子进程之前终止了,则子进程的父进程将变为init进程,其PID为1;保证每个进程都有父进程 当子进程先终止,父进程如何知道子进程的终止状态(exit(5)) 内核为每个终止子进程保存了终止状态等信息 父进程调用wait等函数,可获取该信息
  • 79. exit函数当父进程调用wait等函数后,内核将释放终止进程所使用的所有内存,关闭其打开的所有文件 对于已经终止、但是其父进程尚未对其调用wait等函数的进程,被称为僵尸进程 程序演示(4.14 后台启动) ps Defunct:死了的 对于父进程先终止,而被init领养的进程会是僵尸进程吗? init对每个终止的子进程,都会调用wait函数,获取其终止状态
  • 80. 进程控制进程标识符 fork等函数 exit函数 wait等函数 exec等函数 更改用户ID和组ID system函数 进程会计 用户标识和进程时间
  • 81. wait等函数当一个进程正常获知异常终止时,内核就向其父进程发送SIGCHLD信号 父进程可以选择忽略该信号,也可以提供信号处理函数 系统的默认处理方式:忽略该信号
  • 82. wait函数wait函数可用于获取子进程的终止状态 函数原型 pid_t wait(int *statloc); 参数与返回值 statloc:可用于存放子进程的终止状态 返回值:若成功返回终止进程ID,出错返回-1
  • 83. wait函数调用wait函数之后,进程可能出现的情况 如果所有子进程都还在运行,则阻塞等待,直到有一个子进程终止,wait函数才返回 如果一个子进程已经终止,正等待父进程获取其终止状态,则wait函数会立即返回 若进程没有任何子进程,则立即出错返回 注意:若接收到信号SIGCHLD后,调用wait,通常wait会立即返回
  • 84. wait函数参数statloc statloc可以为NULL,表明父进程不需要子进程的终止状态。为了防止子进程成为僵尸或者需等待子进程结束 若statloc不是空指针,则进程终止状态就存放在它指向的存储单元中 statloc指向的存储单元,存放了诸多信息,可以通过系统提供的宏获取
  • 85. 获取终止状态的宏宏说明WIFEXITED(status)若为正常终止子进程返回的状态,则为真。 对于这种情况可以执行WEXITSTATUS(status), 取子进程传递给exit、_exit、_Exit参数的低8位WIFSIGNALED(status)若为异常终止子进程返回的状态,则为真。 对于这种情况可执行WTERMSTG(status), 取使子进程终止的信号编号WIFSTOPPED(status)若为当前暂停子进程的返回的状态,则为真。 对于这种情况,可执行WSTOPSIG(status), 取使子进程暂停的信号编号WIFCONTINUED(status)若在作业控制暂停后已经继续 的子进程返回了状态,则为真
  • 86. wait函数程序演示,说明如何获取exit状态(4.15)
  • 87. waitpid函数如果一个进程有几个子进程,那么只要有一个子进程终止,wait就返回 如何才能等待一个指定的进程终止? 调用wait,然后将其返回的进程ID和所期望的进程ID进行比较 如果ID不一致,则保存该ID,并循环调用wait函数,直到等到所期望的进程ID为止 下一次又想等待某一特定进程时,先查看已终止的进程列表,若其中已有要等待的进程,则无需再调用wait函数
  • 88. waitpid函数waitpid函数可用于等待某个特定的进程 函数原型 pid_t waitpid(pid_t pid, int *statloc, int options); 参数和返回值 成功返回进程ID,失败返回-1 statloc:存放子进程终止状态
  • 89. waitpid函数参数pid pid==-1:等待任一子进程,同wait pid>0:等待进程ID为pid的子进程 pid==0:等待其组ID等于调用进程组ID的任一子进程 pid<-1:等待其组ID等于pid绝对值的任一子进程
  • 90. waitpid函数参数options:可以为0,也可以是以下常量或运算的结果 WCONTINUED WUNTRACED WNOHANG:若pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回0 程序演示waitpid的非阻塞版本(4.16)
  • 91. waitpid函数waitpid函数提供了wait函数没有的三个功能: waitpid可等待一个特定的进程,而wait则返回任一终止子进程的状态 waitpid提供了一个wait的非阻塞版本。有时用户希望取得一个子进程的状态,但不想阻塞 waitpid支持作业控制
  • 92. waitpid函数程序演示(4.17) 需求:如果一个进程fork了一个子进程,但不要它等待子进程终止,也不希望子进程处于僵死状态直到父进程终止 技巧:调用fork进程两次
  • 93. wait3 and wait4 Functions #include pid_t wait3(int *status, int options, struct rusage *rusage) pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage) Both return: process ID if OK, 0, or -1 on error. These two functions allows the kernel to return a summary of resource used by the terminated process and all its child processes. The resource info includes such as the amount of user CPU time, the amount of system CPU time, and the like. struct timeval ru_utime; /* user time used */ struct timeval ru_stime; /* system time used */ long ru_msgsnd; /* messages sent */ long ru_msgrcv; /* messages received */ long ru_nsignals; /* signals received */
  • 94. 进程控制进程标识符 fork等函数 exit函数 wait等函数 exec等函数 更改用户ID和组ID system函数 进程会计 用户标识和进程时间
  • 95. exec等函数进程调用exec等函数用于执行另一个可执行文件 当进程调用一种exec函数时,该进程执行的程序完全替换为新程序 而新程序则从其main函数开始执行 exec并不创建新进程,所以前后的进程ID并未改变,exec只是用一个全新的程序替换了当前进程的正文、数据、堆和栈段
  • 96. execl函数函数原型 int execl(const char *pathname, const char *arg0, .../*(char*)0*/); 返回值与参数 出错返回-1,成功不返回值 pathname:要执行程序的路径名 可变参数:要执行程序的命令行参数,以“(char *)0”结束
  • 97. execl函数调用方法: execl(“./test”, “./test”, “Hello”, “World”, (char*)0); 程序演示(4.18) 进程ID没有改变 execl调用成功,且新程序执行完毕,execl没有返回到下一行代码执行。为什么?
  • 98. 其他类exec函数execv函数 int execv(const char *pathname, char *const argv[]); execle函数 int execle(const char *pathname, const char *arg0, .../* (char*)0, char *const envp[] */); 最后一项是一个指向环境字符串指针数组的指针
  • 99. 其他类exec函数execve函数 int execve(const char *pathname, char *const argv[], char *const envp[]); execlp函数 int execlp(const char *filename, const char *arg0, .../* (char*)0*/); 指定了要执行的文件名。路径信息:从环境变量PATH中获取 PATH=/bin:/usr/bin:/usr/local/bin/:.
  • 100. 其他类exec函数execvp函数 int execvp(const char *filename, char *const argv[]);
  • 101. exec类函数execl execv execle execve execlp execvp 六个函数开头均为exec,所以称为exec类函数 l:表示list,即每个命令行参数都说明为一个单独的参数 v:表示vector,命令行参数放在数组中 e:调用者提供环境表 p:表示通过环境变量PATH,查找执行文件
  • 102. exec Functions (Cont.)Properties that the new program inherits from the calling process. process ID and parent process ID real user ID and real group ID supplementary group IDs process group ID session ID controlling terminal time left until alarm clock current work directory root directory file mode create mask file locks process signal mask pending signals resource limits tms_utime, tms_stime, tms_cutime, tms_cstime102
  • 103. exec类函数通常,只有execve是内核的系统调用,其他5个都是库函数execvpexeclpexeclexecleexecvexecve (系统调用)build argvbuild argvTry each PATH prefixuse environbuild argv
  • 104. 进程控制进程标识符 fork等函数 exit函数 wait等函数 exec等函数 更改用户ID和组ID system函数 进程会计 用户标识和进程时间
  • 105. 用户ID和组ID第一种ID: Linux是一个多用户的操作系统。每个用户都有一个ID,用以唯一标识该用户。这个ID,被称为UID。 每个用户都属于某一个组,组也有一个ID。这个ID,被称为组ID,GID。 第二种ID:文件所有者相关 文件所有者ID:拥有某文件的用户的ID 文件所有者组ID:拥有某文件的用户所属组的ID
  • 106. 用户ID和组ID第三种ID:实际用户ID和实际组ID 进程的实际用户ID:运行该进程的用户的ID 进程的实际组ID:运行该进程的用户所属的组ID 第四种ID:有效用户ID和有效组ID 进程的有效用户ID:用于文件访问权限的检查 进程的有效组ID: 大多数情况下 有效用户/组ID=实际用户/组ID
  • 107. 用户ID和组ID设置用户ID位和设置组ID位 在可执行文件的权限标记中,有一个“设置用户ID位” 若该位被设置,表示:执行该文件时,进程的有效用户ID变为文件的所有者 对于设置组ID位类似 第五种ID: 保存的设置用户ID 保存的设置组ID 上述两者在执行一个程序时(exec)包含了有效用户ID和有效组ID的副本
  • 108. 更改用户ID和组ID系统的权限检查是基于用户ID或组ID 当程序需要增加特权,或需要访问当前并不允许访问的资源时,需要更换自己的用户ID或组ID 可以用setuid设置实际用户ID和有效用户ID;setgid设置实际组ID和有效组ID
  • 109. setuid和setgid函数int setuid(uid_t uid); int setgid(gid_t gid); 改变用户/组ID的规则 若进程具有超级用户权限,则setuid将实际用户ID、有效用户ID、保存的设置用户ID设置为uid 若进程没有超级用户权限,但uid等于实际用户ID或保存的设置用户ID,则setuid只将有效用户ID设置为uid,不改变实际用户ID和保存的设置用户ID 若以上条件不满足,返回-1,errno设为EPERM
  • 110. 用户ID和组ID只有超级用户进程可以更改实际用户ID 实际用户ID是在用户登录时,由login程序设置的 login是一个超级用户进程,当它调用setuid时,会设置所有三个用户ID 仅当对程序文件设置了设置用户ID位时,exec才会设置有效用户ID。任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或保存的设置用户ID 保存的设置用户ID是由exec复制有效用户ID而得来的
  • 111. 用户ID和组ID例子:man联机手册 man程序文件是由名为man的用户拥有的,且设置用户ID位已设置。当执行exec此程序时,用户ID: 实际用户ID=我们的用户ID 有效用户ID=man 保存的设置用户ID=man man程序访问要手册页,而手册页文件是由名为man的用户所拥有的。有效用户ID是man,所以可以访问这些文件
  • 112. 用户ID和组ID在man代表我们运行任一命令之前(使有效用户ID等于我们的用户ID),它调用setuid(getuid())。(不是超级用户进程) getuid返回实际用户ID,即我们的用户ID 实际用户ID=我们的用户ID 有效用户ID=我们的用户ID 保存的设置用户ID=man 这样,以我们的用户ID作为其有效用户ID而运行。这就意味着能访问的只有我们通常可以访问的,而没有任何额外的权限
  • 113. 用户ID和组ID当man需要对其手册页进行访问时,又需要将其有效用户ID改为man man调用setuid(man) 实际用户ID=我们的用户ID 有效用户ID=man 保存的设置用户ID=man 由于setuid的参数等于保存的设置用户ID,所以setuid可以成功修改有效用户ID 这就是保存的设置用户ID的作用
  • 114. 用户ID和组IDIDexecsetuid(uid)set-user-ID offset-user-ID onsuperusernormal userreal user IDunchangedunchangedset to uidunchangedeffective user IDunchangedSet from user ID of program fileset to uidset to uidsaved set-user-IDCopied from effective user IDCopied from effective user IDset to uidunchanged
  • 115. 进程控制进程标识符 fork等函数 exit函数 wait等函数 exec等函数 更改用户ID和组ID system函数 进程会计 用户标识和进程时间
  • 116. system函数用于执行一个shell命令 函数原型 int system(const char* cmdstring); cmdstring:shell命令 程序演示(4.19)
  • 117. system函数system是通过fork、exec、waitpid等实现的,因此有三种返回值 即fork失败,exec失败,waitpid失败
  • 118. 进程会计进程会计记录 包含命令名,CPU时间总量,用户ID和组ID,启动时间等等
  • 119. 进程控制进程标识符 fork等函数 exit函数 wait等函数 exec等函数 更改用户ID和组ID system函数 进程会计 用户标识和进程时间
  • 120. 用户标识getlogin函数可以获取当前用户的登录名 函数原型 char *getlogin(); 返回值 调用此函数的进程没有连接到用户登录时所用的终端,则本函数会失败,返回NULL 这种进程通常称为守护进程 成功返回登录名 程序演示(4.20)
  • 121. 进程时间times函数用于获取墙上时钟时间、用户CPU时间、系统CPU时间 函数原型 clock_t times(struct tms *buf); times填写buf指向的tms结构 struct tms { clock_t tms_utime; //用户CPU时间 clock_t tms_stime; //系统CPU时间 ............... };
  • 122. 进程时间返回值 返回墙上时钟时间 该值相对于过去某个时刻测量的,不能使用其绝对值。只能用相对值
  • 123. 进程控制进程标识符 fork等函数 exit函数 wait等函数 exec等函数 更改用户ID和组ID system函数 进程会计 用户标识和进程时间
  • 124. 进程进程环境(第七章) 进程控制(第八章) 进程关系(第九章)
  • 125. 进程关系进程组 会话 控制终端
  • 126. 进程组每个进程除了有一个进程ID外,还属于一个进程组 进程组是一个或多个进程的集合。通常,它们与同一作业关联,可以接收来自同一终端的各种信号。 每个进程组有一个唯一的进程组ID 每个进程组都可以有一个组长进程;组长进程的标识是:其进程组ID等于组长进程ID
  • 127. 进程组只要进程组中还有一个进程存在,则进程组就存在,与组长进程存在与否无关 从进程组创建开始,到其中最后一个进程离开为止的时间区间,称为进程组的生存期 进程组中的最后一个进程可以终止,或者转移到另一个进程组
  • 128. setpgid函数用于加入一个现有的进程组或者创建一个新进程组 函数原型 int setpgid(pid_t pid, pid_t pgid); 该函数将进程ID为pid的进程的进程组ID,设置为pgid 若pid=pgid,则pid代表的进程将变为进程组组长
  • 129. setpgid函数若pid=0,则使用调用者的进程ID 若pgid=0,则由pid指定的进程ID将用作进程组ID 注意:一个进程只能为它自己或它的子进程设置进程组ID。在子进程调用exec函数之后,父进程就不能再改变该子进程的进程组ID
  • 130. getpgrp函数用于获取调用进程的进程组ID 函数原型 pid_t getpgrp(); 返回值 返回调用进程的进程组ID
  • 131. 会话会话是一个或多个进程组的集合 一次登录形成一个会话 Shell上的一条命令形成一个进程组 proc1 | proc2 &(开两个终端,观察组长 会话ID) proc3 | proc4 | proc5 (命令 ps –xj;程序4.21;在一个终端中分别前后台启动) 登录shellproc1proc2proc3proc4proc5process groupprocess groupprocess group
  • 132. Controlling terminalThere are a few other characteristics of session and group: A session can have a single controlling terminal. The session leader that establishes the connection to the controlling terminal is called the controlling process. The process groups within a session can be divided into a single foreground process and one or more background group. If a session has a controlling terminal, then it has a single foreground process group, and all other group in the session are background group.132
  • 133. Controlling terminal (Cont.)Whenever we type our terminal’s interrupt key (often delete or Ctrl+C ) or quit key (often Ctrl-\), this causes either the interrupt signal or the quit signal to be sent to all processes in the foreground process group. If a modem disconnect is detected by the terminal interface, the hang-up signal is sent to the controlling process (the session leader). 需要注意的是:会话中的前台或后台进程组是针对该会话是否拥有控制终端而言。也即,如果一个会话没有控制终端,则会话中所有进程组均属“后台”。133
  • 134. process groups and sessionslogin shellproc1proc2proc3proc4proc5background process groupSession leader= Controlling leaderbackground process groupforeground process groupControlling terminalModem disconnect (hangup signal)Terminal input and terminal-generated signal134
  • 135. setsidpid_t setsid(); 非组长进程调用此函数,会创建一个新会话,导致三件事: 该进程变成新会话首进程,即会话首进程是创建该会话的进程 该进程成为一个新进程组的组长进程 该进程没有控制终端 组长进程调用此函数,返回出错-1(如何避免?)
  • 136. 会话pid_t getsid(pid_t pid); 返回会话首进程的进程组ID,即会话ID 程序演示 会话首进程失去终端(程序4.22 ) Test中,Ctrl C只有父进程退出 Test1中,Ctrl C导致父子进程退出136