目录
1 进程在内核中的组织
1.1 进程在内核的组织形式:进程控制块(PCB)(初始化信息)
1.1.1 Linux进程控制块:task_struct结构
1.1.2 task_struct:进程状态
1.1.3 task_struct:文件管理
1.1.4 task_struct:内存管理
1.1.5 进程在内核中的组织形式
2 进程属性
2.1 进程基本属性-进程ID
2.2 真实用户和有效用户的关系
2.3 有效用户和真实用户实例
2.4 普通用户能够修改自己的密码的原因
3 进程生命周期
3.1 进程的启动
3.2 进程的终止
3.3 进程的生命周期
3.3.1 终止进程的函数
3.3.2 exit与return的区别
3.3.3 注册终止处理函数
4 进程环境
4.1 进程内存空间布局
4.1.1 命令行参数
4.1.2 C程序中main函数参数
4.1.3 环境变量表
4.1.4 设置环境变量(putenv、setenv、unsetenv)
5 创建进程
5.1 创建进程
5.2 创建子进程
5.2.1 创建子进程示例
5.2.2 fork函数工作流程
5.2.3 fork函数执行后父子进程的主要异同
5.2.4 父子进程共享文件
5.2.5 fork的用法
5.2.6 vfork函数
6 获知子程序运行状态改变
6.1 僵尸进程
6.2 wait函数
6.3 waitpid函数
7 在进程中运行可执行文件
7.1 exec系列函数
7.2 execl函数
7.3 execv函数
7.4 execle函数
7.5 其他exec函数
7.6 exec类函数之间的关系
8 Linux线程控制
8.1 线程的基本概念
8.2 线程与进程的对比
8.3 线程ID
8.4 线程的创建
8.5 线程的终止
8.6 取消线程
8.7 父线程等待子线程终止
8.7.1 pthread_join函数
1 进程在内核中的组织
CPU载入程序到内存—>CPU初始化程序—>CPU进程调度程序
1.1 进程在内核的组织形式:进程控制块(PCB)(初始化信息)
初始化:进程ID,用户ID,进程状态,调度信息,文件管理,虚拟内存管理,信号,时间和定时器…
1.1.1 Linux进程控制块:task_struct结构
1.1.2 task_struct:进程状态
1.1.3 task_struct:文件管理
1.1.4 task_struct:内存管理
1.1.5 进程在内核中的组织形式
2 进程属性
2.1 进程基本属性-进程ID
2.2 真实用户和有效用户的关系
2.3 有效用户和真实用户实例
2.4 普通用户能够修改自己的密码的原因
3 进程生命周期
3.1 进程的启动
◼C程序的启动函数是main,也是进程代码的入口点
- main ( int argc, char *argv[] );
◼当内核启动C程序时,会在调用main函数前调用特殊的启动函数来获取main函数地址和传递给main函数的参数,并且将这些信息填写到进程控制块中
3.2 进程的终止
◼正常终止
- 从main函数中返回
- 在任意代码中调用exit函数或_exit函数
- 最后一个线程从其启动例程中返回
- 最后一个线程调用pthread_exit函数
◼异常终止
- 在任意代码中调用abort函数
- 接收到终止信号
3.3 进程的生命周期
3.3.1 终止进程的函数
◼头文件stdlib.h ,函数定义:void exit( int status )◼头文件unistd.h,函数定义:void _exit (int status )◼调用这两个函数均会正常地终止一个进程◼调用_exit 函数将会立即返回内核◼调用exit 函数:
- 执行预先注册的终止处理函数
- 执行文件I/O操作的善后工作,使得所有缓冲的输出数据被更新到相应的设备
- 返回内核
3.3.2 exit与return的区别
- return是C语言关键字,exit是POSIX API函数
- 在main函数中,执行return和调用exit函数会产生相同的效果
- 在子函数中,执行return仅仅从子函数中返回,而调用exit函数将会退出当前进程
3.3.3 注册终止处理函数
- 当进程终止时,程序可能需要进行一些自身的清理工作,如日志登记、资源释放等
- 通过atexit函数或on_exit函数允许进程注册若干终止处理函数,当进程终止时,这些终止处理函数将会被自动调用
◼头文件stdlib.h
- int atexit(void (*func)(void));
- int on_exit (void (*func)(int, void *), void *arg);
◼ANSI C规定一个进程最多能注册32个终止处理函数◼当显示调用或者隐含调用exit函数终止进程(从main中返回、最后一个线程退出等)将会回调这些注册的终止处理函数(最先注册的函数最后被回调)◼显示调用_exit函数终止进程时将不会回调这些注册的终止函数 示例代码:atexit示例代码:on_exit
4 进程环境
4.1 进程内存空间布局
正文:CPU执行的代码部分,正文段通常是共享、只读的
初始化的数据:包含了程序中需明确赋初值的变量,如全局变量int maxcount=99
未初始化的数据:程序执行之前,将此段中的数据初始化为0,如全局变量long sum[1000]
栈/堆:用于动态分配内存
命令行参数和环境变量:主要用于支撑函数调用存放参数、局部变量等
4.1.1 命令行参数
◼ls [参数] <路径或文件名>
- ls –l /home
◼mkdir [参数] <目录名>
- mkdir -p /home/xiaokun/src
◼cp [参数] <源文件路径> <目标文件路径>
- cp –r /usr/local/src /root
4.1.2 C程序中main函数参数
4.1.3 环境变量表
◼每个进程都会有自己的环境变量表◼通过全局的环境指针(environ)可以直接访问环境变量表(字符串数组)
- 头文件unistd.h
- extern char **environ;
◼环境变量字符串形式为“name=value”,name是环境变量名称,value为环境变量赋值
4.1.4 设置环境变量(putenv、setenv、unsetenv)
◼设置环境变量的三种方法:
- putenv
- setenv
- unsetenv
◼putenv函数将环境变量字符串放入环境变量表中;若该字符串已经存在,则覆盖
- 头文件:stdlib.h
- int putenv(char *str);
◼setenv
- 头文件:stdlib.h
- int setenv(const char* name,const char* value, int rewrite);
- setenv将指定环境变量的值设置为参数指定值(更改环境变量字符串)
- 若name已经存在 ,rewrite不等于0时,则删除其原先的定义; rewrite等于0,则不删除其原先的定义。
◼unsetenv
- 头文件:stdlib.h
- int unsetenv(const char* name);
- 删除指定的环境变量字符串
5 创建进程
5.1 创建进程
◼Linux中创建进程的方式:
- 在shell中执行命令或可执行文件(由shell进程调用fork函数创建子进程)
- 在代码中(已经存在的进程中)调用fork函数创建子进程 (通过fork函数创建的进程为已经存在进程的子进程)
◼Linux系统中进程0(PID=0)是由内核创建,其他所有进程都是由父进程调用fork函数所创建的◼Linux系统中进程0在创建子进程(PID=1,init进程)后,进程0就转为交换进程或空闲进程◼进程1(init进程)是系统中其他所有进程的共同祖先
5.2 创建子进程
◼函数原型
- 头文件:unistd.h
- pid_t fork(void);
◼返回值 ⚫fork函数被正确调用后,将会在子进程中和父进程中分别返回!!
- 在子进程中返回值为0(不合法的PID,提示当前运行在子进程中)
- 在父进程中返回值为子进程ID(让父进程掌握所创建子进程的ID号)
⚫出错返回-1
5.2.1 创建子进程示例
5.2.2 fork函数工作流程
◼子进程是父进程的副本
- 子进程复制/拷贝父进程的PCB、数据空间(数据段、堆和栈)
- 父子进程共享正文段(只读)
◼子进程和父进程继续执行fork函数调用之后的代码◼为了提高效率,fork后不并立即复制父进程数据段、堆和栈,采用了写时复制机制(Copy-On-Write)
- 当父子进程任意之一要修改数据段、堆、栈时,进行复制操作,并且仅复制修改区域
5.2.3 fork函数执行后父子进程的主要异同
5.2.4 父子进程共享文件
◼父子进程对共享文件的常见处理方式
- 父进程等待子进程完成。当子进程终止后,文件当前位置已经得到了相应的更新
- 父子进程各自执行不同的程序段,各自关闭不需要的文件
5.2.5 fork的用法
◼父进程希望复制自己(共享代码,复制数据空间),但父子进程执行相同代码中的不同分支
- 网络服务程序中,父进程等待客户端的服务请求,当请求达到时,父进程调用fork创建子进程处理该请求,而父进程继续等待下一个服务请求到达
◼父子进程执行不同的可执行文件(父子进程具有完全不同的代码段和数据空间)
- 子进程从fork返回后,立即调用exec类函数执行另外一个可执行文件
5.2.6 vfork函数
◼vfork用于创建新进程,而该新进程的目的是执行另外一个可执行文件◼由于新程序将有自己的地址空间,因此vfork函数并不将父进程的地址空间完全复制到子进程中◼子进程在调用exec或exit之前,在父进程的地址空间中运行◼vfork函数保证子进程先执行,在它调用exec或者exit之后,父进程才会继续被调度执行(父进程处于TASK_UNINTERRUPTIBLE状态)
6 获知子程序运行状态改变
◼当一个进程发生特定的状态变化(进程终止、暂停以及恢复)时,内核向其父进程发送SIGCHLD信号◼父进程可以选择忽略该信号,也可以对信号进行处理(默认处理方式为忽略该信号)◼wait或waitpid函数可以用于等待子进程状态信息改变,并获取其状态信息
6.1 僵尸进程
◼进程在退出之前会释放进程用户空间的所有资源,但PCB等内核空间资源不会被释放
- 当父进程调用wait或waitpid函数后,内核将根据情况关闭该进程打开的所有文件,释放PCB(释放内核空间资源)
- 对于已经终止但父进程尚未对其调用wait或waitpid函数的进程( TASK_ZOMBIE状态),称为僵尸进程
◼如果父进程在子进程终止之前终止,则子进程的父进程将变为init进程,保证每个进程都有父进程,由init进程调用wait函数进行善后
6.2 wait函数
◼功能:获取任意子进程的状态改变信息(如果是终止状态则对子进程进行善后处理) ◼函数原型
- 头文件:sys/wait.h
- pid_t wait(int *statloc);
◼参数与返回值
- statloc:用于获取子进程的状态改变
- 返回值:若成功返回状态信息改变子进程ID,出错返回-1
◼参数statloc
- statloc可以为空指针,此时父进程不需要具体了解子进程的状态变化,只是为了防止子进程成为僵尸进程,或者因为同步原因需等待子进程终止
- 若statloc不是空指针,则内核将子进程状态改变信息存放在它指向的存储空间中
◼子进程状态改变信息包含了多种类型的信息,可以通过系统提供的宏来快速解析子进程的状态◼调用wait函数之后,父进程可能出现的情况
- 如果所有子进程都还在运行,则父进程被阻塞(TASK_INTERRUPTIBLE状态),直到有一个子进程终止或暂停,wait函数才返回
- 如果已经有子进程进入终止或暂停状态,则wait函数会立即返回
- 若进程没有任何子进程,则立即出错返回-1
◼如果一个进程有几个子进程,那么只要有一个子进程状态改变,wait函数就返回 ◼如何才能使用wait函数等待某个特定子进程的状态改变?
- 调用wait,然后将其返回的进程ID和所期望的子进程ID进行比较
- 如果ID不一致,则保存该ID,并循环调用wait函数,直到等到所期望的子进程ID为止
6.3 waitpid函数
◼功能:等待某个特定子进程状态改变 ◼函数原型
- 头文件:sys/wait.h
- pid_t waitpid(pid_t pid, int *statloc, int options);
◼返回值
- 成功返回终止子进程ID,失败返回-1
◼参数 ⚫pid
- pid == -1:等待任意子进程执行终止(同wait)
- pid > 0:等待进程ID为pid的子进程执行终止
- pid == 0:等待其组ID等于调用进程组ID的任意子进程
- pid < -1:等待其组ID等于pid绝对值的任意子进程
⚫statloc:存放子进程终止状态⚫options:可以为0,也可以是以下常量或常量的或
- WCONTINUED:如果有暂停的进程由于SIGCONT信号的到来而继续运行,则函数将返回
- WUNTRACED:如果有处于终止状态的进程,则函数返回
- WNOHANG:如果没有任何已经终止的子进程则马上返回, 函数不等待,此时返回值为0
waitpid的特有功能 ◼waitpid可等待一个特定的进程的状态改变信息 ◼waitpid可以实现非阻塞的等待操作,有时希望取得子进程的状态改变信息,但不希望阻塞等待子进程状态改变◼waitpid支持作业控制(进程组控制 )
7 在进程中运行可执行文件
7.1 exec系列函数
◼进程调用exec系列函数在进程中加载执行另外一个可执行文件◼exec系列函数替换了当前进程(执行该函数的进程)的正文段、数据段、堆和栈(来源于加载的可执行文件)◼执行exec系列函数后从加载可执行文件的main函数开始重新执行◼exec系列函数并不创建新进程,所以在调用exec系列函数后其进程ID并未改变,已经打开的文件描述符不变◼execl execle execlp execv execve execvp◼六个函数开头均为exec,所以称为exec系列函数
- l:表示list,每个命令行参数都说明为一个单独的参数
- v:表示vector,命令行参数放在数组中
- e:表示由函数调用者提供环境变量表
- p:表示通过环境变量PATH来指定路径,查找可执行文件
7.2 execl函数
◼函数原型
- 头文件:unistd.h
- int execl(const char *pathname,const char *arg0, …,NULL);
◼参数
- pathname:要执行程序的绝对路径名
- 可变参数:要执行程序的命令行参数,以空指针结束
◼返回值
- 出错返回-1
- 成功该函数不返回!
7.3 execv函数
◼函数原型
- 头文件:unistd.h
- int execv(const char *pathname, char *const argv[]);
◼参数
- pathname:要执行程序的绝对路径名
- argv:数组指针维护的程序命令行参数列表,该数组的最后一个成员必须为空指针
◼返回值
- 出错返回-1
- 成功该函数不返回
7.4 execle函数
◼函数原型
- 头文件:unistd.h
- int execle(const char *pathname, const char *arg0,… NULL, char *const envp[]);
◼参数
- pathname:要执行程序的绝对路径名
- 可变参数:要执行程序的命令行参数,以空指针结束
- envp指向环境字符串指针数组的指针,该数组的最后一个成员必须为空指针
◼返回值
- 出错返回-1
- 成功该函数不返回
7.5 其他exec函数
◼execve函数
- int execve(const char *pathname,char *const argv[], char *const envp[]);
◼execlp函数
- int execlp(const char *filename,const char *arg0, …,NULL);
- filename参数可以是相对路径(路径信息从环境变量PATH中获取)
- 例如默认环境变量中包含的PATH=/bin:/usr/bin:/usr/local/bin/
◼execvp函数
- int execvp(const char *filename,char *const argv[]);
7.6 exec类函数之间的关系
8 Linux线程控制
8.1 线程的基本概念
◼ 进程的概念体现出两个特点:资源(代码和数据空间、打开的文件等)以及调度/执行。线程是进程内的独立执行代码的实体和调度单元◼ 一个进程内的所有线程共享进程的很多资源(这种共享又带来了同步问题)
8.2 线程与进程的对比
◼线程只拥有少量在运行中必不可少的资源
- PC指针:标识当前线程执行的位置
- 寄存器:当前线程执行的上下文环境
- 栈:用于实现函数调用、局部变量(局部变量是私有的)
◼进程占用资源多,线程占用资源少,使用灵活 ◼线程不能脱离进程而存在,线程的层次关系,执行顺序并不明显,会增加程序的复杂度 ◼没有通过代码显示创建线程的进程,可以看成是只有一个线程的进程
8.3 线程ID
◼同进程一样,每个线程也有一个线程ID◼进程ID在整个系统中是唯一的,线程ID只在它所属的进程环境中唯一◼线程ID的类型是pthread_t,在Linux中的定义:
- typedef unsigned long int pthread_t( /usr/include/bits/pthreadtypes.h)
◼pthread_self函数可以让调用线程获取自己的线程ID ◼函数原型
- 头文件:pthread.h
- pthread_t pthread_self();
◼返回调用线程的线程ID
8.4 线程的创建
◼pthread_create函数用于创建一个线程 ◼函数原型
- 头文件:pthread.h
- int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
◼调用pthread_create函数的线程是所创建线程的父线程 ◼参数
- tidp:指向线程ID的指针,当函数成功返回时将存储所创建的子线程ID
- attr:用于指定线程属性(一般直接传入空指针NULL,采用默认线程属性)
- start_rtn:线程的启动例程函数指针,创建的线程首先执行该函数代码(可以调用其他函数)
- arg:向线程的启动例程函数传递信息的参数
◼返回值
- 成功返回0,出错时返回各种错误码
8.5 线程的终止
◼线程的三种终止方式
- 线程从启动例程函数中返回,函数返回值作为线程的退出码
- 线程被同一进程中的其他线程取消
- 线程在任意函数中调用pthread_exit函数终止执行
线程终止函数◼函数原型
- 头文件:pthread.h
- void pthread_exit(void *rval_ptr);
◼参数
- rval_ptr:该指针将参数传递给pthread_join函数(与exit函数参数用法类似)
8.6 取消线程
◼线程调用该函数可以取消同一进程中的其他线程(即让该线程终止) ◼函数原型
- 头文件: pthread.h
- int pthread_cancel(pthread_t tid);
◼参数与返回值
- tid:需要取消的线程ID
- 成功返回0,出错返回错误编号
◼在默认情况下,pthread_cancel函数与被取消线程(ID等于tid的线程)自身调用pthread_exit函数(参数为PTHREAD_CANCELED)效果等同◼线程可以选择忽略取消方式或者控制取消方式◼pthread_cancel并不等待线程终止,它仅仅是提出请求
8.7 父线程等待子线程终止
◼函数原型
- 头文件:pthread.h
- int pthread_join(pthread_t thread,void **rval_ptr);
◼调用该函数的父线程将一直被阻塞,直到指定的子线程终止 ◼返回值
- 成功返回0,否则返回错误编号
8.7.1 pthread_join函数
◼参数
- thread:需要等待的子线程ID
- rval_ptr:(若不关心线程返回值,可直接将该参数设置为空指针)
1.若线程从启动例程返回,rval_ptr将包含返回码 2.若线程被取消,rval_ptr指向的内存单元值置为PTHREAD_CANCELED 3.若线程通过调用pthread_exit函数终止,rval_ptr就是调用pthread_exit时传入的参数