资源预览内容
第1页 / 共144页
第2页 / 共144页
第3页 / 共144页
第4页 / 共144页
第5页 / 共144页
第6页 / 共144页
第7页 / 共144页
第8页 / 共144页
第9页 / 共144页
第10页 / 共144页
亲,该文档总共144页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
第三讲:第三讲:UNIX&Linux进程与线程进程与线程内容内容UNIX&Linux进程环境及进程属性进程环境及进程属性UNIX&Linux进程管理及控制进程管理及控制UNIX&Linux线程概念线程概念UNIX&Linux线程控制线程控制3进进程与程序程与程序程序是一个包含可执行代码的文件,它放在磁盘程序是一个包含可执行代码的文件,它放在磁盘等介质上。等介质上。当程序被操作系统装载到内存并分配给它一定资当程序被操作系统装载到内存并分配给它一定资源后,此时可称为进程。源后,此时可称为进程。程序是一个静态概念,进程是一个动态概念程序是一个静态概念,进程是一个动态概念。Linux的进程结构的进程结构进程状态(进程状态(进程状态(进程状态(StateState)调度信息(调度信息(调度信息(调度信息(ScheduleSchedule)标识(标识(标识(标识(IdentifiersIdentifiers)进程间通信(进程间通信(进程间通信(进程间通信(Inter-ProcessInter-ProcessCommunicationCommunication)时间和定时器(时间和定时器(时间和定时器(时间和定时器(TimesandTimesandTimersTimers)文件系统(文件系统(文件系统(文件系统(FilesystemFilesystem)。虚拟内存(虚拟内存(虚拟内存(虚拟内存(VirtualmemoryVirtualmemory)task_structstructtask_structvolatilelongstate;intexit_state;unsignedintflags;unsignedintrt_priority;structlist_headtasks;structmm_struct*mm;intexit_state;intexit_code,exit_signal;pid_tpid;pid_ttgid;structtask_struct*real_parent;structtask_struct*parent;structlist_headchildren;structlist_headsibling;structtask_struct*group_leader;structlist_headthread_group;cputime_tutime,stime;structtimespecstart_time;structtimespecreal_start_time;charcommTASK_COMM_LEN;intlink_count,total_link_count;structthread_structthread;structfs_struct*fs;structfiles_struct*files;structsignal_struct*signal;structsighand_struct*sighand;unsignedlongtimer_slack_ns;unsignedlongdefault_timer_slack_ns;task_struct:进程状态:进程状态进程状态:进程状态:lvolatilelongstate;lintexit_state;state成员的可能取值如下:成员的可能取值如下:l#defineTASK_RUNNING0l#defineTASK_INTERRUPTIBLE1l#defineTASK_UNINTERRUPTIBLE2l#defineTASK_ZOMBIE4l#defineTASK_STOPPED8exit_state成员的可能取值如下:成员的可能取值如下:l#defineEXIT_ZOMBIE16l#defineEXIT_DEAD32用户级进程状态切换用户级进程状态切换/usr/include/linux/sched.h/usr/include/linux/sched.h#defineTASK_RUNNING0#defineTASK_RUNNING0#defineTASK_INTERRUPTIBLE1#defineTASK_INTERRUPTIBLE1#defineTASK_UNINTERRUPTIBLE2#defineTASK_UNINTERRUPTIBLE2#defineTASK_ZOMBIE4#defineTASK_ZOMBIE4#defineTASK_STOPPED8#defineTASK_STOPPED8task_struct:内存管理内存管理mmmmtask_structcountcountpgdpgdmmapmmapmmap_avlmmap_avlmmap_semmmap_semmm_structvm_endvm_endvm_startvm_startvm_flagsvm_flagsvm_inodevm_inodevm_opsvm_opsvm_nextvm_nextdatadatacodecodevm_endvm_endvm_startvm_startvm_flagsvm_flagsvm_inodevm_inodevm_opsvm_opsvm_nextvm_nextvm_area_structvm_area_structprocessvirtualMemorytask_struct:文件管理文件管理fs fsfilesfilestask_structcountcountumaskumask*root*root*pwd*pwdcountcountclose_on_execclose_on_execopen_fsopen_fsfd0fd0fd1fd1f_modef_modef_pcsf_pcsf_flagsf_flagsf_countf_countf_ownerf_ownerfd255fd255f_inodef_inodef_opf_opf_versionf_versionfs_structinodeinodeinodefiles_structfilefileoperationroutinesLinux的进程组织的进程组织Proc1Proc1Proc2Proc2ProcProcn nProcn+1Procn+1Procn+2Procn+2进程的物理组织结构进程的物理组织结构CurrentCurrentP1P2P3PnPn+1Pn+2Pn+3Pn+4进程的逻辑组织结构进程的逻辑组织结构2024/7/3012进程基本属进程基本属性性-进进程程号与父进程号号与父进程号LinuxSolaris2024/7/3014进程基本属性进程基本属性-进进程组号(程组号(PGID)进程组是一个或多个进程的集合。它们与同一作业进程组是一个或多个进程的集合。它们与同一作业相关联,可以接受来自同一终端的各种信号。每个相关联,可以接受来自同一终端的各种信号。每个进程组都有唯一的进程组号,进程组号是可以在用进程组都有唯一的进程组号,进程组号是可以在用户层修改的。户层修改的。15进程用户属性进程用户属性进程真实用户号我们实际是谁(即执行程序或创建子进程的用户ID)进程真实用户组号进程有效有效用户号用于文件存取许可权检查进程有效用户组号设置-用户-ID由exec函数保存设置-组-ID与一个进程相关联的用户与一个进程相关联的用户ID有如下几种:有如下几种:2024/7/3016进程真实用户号进程真实用户号(RUID)2024/7/30人民邮电出版社出版杨宗德编著17进进程真实用程真实用户组号户组号(RGID)创建进程的用户所在的组号为该进程的真实用户组创建进程的用户所在的组号为该进程的真实用户组号(号(RGID)。可以通过调用)。可以通过调用getgid()函数来获得当函数来获得当前进程的真实用户组号。前进程的真实用户组号。18真实用户与有效用户的关系真实用户与有效用户的关系通常情况下,有效用户通常情况下,有效用户ID等于实际用户等于实际用户ID,有效组,有效组ID等于等于实际组实际组ID;文件模式中有特殊标志位,定义为文件模式中有特殊标志位,定义为“当执行此文件时当执行此文件时, ,将进将进程的有效用户设置为文件的所有者程的有效用户设置为文件的所有者”,与此类似,组,与此类似,组ID也也有类似的情况,定义为有类似的情况,定义为“当执行此文件时当执行此文件时, ,将进程的有效用将进程的有效用户组置为文件所有者所在组户组置为文件所有者所在组”。这两个标志位称为:设置。这两个标志位称为:设置-用户用户-ID(setuid)和设置)和设置-组组-ID(setgid)。)。2024/7/3019普通用户能够修改自己的密码的原因普通用户能够修改自己的密码的原因/etc/passwd文件用来存储所有用户信息,任何用户都可以修改自己的文件用来存储所有用户信息,任何用户都可以修改自己的密码,显然,其它用户在执行密码,显然,其它用户在执行/usr/bin/passwd命令时修改了命令时修改了/etc/passwd文件(并不是说可以使用文件(并不是说可以使用vi编辑器修改),但是,通过查编辑器修改),但是,通过查看看/etc/passwd文件的权限,发现普通用户对此文件仅有读的权限。是文件的权限,发现普通用户对此文件仅有读的权限。是什么原因导致普通用户可以修改什么原因导致普通用户可以修改/etc/passwd文件呢?文件呢?这是因为用户执行这是因为用户执行“/usr/bin/passwd”命令时,命令时,/usr/bin/passwd文件文件设置了设置了setuid位,在执行此程序(位,在执行此程序(/usr/bin/passwd)时,该用户所拥)时,该用户所拥有的权限等同于文件有的权限等同于文件“/usr/bin/passwd”的拥有者的拥有者root的权限,而的权限,而root用户拥有对用户拥有对/ect/passwd文件写的权限,因此普通用户可以通过文件写的权限,因此普通用户可以通过/usr/bin/passwd来修改来修改/etc/passwd文件的内容。文件的内容。如果清除掉如果清除掉“/usr/bin/passwd”文件的文件的setuid权限位,普通用户就不权限位,普通用户就不能修改自己的密码了。能修改自己的密码了。2024/7/3020获取进程有效用户号和有效用户组号获取进程有效用户号和有效用户组号头文件头文件unistd.h_uid_tgeteuid(void)_uid_tgetegid(void)进程的启动进程的启动C程序的启动函数是程序的启动函数是mainlmain(intargc,char*argv);当内核启动一个当内核启动一个C程序时,会在调用程序时,会在调用main函数前函数前调用特殊的启动函数来获取调用特殊的启动函数来获取main函数地址和传递函数地址和传递给给main函数的参数,并且将这些信息填写到函数的参数,并且将这些信息填写到PCB中中进程的终止进程的终止正常终止正常终止l从从main返回返回l调用调用exitl调用调用_exit或或_Exitl最后一个线程从其启动例程中返回最后一个线程从其启动例程中返回l最后一个线程调用最后一个线程调用pthread_exit异常终止异常终止l调用调用abort,产生,产生SIGABRT信号信号l接收到终止信号接收到终止信号终止进程的终止进程的exit和和_exit函数函数voidexit(intstatus);/*/void_exit(intstatus);/*/这两个函数正常地终止一个进程这两个函数正常地终止一个进程:l_exitexit函数将会立即返回内核l lexitexit函数会执行一些特定的清理操作,还会执行标准I/O库的清理关闭操作(为所有打开流调用fclose函数,这会使得所有缓冲的输出数据被更新到相应的设备),然后返回内核2024/7/3024exit与与return的区别的区别C语言关键字语言关键字return与函数与函数exit()在在main函数退出函数退出时有相似之处,但两者有本质的区别:时有相似之处,但两者有本质的区别:lreturn退出当前函数主体,exit()函数退出当前进程l在main函数中,return0和exit(0)完成一样的功能l在子函数中,return仅仅从子函数中返回,而调用exit()将会退出当前进程a atexittexit及及on_exiton_exit函数函数当进程终止时,程序可能需要进行一些自身的清当进程终止时,程序可能需要进行一些自身的清理工作,如资源释放等等理工作,如资源释放等等atexit及及on_exit函数提供了进行这样工作的机会,函数提供了进行这样工作的机会,它允许用户注册若干终止处理函数,当进程终止它允许用户注册若干终止处理函数,当进程终止时,这些时,这些终止处理函数将会被自动调用终止处理函数将会被自动调用a atexittexit及及on_exiton_exit函数函数函数原型(stdlib.h)intatexit(void(*func)(void);inton_exit(void(*func)(int,void*),void*arg);ANSIC规定一个进程最多能注册规定一个进程最多能注册32个终止处理函个终止处理函数数调用调用exit函数终止进程时将会回调这些注册的终止函数终止进程时将会回调这些注册的终止处理函数(最先注册函数的最后被回调)处理函数(最先注册函数的最后被回调)调用调用_exit函数终止进程时将不会回调这些注册的函数终止进程时将不会回调这些注册的终止函数终止函数Example:(showtime.c)#include#include#include#include#includestaticvoidshowtimes(void)doubleticks;structtmstinfo;if(ticks=(double)sysconf(_SC_CLK_TCK)=-1)perror(Failedtodetermineclocktickspersecond);elseif(times(&tinfo)=(clock_t)-1)perror(Failedtogettimesinformation);elsefprintf(stderr,Usertime:%8.3fsecondsn,tinfo.tms_utime/ticks);fprintf(stderr,Systemtime:%8.3fsecondsn,tinfo.tms_stime/ticks);fprintf(stderr,childutime:%8.3fsecondsn,tinfo.tms_cutime/ticks);fprintf(stderr,Childsystime:%8.3fsecondsn,tinfo.tms_cstime/ticks);intmain(void)if(atexit(showtimes)fprintf(stderr,Failedtoinstallshowtimesexithandlern);return1;return0;进程的启动和终止进程的启动和终止KernelKernelKernelKernelexitexitfunc.func.用户函数用户函数用户函数用户函数mainmain函数函数函数函数C C启动启动启动启动例程例程例程例程终止处理函数终止处理函数终止处理函数终止处理函数终止处理函数终止处理函数终止处理函数终止处理函数标准标准标准标准I/OI/O清理函数清理函数清理函数清理函数_exitexit不返回不返回 execl ll la ac ccallreturncallreturncallreturns s s ss s s se e e ec c c co o o or r r rp p p p r r r re e e es s s sU U U U_exit_exitexit不返回不返回 exit不返回不返回l ll la ac c回回返返回回返返C C程序内存空间布局程序内存空间布局命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量栈栈堆堆未初始化的数据未初始化的数据初始化的数据初始化的数据初始化的数据初始化的数据正文正文正文正文High addressLow addressC C程序内存空间布局程序内存空间布局命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量栈栈堆堆未初始化的数据未初始化的数据初始化的数据初始化的数据初始化的数据初始化的数据正文正文正文正文High addressLow addressCPU执行的机器指令部分,正文段通常是共享、只读的C C程序内存空间布局程序内存空间布局命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量栈栈堆堆未初始化的数据未初始化的数据初始化的数据初始化的数据初始化的数据初始化的数据正文正文正文正文High addressLow address包含了程序中需明确赋初值的变量,如全局变量intmaxcount=99;C C程序内存空间布局程序内存空间布局命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量栈栈堆堆未初始化的数据未初始化的数据初始化的数据初始化的数据初始化的数据初始化的数据正文正文正文正文High addressLow address程序执行之前,将此段中的数据初始化为0,如全局变量longsum1000;C C程序存储空间布局程序存储空间布局命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量栈栈堆堆未初始化的数据未初始化的数据初始化的数据初始化的数据初始化的数据初始化的数据正文正文正文正文High addressLow address用于动态分配内存C C程序内存空间布局程序内存空间布局命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量命令行参数和环境变量栈栈堆堆未初始化的数据未初始化的数据初始化的数据初始化的数据初始化的数据初始化的数据正文正文正文正文High addressLow address主要用于支撑函数调用存放参数、局部变量等常用内存分配函数常用内存分配函数#include#includevoid*calloc(size_tnmemb,size_tsize);void*alloca(size_tsize);void*malloc(size_tsize);void*realloc(void*ptr,size_tsize);voidfree(void*ptr);在堆中分配内存空间在堆中分配内存空间lcalloccalloc.分配指定长度的内存空间,该内存空间的值被初始化为0l lmallocmalloc.分配指定长度的内存空间,该内存空间的值为随机值l lreallocrealloc.改变已经已经分配的内存区域的大小l以上三个函数都会调用sbrk系统调用,allocaalloca会在该函数调用者的堆栈中分配内存空间会在该函数调用者的堆栈中分配内存空间(临时空间),该内存空间会被自动释放(临时空间),该内存空间会被自动释放环境表环境表每个进程都会接收到一张环境表每个进程都会接收到一张环境表通过通过environenviron找到环境表找到环境表extern char *environ;环境字符串:环境字符串:name=valuename=value获取环境变量获取环境变量访问环境变量的方法访问环境变量的方法直接使用environ访问环境表使用getenv和putenv等函数getenvgetenv函数用于获取环境变量值函数用于获取环境变量值函数原型函数原型char* getenv(const char *name);(stdlib.h)返回与返回与namename关联的关联的valuevalue的指针,若未找到则返回的指针,若未找到则返回NULLNULL设置环境变量设置环境变量三种方法:三种方法:putenvsetenvunsetenvputenvputenv函数将形式为函数将形式为name=valuename=value的字符串,放入的字符串,放入环境表中;若环境表中;若namename已经存在,则先删除其原来的已经存在,则先删除其原来的定义。定义。函数原型:函数原型:int putenv(char *str);(stdlib.h)设置环境变量设置环境变量setenvsetenv函数原型:函数原型:int setenv(const char* name, const char* value, int rewrite);(stdlib.h)setenvsetenv将环境变量将环境变量namename的值设置为的值设置为valuevalue。若若namename已经存在已经存在rewrite != 0,则删除其原先的定义rewrite = 0,则不删除其原先的定义设置环境变量设置环境变量unsetenvunsetenv函数用于删除某个环境变量函数用于删除某个环境变量函数原型函数原型int unsetenv(const char* name); (stdlib.h)删除删除namename的定义的定义内容内容UNIX&Linux进程环境及进程属性进程环境及进程属性UNIX&Linux进程管理及控制进程管理及控制UNIX&Linux线程概念线程概念UNIX&Linux线程控制线程控制创建进程创建进程 UNIX&LinuxUNIX&Linux中创建进程的方式:中创建进程的方式:中创建进程的方式:中创建进程的方式:l lShellShell中执行命令的方式来创建进程中执行命令的方式来创建进程中执行命令的方式来创建进程中执行命令的方式来创建进程l l在一个已经存在的进程中调用在一个已经存在的进程中调用在一个已经存在的进程中调用在一个已经存在的进程中调用forkfork函数来生成新的进程函数来生成新的进程函数来生成新的进程函数来生成新的进程 。这里调用。这里调用。这里调用。这里调用forkfork创建新进程的进程即为父进程,而相对创建新进程的进程即为父进程,而相对创建新进程的进程即为父进程,而相对创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程应的为其创建出的进程则为子进程应的为其创建出的进程则为子进程应的为其创建出的进程则为子进程 在在在在UNIXUNIX中除了中除了中除了中除了进程进程进程进程0 0(即(即(即(即PID=0PID=0的交换进程,的交换进程,的交换进程,的交换进程,SwapperSwapperProcessProcess)以外的所有进程都是由其他进程使用)以外的所有进程都是由其他进程使用)以外的所有进程都是由其他进程使用)以外的所有进程都是由其他进程使用forkfork创建的。创建的。创建的。创建的。因而除了进程因而除了进程因而除了进程因而除了进程0 0以外的进程都只有一个父进程,但一个进程以外的进程都只有一个父进程,但一个进程以外的进程都只有一个父进程,但一个进程以外的进程都只有一个父进程,但一个进程可以有多个子进程。可以有多个子进程。可以有多个子进程。可以有多个子进程。 进程进程进程进程0 0是系统是系统是系统是系统引导引导引导引导时创建的一个特殊进程,在其调用时创建的一个特殊进程,在其调用时创建的一个特殊进程,在其调用时创建的一个特殊进程,在其调用forkfork创创创创建出一个子进程(即建出一个子进程(即建出一个子进程(即建出一个子进程(即PID=1PID=1的进程的进程的进程的进程1 1,又称,又称,又称,又称initinit)后,进程)后,进程)后,进程)后,进程0 0就转为就转为就转为就转为交换进程交换进程交换进程交换进程(有时也被称为(有时也被称为(有时也被称为(有时也被称为空闲进程空闲进程空闲进程空闲进程),而进程),而进程),而进程),而进程1 1(initinit进程)就是系统里其他所有进程的祖先。进程)就是系统里其他所有进程的祖先。进程)就是系统里其他所有进程的祖先。进程)就是系统里其他所有进程的祖先。创建进程函数创建进程函数函数原型函数原型pid_t fork(void);(unistd.h)返回值返回值fork函数被正确调用一次,将会返回两次在子进程中返回一次,返回值为0(PID=0的进程是Swapper)在父进程中返回一次,返回值为子进程ID(可以让父进程指导创建的子进程ID)通过返回值,可以确定是在父进程还是子进程中出错返回-1f forkork示例示例# #includeinclude#include#include#include#includeint main(void)int main(void) pid_t pid;if(pid=fork()=-1)Printf(“fork errorn”);else if(pid=0)Printf(“in the child processn”);elsePrint(“in the parent process,the child process id is %dn”,pid);return 0; copy_process()copy_process()fork函数工作流程函数工作流程调用调用dup_task_struct为新进程创建一个内核栈,为新进程创建一个内核栈,复制复制task_struct结构;结构;检查当前用户拥有的进程数是否超出配额;检查当前用户拥有的进程数是否超出配额;子进程状态设置为子进程状态设置为TASK_UNINTERRUPTIBLE;调用调用copy_flags()更新更新flags成员;成员;调用调用get_pid()为新进程获取一个有效的为新进程获取一个有效的PID;根据传递给根据传递给clone()的参数标志,拷贝或共享打的参数标志,拷贝或共享打开的文件、文件系统信息、信号处理函数、开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等;进程地址空间和命名空间等;让父子进程平分剩余的时间片;让父子进程平分剩余的时间片;最后,作扫尾工作并返回一个指向子进程的指针最后,作扫尾工作并返回一个指向子进程的指针fork()fork()clone()clone()do_fork()do_fork()forkfork函数工作流程函数工作流程子进程和父进程继续执行子进程和父进程继续执行forkfork调用之后的指令调用之后的指令子进程是父进程的副本子进程是父进程的副本子进程获得父进程数据段、堆和栈的副本(并非共享)父子进程共享正文段(只读的)为了提高效率,为了提高效率,forkfork后不并立即复制父进程空间,后不并立即复制父进程空间,采用了采用了COWCOW(Copy-On-WriteCopy-On-Write)当父子进程任意之一,要修改数据段、堆、栈时,进行复制操作,但仅复制修改区域47创建子进程创建子进程写时复制写时复制1.1.父子进程均无写操作时:父子进程均无写操作时:2.2.父进程或者子进程对数据有修改操作时,父进程或者子进程对数据有修改操作时,子进程或父进程将新产生一份进程的数据子进程或父进程将新产生一份进程的数据拷贝,然后再修改。拷贝,然后再修改。fork函数执行后父子进程的异同函数执行后父子进程的异同 子进程继承的子进程继承的子进程继承的子进程继承的: :l lRealuser/groupID,effectiveRealuser/groupID,effectiveuser/groupIDuser/groupID;SupplementarygroupIDSupplementarygroupID;ProcessgroupIDProcessgroupIDl lSessionIDSessionID;l lControlterminal.Controlterminal.l lSet-user/group-IDSet-user/group-IDl lcurrentworkdirectorycurrentworkdirectoryl lFilemodemaskFilemodemaskl lSignalmaskSignalmask;l l环境变量;环境变量;环境变量;环境变量;l lResourcelimitsResourcelimits;l l堆栈堆栈堆栈堆栈l l打开的文件描述符打开的文件描述符打开的文件描述符打开的文件描述符l l 父子进程的区别:父子进程的区别:父子进程的区别:父子进程的区别:l lreturnvaluefromforkreturnvaluefromforkl lProcessIDProcessIDl lParentprocessIDParentprocessID;l lThechildsvalueforThechildsvaluefortms_utime,tms_stimetms_utime,tms_stime,tms_cutime,tms_ustimeare,tms_cutime,tms_ustimearesetto0setto0;l lFilelocksdonotbeinheritedFilelocksdonotbeinheritedbychildbychildl lPendingalarmareclearedforPendingalarmareclearedforchildchild49父子进程共享文件父子进程共享文件父子进程共享父子进程共享父子进程共享父子进程共享文件表项文件表项文件表项文件表项文件描述表文件描述表文件描述表文件描述表文件状态标志文件状态标志文件状态标志文件状态标志文件读写偏移量文件读写偏移量文件读写偏移量文件读写偏移量索引节点指针索引节点指针索引节点指针索引节点指针引用数:引用数:引用数:引用数:2 2文件表文件表文件属性文件属性文件属性文件属性引用数:引用数:引用数:引用数:1 1数据块位置数据块位置数据块位置数据块位置当前文件长度当前文件长度当前文件长度当前文件长度索引节点表索引节点表文件描述表文件描述表文件描述表文件描述表进进进进程程程程控控控控制制制制块块块块进进进进程程程程控控控控制制制制块块块块父子进程共享文件父子进程共享文件task_struct.files.files_structfd0fd1fd2.files_structfilef_posf_dentry文件标志索引节点号文件各信息inodetask_struct.files.files_structfd0fd1fd2files_struct父进程子进程.filef_posf_dentry文件标志.f_posf_dentry文件标志.f_posf_dentry文件标志索引节点号文件各信息inode索引节点号文件各信息inode父子进程共享文件父子进程共享文件forkfork的一个特性:父进程的所有打开文件描述符,的一个特性:父进程的所有打开文件描述符,都被复制到子进程中都被复制到子进程中父子进程对同一文件使用了一个文件偏移量父子进程对同一文件使用了一个文件偏移量父子进程对共享文件的常见处理方式:父子进程对共享文件的常见处理方式:父进程等待子进程完成。父进程无需对描述符做任何处理,当子进程终止后,文件偏移量已经得到了相应的更新父子进程各自执行不同的程序段,各自关闭不需要的文件描述符intglob=6;/*externalvariableininitializeddata*/charbuf=awritetostdoutn;intmain(void)intvar;/*automaticvariableonthestack*/pid_tpid;var=88;if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!=sizeof(buf)-1)err_sys(writeerror);printf(beforeforkn);/*wedontflushstdout*/if(pid=fork()0)err_sys(forkerror);elseif(pid=0)/*child*/glob+;/*modifyvariables*/var+;elsesleep(2);/*parent*/printf(pid=%d,glob=%d,var=%dn,getpid(),glob,var);exit(0);writewrite hasnothasnotbufferbuffer。标准标准标准标准IOIO函数函数函数函数是带缓存的是带缓存的是带缓存的是带缓存的示例示例打印两次,因为,标打印两次,因为,标打印两次,因为,标打印两次,因为,标准准准准I/OI/O库对普通输出是库对普通输出是库对普通输出是库对普通输出是全缓存的。全缓存的。全缓存的。全缓存的。forkfork函数的通常编程用法函数的通常编程用法一个父进程希望复制自己,使父子进程同时执行一个父进程希望复制自己,使父子进程同时执行不同的代码段不同的代码段网络服务程序中,父进程等待客户端的服务请求,当请求达到时,父进程调用fork,使子进程处理该次请求,而父进程继续等待下一个服务请求到达一个进程要执行一个不同的程序一个进程要执行一个不同的程序子进程从fork返回后,立即调用exec执行另外一个程序vforkvfork函数函数vforkvfork与与forkfork的函数原型相同,但两者的语义不同的函数原型相同,但两者的语义不同vforkvfork用于创建新进程,而该新进程的目的是用于创建新进程,而该新进程的目的是execexec一一个新程序(执行一个可执行的文件)个新程序(执行一个可执行的文件)由于新程序将有自己的地址空间,因此由于新程序将有自己的地址空间,因此vforkvfork函数并函数并不将父进程的地址空间完全复制到子进程中。不将父进程的地址空间完全复制到子进程中。子进程在调用子进程在调用execexec或或exitexit之前,在父进程的地址空之前,在父进程的地址空间中运行间中运行vforkvfork函数保证子进程先执行,在它调用函数保证子进程先执行,在它调用execexec或者或者exitexit之后,父进程才可能被调度执行之后,父进程才可能被调度执行等待子进程执行结束的等待子进程执行结束的waitwait类函数类函数当一个进程正常或异常终止时,内核就向其父进当一个进程正常或异常终止时,内核就向其父进程发送程发送SIGCHLDSIGCHLD信号信号父进程可以选择忽略该信号,也可以提供信号处父进程可以选择忽略该信号,也可以提供信号处理函数理函数系统的默认处理方式:忽略该信号系统的默认处理方式:忽略该信号waitwait函数函数waitwait函数可用于获取子进程的终止状态函数可用于获取子进程的终止状态函数原型函数原型pid_t wait(int *statloc);(sys/wait.h)参数与返回值参数与返回值statloc:可用于存放子进程的终止状态返回值:若成功返回终止进程ID,出错返回-1waitwait函数函数参数参数statlocstatlocstatloc可以为NULL,表明父进程不需要子进程的终止状态,而是为了防止子进程成为僵尸进程,或者因为同步原因需等待子进程结束。若statloc不是空指针,则进程终止状态就存放在它指向的存储单元中statlocstatloc指向的存储单元,存放了诸多信息,可以指向的存储单元,存放了诸多信息,可以通过系统提供的宏来快速判断终止状态种类通过系统提供的宏来快速判断终止状态种类判断子进程终止状态种类的宏判断子进程终止状态种类的宏宏说明WIFEXITED(status)若为正常终止子进程返回的状态,则为真。对于这种情况可以执行WEXITSTATUS(status),取子进程传递给exit、_exit、_Exit参数的低8位WIFSIGNALED(status)若为异常终止子进程返回的状态,则为真。对于这种情况可执行WTERMSTG(status),取使子进程终止的信号编号WIFSTOPPED(status)若为当前暂停子进程的返回的状态,则为真。对于这种情况,可执行WSTOPSIG(status),取使子进程暂停的信号编号WIFCONTINUED(status)若在作业控制暂停后已经继续的子进程返回了状态,则为真waitwait函数函数调用调用waitwait函数之后,进程可能出现的情况函数之后,进程可能出现的情况如果所有子进程都还在运行,则阻塞父进程执行,进入等待状态,直到有一个子进程终止,wait函数才返回如果已经有子进程进入终止状态并等待父进程获取其终止状态,则wait函数会立即返回若进程没有任何子进程,则立即出错返回注意:若接收到信号注意:若接收到信号SIGCHLDSIGCHLD后,调用后,调用waitwait,通常,通常waitwait会立即返回会立即返回等待特定子进程执行终止等待特定子进程执行终止intmain(void)pid_tchildpid;/*setupsignalhandlershere.*/childpid=fork();if(childpid=-1)perror(Failedtofork);return1;if(childpid=0)printf(Iamchild%ldn,(long)getpid();elseif(wait(NULL)!=childpid)printf(Asignalmusthaveinterruptedthewait!n);elseprintf(Iamparent%ldwithchild%ldn,(long)getpid(),(long)childpid);return0;解析子进程终止状态解析子进程终止状态voidshow_return_status(void)pid_tchildpid;intstatus;childpid=wait(&status);if(childpid=-1)perror(Failedtowaitforchild);elseif(WIFEXITED(status)&!WEXITSTATUS(status)printf(Child%ldterminatednormallyn,(long)childpid);elseif(WIFEXITED(status)printf(Child%ldterminatedwithreturnstatus%dn,(long)childpid,WEXITSTATUS(status);elseif(WIFSIGNALED(status)printf(Child%ldterminatedduetouncaughtsignal%dn,(long)childpid,WTERMSIG(status);elseif(WIFSTOPPED(status)printf(Child%ldstoppedduetosignal%dn,(long)childpid,WSTOPSIG(status);等待所有子进程执行终止等待所有子进程执行终止intmain(intargc,char*argv)pid_tchildpid;inti,n;if(argc!=2)printf(Usage:%snn,argv0);return1;n=atoi(argv1);for(i=1;in;i+)if(childpid=fork()0);/*waitforallofyourchildren*/printf(i:%dprocessID:%ldparentID:%ldchildID:%ldn,i,(long)getpid(),(long)getppid(),(long)childpid);return0;waitpidwaitpid函数函数如果一个进程有几个子进程,那么只要有一个子如果一个进程有几个子进程,那么只要有一个子进程终止,进程终止,waitwait就返回就返回如何才能等待一个指定的进程终止?如何才能等待一个指定的进程终止?调用wait,然后将其返回的进程ID和所期望的进程ID进行比较如果ID不一致,则保存该ID,并循环调用wait函数,直到等到所期望的进程ID为止下一次又想等待某一特定进程时,先查看已终止的进程列表,若其中已有要等待的进程,则无需再调用wait函数waitpidwaitpid函数函数waitpidwaitpid函数可用于等待某个特定的进程函数可用于等待某个特定的进程函数原型函数原型pid_t waitpid(pid_t pid, int *statloc, int options);(sys/wait.h)参数和返回值参数和返回值成功返回进程ID,失败返回-1statloc:存放子进程终止状态waitpidwaitpid函数函数参数参数pidpidpid=-1:等待任意子进程执行终止,同waitpid0:等待进程ID为pid的子进程执行终止pid=0:等待其组ID等于调用进程组ID的任意子进程pid-1:等待其组ID等于pid绝对值的任意子进程参数参数optionsoptions:可以为:可以为0 0,也可以是以下常量或运,也可以是以下常量或运算的结果算的结果WCONTINUEDWUNTRACEDWNOHANG:若pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回0waitpidwaitpid函数示例函数示例pid_t pc, pr; pid_t pc, pr; pc=fork(); pc=fork(); if(pc0) printf(Erroroccured on forking.n); if(pc0) printf(Erroroccured on forking.n); else if(pc=0) else if(pc=0) sleep(10); sleep(10); exit(0); exit(0); do do pr=waitpid(pc,NULL, WNOHANG); pr=waitpid(pc,NULL, WNOHANG); if(pr=0) if(pr=0) printf(No child exitedn); printf(No child exitedn); sleep(1); sleep(1); while(pr=0); while(pr=0); if(pr=pc) printf(successfullyget child %dn, pr); if(pr=pc) printf(successfullyget child %dn, pr); else printf(some error occuredn); else printf(some error occuredn); waitpidwaitpid函数函数waitpidwaitpid函数提供了函数提供了waitwait函数没有的三个功能:函数没有的三个功能:waitpid可等待一个特定的进程,而wait则返回任一终止子进程的状态waitpid提供了一个wait的非阻塞版本。有时用户希望取得一个子进程的状态,但不想阻塞waitpid支持作业控制wait3 andwait4函数函数#includepid_twait3(int*status,intoptions,structrusage*rusage)pid_twait4(pid_tpid,int*status,intoptions,structrusage*rusage)与与wait,waitpid功能类似,成功返回终止进程功能类似,成功返回终止进程PID,出错返,出错返回回-1出此之外,这两个函数允许出此之外,这两个函数允许kenel返回子进程使用的资源汇返回子进程使用的资源汇总总rusage包括如下资源信息:包括如下资源信息:structtimevalru_utime;/*usertimeused*/structtimevalru_utime;/*usertimeused*/structtimevalru_stime;/*systemtimeused*/structtimevalru_stime;/*systemtimeused*/longru_msgsnd;/*messagessent*/longru_msgsnd;/*messagessent*/longru_msgrcv;/*messagesreceived*/longru_msgrcv;/*messagesreceived*/longru_nsignals;/*signalsreceived*/longru_nsignals;/*signalsreceived*/僵尸进程僵尸进程 进程在正常退出之前会释放进程用户空间的所有资源,进程在正常退出之前会释放进程用户空间的所有资源,进程在正常退出之前会释放进程用户空间的所有资源,进程在正常退出之前会释放进程用户空间的所有资源,但进程控制块等内核空间资源不会在此时释放但进程控制块等内核空间资源不会在此时释放但进程控制块等内核空间资源不会在此时释放但进程控制块等内核空间资源不会在此时释放 当父进程调用当父进程调用当父进程调用当父进程调用waitwait等函数后,内核将释放终止进程所等函数后,内核将释放终止进程所等函数后,内核将释放终止进程所等函数后,内核将释放终止进程所使用的所有内存,关闭其打开的所有文件使用的所有内存,关闭其打开的所有文件使用的所有内存,关闭其打开的所有文件使用的所有内存,关闭其打开的所有文件(回收内核(回收内核(回收内核(回收内核空间资源)空间资源)空间资源)空间资源) 对于已经终止、但是其父进程尚未对其调用对于已经终止、但是其父进程尚未对其调用对于已经终止、但是其父进程尚未对其调用对于已经终止、但是其父进程尚未对其调用waitwait等函等函等函等函数的进程,被称为僵尸进程数的进程,被称为僵尸进程数的进程,被称为僵尸进程数的进程,被称为僵尸进程 对于父进程先终止,而被对于父进程先终止,而被对于父进程先终止,而被对于父进程先终止,而被initinit领养的进程会是僵尸进领养的进程会是僵尸进领养的进程会是僵尸进领养的进程会是僵尸进程吗?程吗?程吗?程吗?l l若父进程在子进程之前终止了,则子进程的父进程将变为若父进程在子进程之前终止了,则子进程的父进程将变为若父进程在子进程之前终止了,则子进程的父进程将变为若父进程在子进程之前终止了,则子进程的父进程将变为initinit进程进程进程进程,保保保保证每个进程都有父进程证每个进程都有父进程证每个进程都有父进程证每个进程都有父进程l linitinit对每个终止的子进程,都会调用对每个终止的子进程,都会调用对每个终止的子进程,都会调用对每个终止的子进程,都会调用waitwait函数,获取其终止状态函数,获取其终止状态函数,获取其终止状态函数,获取其终止状态execexec类函数类函数进程调用进程调用execexec类函数用于在进程中执行另一个可类函数用于在进程中执行另一个可执行文件执行文件当进程调用当进程调用execexec类函数时,该进程执行的程序完类函数时,该进程执行的程序完全替换为新程序全替换为新程序而新程序则从其而新程序则从其mainmain函数开始执行函数开始执行execexec并不创建新进程,所以前后的进程并不创建新进程,所以前后的进程IDID并未改并未改变,变,execexec只是用一个全新的程序替换了当前进程只是用一个全新的程序替换了当前进程的正文、数据、堆和栈段的正文、数据、堆和栈段execlexecl函数函数函数原型函数原型int execl(const char *pathname, const char *arg0, ./*(char*)0*/);(unistd.h)返回值与参数返回值与参数出错返回-1,成功不返回值pathname:要执行程序的绝对路径名可变参数:要执行程序的命令行参数,以“(char *)0”结束execlexecl函数示例函数示例#include#include#includeintmain(void)printf(enteringmainprocess-n);execl(/bin/ls,ls,-l,NULL);printf(exitingmainprocess-n);return0;execvexecv函数函数execvexecv函数函数int execv(const char *pathname, char *const argv);返回值与参数返回值与参数出错返回-1,成功不返回值pathname:要执行程序的绝对路径名argv:数组指针维护的程序参数列表,该数组的最后一个成员必须为NULLexecvexecv函数示例函数示例#include#include#includeintmain(void)printf(enteringmainprocess-n);intret;char*argv=ls,-l,NULL;ret=execv(“/bin/ls,argv);if(ret=-1)perror(execlerror);printf(exitingmainprocess-n);return0;execleexecle函数函数execleexecle函数函数int execle(const char *pathname, const char *arg0,./* (char*)0, char *const envp */);返回值与参数返回值与参数出错返回-1,成功不返回值pathname:要执行程序的绝对路径名可变参数:要执行程序的命令行参数,以“(char *)0”结束envp指向环境字符串指针数组的指针execleexecle函数示例函数示例#include#include#includeintmain()char*envp=“PATH=/tmp”,“USER=shan”,NULL;if(fork()=0)if(execle(“/bin/ls”,“ls”,“-l”,NULL,envp)0)perror(execleerror!);return0;其他其他execexec类函数类函数execveexecve函数函数int execve(const char *pathname, char *const argv, char *const envp);execlpexeclp函数函数int execlp(const char *filename, const char *arg0, ./* (char*)0*/);Filename参数仅需指定要执行的文件名(其路径信息从环境变量PATH中获取)例如:PATH=/bin:/usr/bin:/usr/local/bin/execvpexecvp函数函数int execvp(const char *filename, char *const argv);execexec类函数类函数execl execle execlpexecl execle execlpexecv execve execvpexecv execve execvp六个函数开头均为六个函数开头均为execexec,所以称为,所以称为execexec类函数类函数l:表示list,即每个命令行参数都说明为一个单独的参数v:表示vector,命令行参数放在数组中e:调用者提供环境表p:表示通过环境变量PATH来指定路径,查找可执行文件execexec类函数之间的关系类函数之间的关系execvpexeclpexeclexecleexecvexecveexecve( (系统调用系统调用) )buildbuildargvargvbuildargvbuildargvTryeachTryeachPATHprefixPATHprefixuseuseenvironenvironbuildbuildargvargv2024/7/3081exec类函类函数比较数比较函数使用文件名使用路径名使用参数表(函数出现字母l)使用argv(函数出现字母v) execl execlp execle execvexecvp execvesystemsystem函数函数用于执行一个用于执行一个shellshell命令命令函数原型函数原型 int system(const char* cmdstring);cmdstringcmdstring:shellshell命令命令systemsystem是通过是通过forkfork、execleexecle、waitpidwaitpid等实现的,等实现的,因此有三种返回值因此有三种返回值即fork失败,execle失败,waitpid失败systemsystem函数源代码分析函数源代码分析intsystem(constchar*cmdstring)pid_tpid;intstatus;if(cmdstring=NULL)return(1);/如果cmdstring为空,返回非零值,一般为1if(pid=fork()0) status=-1;/fork失败,返回-1elseif(pid=0) execl(/bin/sh,sh,-c,cmdstring,(char*)0); _exit(127);/exec执行失败返回127,注意exec只在失败时才返回现在的代码,成功的话现在的代码就不会运行else/父进程while(waitpid(pid,&status,0)0)if(errno!=EINTR)status=-1;/如果waitpid被信号中断,则返回-1break; returnstatus;/如果waitpid成功,则返回子进程的返回状态用户标识用户标识getlogingetlogin函数可以获取当前用户的登录名函数可以获取当前用户的登录名函数原型函数原型char *getlogin();返回值返回值成功返回登录名当调用此函数的进程没有连接到用户登录时所用的终端,则本函数会失败,返回NULL(这种进程通常称为守护进程)进程时间进程时间timestimes函数用于获取墙上时钟时间、用户函数用于获取墙上时钟时间、用户CPUCPU时间、时间、系统系统CPUCPU时间时间函数原型函数原型clock_t times(struct tms *buf);timestimes填写填写bufbuf指向的指向的tmstms结构结构struct tms clock_t tms_utime; /用户CPU时间 clock_t tms_stime; /系统CPU时间 clock_t tms_cutime; clock_t tms_cstime;进程组进程组每个进程除了有一个进程每个进程除了有一个进程IDID外,还属于一个进程组外,还属于一个进程组进程组是一个或多个进程的集合。通常,它们与同进程组是一个或多个进程的集合。通常,它们与同一作业关联,可以接收来自同一终端的各种信号。一作业关联,可以接收来自同一终端的各种信号。每个进程组有一个唯一的进程组每个进程组有一个唯一的进程组IDID每个进程组都可以有一个组长进程;组长进程的标每个进程组都可以有一个组长进程;组长进程的标识是:其进程组识是:其进程组IDID等于组长进程等于组长进程IDID进程组进程组只要进程组中还有一个进程存在,则进程组就存只要进程组中还有一个进程存在,则进程组就存在,与组长进程存在与否无关在,与组长进程存在与否无关从进程组创建开始,到其中最后一个进程离开为从进程组创建开始,到其中最后一个进程离开为止的时间区间,称为进程组的生存期止的时间区间,称为进程组的生存期进程组中的最后一个进程可以终止,或者转移到进程组中的最后一个进程可以终止,或者转移到另一个进程组另一个进程组setpgidsetpgid函数函数用于加入一个现有的进程组或者创建一个新进程组用于加入一个现有的进程组或者创建一个新进程组函数原型函数原型int setpgid(pid_t pid, pid_t pgid);该函数将进程该函数将进程IDID为为pidpid的进程的进程组的进程的进程组IDID,设置为,设置为pgidpgid若若pid=pgidpid=pgid,则,则pidpid代表的进程将变为进程组组长代表的进程将变为进程组组长setpgidsetpgid函数函数若若pid=0pid=0,则使用调用者的进程,则使用调用者的进程IDID若若pgid=0pgid=0,则由,则由pidpid指定的进程指定的进程IDID将用作进程组将用作进程组IDID注意:一个进程只能为它自己或它的子进程设置注意:一个进程只能为它自己或它的子进程设置进程组进程组IDID。在子进程调用。在子进程调用execexec函数之后,父进程函数之后,父进程就不能再改变该子进程的进程组就不能再改变该子进程的进程组IDIDgetpgrpgetpgrp函数函数用于获取调用进程的进程组用于获取调用进程的进程组IDID函数原型函数原型pid_t getpgrp();返回值返回值返回调用进程的进程组ID会话会话会话是一个或多个进程组的集合会话是一个或多个进程组的集合一次登录形成一个会话,开始于用户登录,终止于一次登录形成一个会话,开始于用户登录,终止于用户退出用户退出 ShellShell上的一条命令形成一个进程组上的一条命令形成一个进程组lproc1 | proc2 &lproc3 | proc4 | proc5登录登录登录登录shellshellproc1proc1proc2proc2proc3proc3proc4proc4proc5proc5processgroupprocessgroupprocessgroup内容内容UNIX&Linux进程环境及进程属性进程环境及进程属性UNIX&Linux进程管理及控制进程管理及控制UNIX&Linux线程概念线程概念UNIX&Linux线程控制线程控制2024年7月30日93线程线程的的基本概念基本概念线程是进程内的独立执行实体和调度单元,创建线程比进线程是进程内的独立执行实体和调度单元,创建线程比进程快程快10100倍倍一个进程内的所有线程共享相同的全局内存、全局变量等一个进程内的所有线程共享相同的全局内存、全局变量等信息(这种机制又带来了同步问题)信息(这种机制又带来了同步问题)共享信息共享信息私有信息私有信息l进程指令线程IDl大多数数据寄存器集合(包括PC和栈指针)l打开的文件栈(用于存放局部变量)l信号处理程序和信号处置 errorl当前工作目录信号掩码l用户ID和组ID优先级l地址空间2024/7/3094线程与进程的对比线程与进程的对比2024/7/3095线线程与进程的对比程与进程的对比线程自己基本上不拥有系统资源,只拥有少量在运行中必线程自己基本上不拥有系统资源,只拥有少量在运行中必不可少的资不可少的资源源l程序计数器:标识当前线程执行的位置;l一组寄存器:当前线程执行的上下文内容;l栈:用于实现函数调用、局部变量。因此,局部变量是私有的;l线程信号掩码:因此可以设置每线程阻塞的信号,见本书下一章内容;l局部线程变量:在栈中申请的数据;l线程私有数据进进程在使用时占用了大量的内存空间,特别是进行进程间程在使用时占用了大量的内存空间,特别是进行进程间通信时一定要借助操作系统提供的通信机制,这使得进程通信时一定要借助操作系统提供的通信机制,这使得进程有自身的弱有自身的弱点;线点;线程占用资源少,使用灵活,很多应用程程占用资源少,使用灵活,很多应用程序中都大量使用线程,而较少的使用多进序中都大量使用线程,而较少的使用多进程程线线程不能脱离进程而存在程不能脱离进程而存在,线,线程的层次关系,执行顺序并程的层次关系,执行顺序并不明显,对于初学者大量使用线程会增加程序的复杂不明显,对于初学者大量使用线程会增加程序的复杂度度2024/7/3096进程进程/线线程控制操作对程控制操作对比比应用功能线程进程创建pthread_createfork,vfork退出pthread_exitexit等待pthread_joinwait、waitpid取消/终止pthread_cancelabort读取IDpthread_self()getpid()调度策略SCHED_OTHER、SCHED_FIFO、SCHED_RRSCHED_OTHER、SCHED_FIFO、SCHED_RR通信机制信号量、信号、互斥锁、条件变量、读写锁无名管道、有名管道、信号、消息队列、信号量、共享内存97线线程程ID典型的典型的UNIX进程,可以看成是只有一个线程的进进程,可以看成是只有一个线程的进程程同进程一样,每个线程也有一个线程同进程一样,每个线程也有一个线程ID进程进程ID在整个系统中是唯一的,但线程在整个系统中是唯一的,但线程ID不同,不同,线程线程ID只在它所属的进程环境中有效只在它所属的进程环境中有效线程线程ID的类型是的类型是pthread_t,在,在Linux中的定义:中的定义:l在/usr/include/bits/pthreadtypes.h中ltypedefunsignedlongintpthread_t;98获取线程获取线程IDpthread_self函数可以使调用线程获取自己的线函数可以使调用线程获取自己的线程程ID函数原型函数原型#includepthread_tpthread_self();返回调用线程的线程返回调用线程的线程ID99比较线程比较线程IDLinux中使用整型表示线程中使用整型表示线程ID,而其他系统则不一,而其他系统则不一定定FreeBSD5.2.1、MacOSX10.3用一个指向用一个指向pthread结构的指针来表示结构的指针来表示pthread_t类型。类型。为了保证应用程序的可移植性,在比较两个线程为了保证应用程序的可移植性,在比较两个线程ID是否相同时,可以使用是否相同时,可以使用pthread_equal函数函数100pthread_equal函数函数该函数用于比较两个线程该函数用于比较两个线程ID是否相同是否相同函数原型函数原型#includeintpthread_equal(pthread_ttid1,pthread_ttid2);若相等则返回非若相等则返回非0值,否则返回值,否则返回0内容内容UNIX&Linux进程环境及进程属性进程环境及进程属性UNIX&Linux进程管理及控制进程管理及控制UNIX&Linux线程概念线程概念UNIX&Linux线程控制线程控制102线程的创建线程的创建pthread_create函数用于创建一个线程函数用于创建一个线程函数原型函数原型#includeintpthread_create(pthread_t*restricttidp,constpthread_attr_t*restrictattr,void*(*start_rtn)(void*),void*restrictarg);参数与返回值参数与返回值ltidp:当pthread_create成功返回时,该函数将线程ID存储在tidp指向的内存区域中103pthread_create函数函数参数与返回值参数与返回值lattr:用于定制各种不同的线程属性,将在后面部分讨论。通常可设为NULL,采用默认线程属性lstart_rtn:线程的启动例程,即新创建的线程从该函数开始执行。该函数只有一个参数,即arglarg:作为start_rtn的第一个参数l成功返回0,出错时返回各种错误码104线程的终止线程的终止线程终止的概念线程终止的概念pthread_exit函数函数pthread_join函数函数pthread_cancel函数函数线程清理处理函数线程清理处理函数pthread_detach函数函数105线程的终止线程的终止进程中任一线程调用进程中任一线程调用exit、_Exit、_exit,都会导,都会导致整个进程终止致整个进程终止当线程接收到信号,若信号的处理动作是终止进当线程接收到信号,若信号的处理动作是终止进程,则进程将被终止程,则进程将被终止如何只终止某个线程,而不终止整个进程?如何只终止某个线程,而不终止整个进程?106线程的终止线程的终止单个线程的三种退出方式单个线程的三种退出方式l线程从启动例程中返回,返回值是线程的退出码l线程被同一进程中的其他线程取消l线程调用pthread_exit函数107pthread_exit函数函数该函数让线程退出该函数让线程退出#includevoidpthread_exit(void*rval_ptr);参数参数lrval_ptr:该指针将传递给pthread_join函数(与exit函数参数用法类似)108pthread_join函数函数该函数用于等待某个线程终止该函数用于等待某个线程终止函数原型函数原型#includeintpthread_join(pthread_tthread,void*rval_ptr);调用该函数的线程将一直被阻塞,直到指定的线调用该函数的线程将一直被阻塞,直到指定的线程调用程调用pthread_exit从启动例程中返回从启动例程中返回109pthread_join函数函数intpthread_join(pthread_tthread,void*rval_ptr);返回值与参数返回值与参数l成功返回0,否则返回错误编号lthread:需要等待的线程IDlrval_ptr:若线程从启动例程返回,rval_ptr将包含返回码若线程由于pthread_exit终止,rval_ptr即pthread_exit的参数若线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED若不关心线程返回值,可将该参数设置为NULL110pthread_cancel函数函数线程调用该函数可以取消同一进程中的其他线程,线程调用该函数可以取消同一进程中的其他线程,即让线程终止即让线程终止函数原型函数原型#includeintpthread_cancel(pthread_ttid);参数与返回值参数与返回值ltid:需要取消的线程IDl成功返回0,出错返回错误编号111pthread_cancel函数函数在默认情况下,在默认情况下,pthread_cancel函数会使得线程函数会使得线程ID等于等于tid的线程,如同其调用了参数为的线程,如同其调用了参数为PTHREAD_CANCELED的的pthread_exit线程可以选择忽略取消方式或者控制取消方式,线程可以选择忽略取消方式或者控制取消方式,将在后面讨论将在后面讨论pthread_cancel并不等待线程终止,它仅仅是提并不等待线程终止,它仅仅是提出请求出请求112线程清理处理函数线程清理处理函数当线程终止时,可以调用自定义的线程清理处理函数,当线程终止时,可以调用自定义的线程清理处理函数,进行资源释放等操作。类似于进行资源释放等操作。类似于atexit函数。函数。线程可以注册多个清理处理函数,这些函数被记录在线程可以注册多个清理处理函数,这些函数被记录在栈中,它们的执行顺序与它们的注册顺序相反栈中,它们的执行顺序与它们的注册顺序相反线程清理处理函数的注册:线程清理处理函数的注册:#includevoidpthread_cleanup_push(void(*rtn)(void*),void*arg);113pthread_cleanup_push函数函数参数参数lrtn:清理函数,无返回值,包含一个类型为指针的参数larg:当清理函数被调用时,arg将传递给清理函数清理函数被调用的时机清理函数被调用的时机l调用pthread_exit时l响应取消请求时l以非0参数调用pthread_cleanup_pop时114线程清理函数线程清理函数pthread_cleanup_push必须和必须和pthread_cleanup_pop成对出现,而且出现的地成对出现,而且出现的地方必须在同一个作用域内方必须在同一个作用域内函数原型函数原型l#includelvoidpthread_cleanup_pop(intexecute);115pthread_cleanup_pop函数函数为了与为了与pthread_cleanup_push函数配对,参数函数配对,参数execute应设置为应设置为0注意注意pthread_exit,pthread_cancel的调用,必须的调用,必须在在pthread_cleanup_push和和pthread_cleanup_pop之间之间116线程退出与清理示例线程退出与清理示例voidcleanup()printf(“cleanupn”);void*test_cancel(void)pthread_clean_push(cleanup,NULL);printf(“test_canceln”);while(1)printf(“testmessagen”);sleep(1);pthread_cleanup_pop(1);Intmain()phtread_ttid;phtread_create(&tid,NULL,(void*)test_cancel,NULL);sleep(2);phread_cancel(tid);pthread_join(tid,NULL);117pthread_detach函数函数在任何一个时间点上,线程是可结合的在任何一个时间点上,线程是可结合的(joinable)或者是分离的()或者是分离的(detached)。一个)。一个可结合的线程能够被其他线程收回其资源和杀死。可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它被其他线程回收或杀死的,它的存储器资源在它终止时由终止时由系统系统自动释放。自动释放。若线程已经处于分离状态,线程的底层存储资源若线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回可以在线程终止时立即被收回当线程被分离时,并不能用当线程被分离时,并不能用pthread_join函数等函数等待它的终止状态,此时待它的终止状态,此时pthread_join返回返回EINVALpthread_detach函数可以使线程进入分离状态函数可以使线程进入分离状态118pthread_detach函数函数函数原型函数原型#includeintpthread_detach(pthread_ttid);参数与返回值参数与返回值ltid:进入分离状态的线程的IDl成功返回0,出错返回错误编号119线程属性线程属性前面讨论前面讨论pthread_create时,针对线程属性,传时,针对线程属性,传入的参数都是入的参数都是NULL。实际上,可以通过构建实际上,可以通过构建pthread_attr_t结构体,结构体,设置若干线程属性设置若干线程属性要使用该结构体,必须首先对其进行初始化;使要使用该结构体,必须首先对其进行初始化;使用完毕后,需要销毁它用完毕后,需要销毁它2024/7/30120线程属线程属性性POSIX规定的一些线程属性规定的一些线程属性121初始化和销毁初始化和销毁函数原型函数原型#includeintpthread_attr_init(pthread_attr_t*attr);intpthread_attr_destroy(pthread_attr_t*attr);参数与返回值参数与返回值l成功返回0,否则返回错误编号lattr:线程属性,确保attr指向的存储区域有效l为了移植性,pthread_attr_t结构对应用程序是不可见的,应使用设置和查询等函数访问属性2024/7/30122初始化线程属性对象初始化线程属性对象属性缺省值描述scopePTHREAD_SCOPE_PROCESS新线程与进程中的其他线程发生竞争detachstatePTHREAD_CREATE_JOINABLE线程可以被其它线程等待stackaddrNULL新线程具有系统分配的栈地址stacksize0新线程具有系统定义的栈大小priority0新线程的优先级为0inheritschedPTHREAD_EXPLICIT_SCHED新线程不继承父线程调度优先级schedpolicySCHED_OTHER新线程使用优先级调用策略123设置分离状态属性设置分离状态属性回忆什么是分离状态回忆什么是分离状态l若对现有的某个线程的终止状态不感兴趣,可以使用pthread_detach函数,使调用线程处于分离状态,以便让OS在线程退出时收回它所占的资源l而不需要其他线程调用pthread_join函数实际上,在创建线程时即可让线程以分离状态启实际上,在创建线程时即可让线程以分离状态启动动124设置分离状态设置分离状态函数原型函数原型#includeintpthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate);参数与返回值参数与返回值l成功返回0,否则返回错误编号lattr:线程属性结构体指针ldetachstate:PTHREAD_CREATE_DETACHED:以分离状态启动线程PTHREAD_CREATE_JOINABLE:正常启动线程(默认属性)125获取分离状态获取分离状态函数原型函数原型#includeintpthread_attr_getdetachstate(constpthread_attr_t*attr,int*detachstate);参数与返回值参数与返回值l成功返回0,否则返回错误编号lattr:线程属性结构体指针ldetachstate指向的整数,可被设置为以下值PTHREAD_CREATE_DETACHED:以分离状态启动线程PTHREAD_CREATE_JOINABLE:正常启动线程(默认属性)126线程属性线程属性不是所有的系统都支持线程栈属性,如何判断一不是所有的系统都支持线程栈属性,如何判断一个系统是否支持该属性?个系统是否支持该属性?l_POSIX_THREAD_ATTR_STACKADDRl_POSIX_THREAD_ATTR_STACKSIZE127获取线程栈属性获取线程栈属性函数原型函数原型#includeintpthread_attr_getstack(constpthread_attr_t*attr,void*stackaddr,size_t*stacksize);参数与返回值参数与返回值lattr:线程属性lstackaddr:该函数返回的线程栈的最低地址lstacksize:该函数返回的线程栈的大小l成功返回0,否则返回错误编号128设置线程栈属性设置线程栈属性函数原型函数原型#includeintpthread_attr_setstack(constpthread_attr_t*attr,void*stackaddr,size_t*stacksize);当用完线程栈时,可以再分配内存,并调用本函当用完线程栈时,可以再分配内存,并调用本函数设置新建栈的位置数设置新建栈的位置129设置线程栈属性设置线程栈属性参数与返回值参数与返回值lattr:线程属性lstackaddr:新栈的内存单元的最低地址,通常是栈的开始位置;对于某些处理器,栈是从高地址向低地址方向伸展的,stackaddr就是栈的结尾lstacksize:新栈的大小l成功返回0,否则返回错误编号130获取、设置栈大小获取、设置栈大小函数原型函数原型#includeintpthread_attr_getstacksize(constpthread_attr_t*attr,size_t*stacksize);intpthread_attr_setstacksize(pthread_attr_t*attr,size_tstacksize);若希望改变栈的默认大小,但又不想自己处理线若希望改变栈的默认大小,但又不想自己处理线程栈的分配问题,可以使用上述函数程栈的分配问题,可以使用上述函数131guardsize属性属性线程属性线程属性guardsize控制着线程栈末尾之后用以避控制着线程栈末尾之后用以避免栈溢出的扩展内存的大小免栈溢出的扩展内存的大小该属性的默认设置为该属性的默认设置为PAGESIZE个字节个字节注意:若对注意:若对stackaddr属性进行了修改,则系统属性进行了修改,则系统认为我们会自己管理栈,并使警戒栈缓冲区机制认为我们会自己管理栈,并使警戒栈缓冲区机制无效,即无效,即guardsize=0132设置、获取设置、获取guardsize属性属性函数原型函数原型#includeintpthread_attr_getguardsize(constpthread_attr_t*attr,size_t*guardsize);intpthread_attr_setguardsize(pthread_attr_t*attr,size_t*guardsize);若栈指针溢出到警戒区域,应用程序可能通过信若栈指针溢出到警戒区域,应用程序可能通过信号接收到出错信息号接收到出错信息2024/7/30133调度策略调度策略在操作系统中,调度策略主要有:在操作系统中,调度策略主要有:lFIFO先入先出原则:首先请求服务的对象首先得到CPU的处理。l最短作业优先原则:需要最小系统时间的服务首先得到处理。l最高优先级优先原则:优先级最高的服务首先得到处理。l时间轮片原则:每个任务分配一个系统时间片,轮流执行。POSIX为线程指定了三个调度策略:为线程指定了三个调度策略:lSCHED_OTHER:系统默认策略。如时间轮片策略或基于优先级的调度策略。lSCHED_FIFO:先进先出原则。具有最高优先级的、等待时间最长的线程将成为下一个要执行的线程。lSCHED_RR:轮转调度。类似于FIFO,但加上了时间轮片策略。2024/7/30134获取获取/设置线程属性调度属性设置线程属性调度属性2024/7/30135获取获取/设置调度策略属性设置调度策略属性136设置线程是否绑定设置线程是否绑定关于绑定的概念,牵涉到轻进程(关于绑定的概念,牵涉到轻进程(LWP:LightWeightProcess)的概念)的概念轻进程可以理解为内核线程,它位于用户层和系统层之间。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程现的,一个轻进程可以控制一个或多个线程默认状况下,启动多少轻进程、哪些轻进程来控制哪些线默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的程是由系统来控制的,这种状况即称为非绑定的绑定状况即某个线程固定的绑定状况即某个线程固定的绑绑在一个轻进程之上。被绑在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为定的线程具有较高的响应速度,这是因为CPU时间片的调时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候总度是面向轻进程的,绑定的线程可以保证在需要的时候总有一个轻进程可用有一个轻进程可用通过设置被绑定的轻进程的优先级和调度级可以使得绑定通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求的线程满足诸如实时反应之类的要求137设置线程是否绑定设置线程是否绑定获取线程绑定使用函数获取线程绑定使用函数pthread_attr_getscope,函数,函数原型如下:原型如下:lintpthread_attr_getscope(constpthread_attr_t*attr,int*contentionscope);设置线程绑定使用函数设置线程绑定使用函数pthread_attr_setscope,函数,函数原型是:原型是:lintpthread_attr_setscope(pthread_attr_t*attr,intcontentionscope);l第一个参数是指向属性结构的指针;l第二个参数是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)138获取获取/设置线程的优先级设置线程的优先级线程的优先级存放在结构线程的优先级存放在结构sched_param中(中(大的优先权值对应高的优先权)Structsched_paramint_sched_priority;通常修改线程的优先级是使用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存取,一般说来,先取优先级,对取得的值修改后再存放回去。获取线程的优先级函数pthread_attr_getschedparam,函数原型如下lintpthread_attr_getschedparam(constpthread_attr_t*attr,structsched_param*param);设置线程的优先级函数pthread_attr_setschedparam,函数原型如下lintpthread_attr_setschedparam(pthread_attr_t*attr,conststructsched_param*param);l第一个参数是指向属性结构的指针;l第二个参数就是结构sched_param,保存着线程的优先级。139线程属性操作示例代码线程属性操作示例代码#include#includeintmain(void)intret;pthread_tpid;/*线程ID*/pthread_attr_tpattr;/*线程属性结构体*/structsched_paramparam;/*线程优先级结构体*/pthread_attr_init(&pattr);/*初始化线程属性对象,这时是默认值*/pthread_attr_setscope(&pattr,PTHREAD_SCOPE_SYSTEM);/*设置线程绑定*/pthread_attr_getschedparam(&pattr,¶m);/*修改线程优先级*/param.sched_priority=20;pthread_attr_setschedparam(&pattr,¶m);/*使用设置好的线程属性来创建一个新的线程*/ret=pthread_create(&pid,&pattr,(void*)thread,NULL);140课堂作业课堂作业分析以下代码在实际系统中运行时可能出现什么问题,为什么?分析以下代码在实际系统中运行时可能出现什么问题,为什么?staticvoidcharactertime(char*);intmain(void)pid_tpid;If(pid=fork()0)err_sys(“forkerror”);elseif(pid=0)charactertime(“outputfromchildn”;)elsecharactertime(“outputfromparentn”);exit(0);staticvoidcharactertime(char*str)char*ptr;Intc;setbuf(sdtout,NULL);for(ptr=str;(c=*ptr+)!=0;)putc(c,stdout);实验四:进程控制实验实验四:进程控制实验实验目的:实验目的:lLinux系统创建进程的方式l在代码中如何区别父子进程l父子进程之间的资源共享与异同l等待子进程执行结束的方法l在进程中执行另外一个可执行文件的方法实验四:进程控制实验实验四:进程控制实验实验内容:实验内容:l基于实验二实现的lsl命令代码和实验三实现的cpr命令代码l将lsl作为主进程运行lsl在遍历目录时,每获取一个目录文件的路径名,就创建一个子进程来运行cpr命令(将目录文件的路径名作为参数传递给cpr命令)通过编程,在/home目录下以自己的名字的汉语拼音创建一个目录,将lsl遍历目录中的文件作为cpr命令的源文件,将其复制到所创建目录中主进程等待子进程运行结束,回收其内核空间资源循环往复,直到lsl遍历完成实验五:线程控制实验实验五:线程控制实验实验目的:实验目的:l掌握Linux系统创建线程的方式l理解线程例程的设计思想l掌握线程退出的方式l掌握线程之间共享数据的方法l掌握等待线程终止的方式实验五:线程控制实验实验五:线程控制实验实验内容:实验内容:l将实验二实现的lsl命令代码和实验三实现的cpr命令代码改造为两个线程例程l在main函数中首先创建lsl线程例程并运行lsl在遍历目录时,每获取一个目录文件的路径名,就创建一个cp线程例程(可以将目录文件的路径名作为cp线程例程参数传递给cp线程例程也可以通过全局变量的方式共享给cp线程)通过编程,在/home目录下以自己的名字的汉语拼音创建一个目录,将lsl遍历目录中的文件作为cp线程的源文件,将其复制到所创建目录中lsl线程等待cp线程运行结束,回收其内核空间资源循环往复,直到lsl遍历完成
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号