linux 进程
来源:互联网 发布:enako 知乎 编辑:程序博客网 时间:2024/05/17 17:43
/*嵌入式,linux,进程,进程函数,进程的概念*/
>>多任务机制:
Linux允许用户在同一时间处理多个应用程序,这种机制称为“多任务”机制。
>>"多任务"的实现原理:
建立多个进程或线程,彼此之间可以独立运行不同的应用程序(也可以相互联系)。
>>进程分类:
1、交互式进程:与用户进行互动,及时响应用户的操作和命令(如监测鼠标键盘的进程、shell命令进程、文本编辑器等)。
2、守护进程:不与任何终端联系,一直在后台运行,一般在系统启动时开始执行,待系统关闭时才结束(如各类系统进程)。
3、批处理进程:不需要与用户进行及时互动,也不是系统运行所必须的,调度优先权很低,通常在后台运行(如数据搜索引擎、编译器等)。
>>进程组、会话:
1、进程组是一个或多个进程的集合,每个进程组都会有一个进程组长(组长进程ID=进程组ID,但进程组ID不会因组长进程的退出而收到影响)。
2、会话是一个或多个进程组的集合,会话的第一个进程组称为会话组长。
>>进程控制块:
1、对于多CPU的处理器来说,每个CPU可以运行不同的进程,这是真正意义上的“并行执行”。
2、实际应用中,进程数往往远多于CPU的个数,所以同一个CUP上各进程间实际上是按照时间片(一般是ms级)轮流执行的。
3、为了方便对进程的调度管理,所有进程的相关信息都被放在一个双向循环链表中(进程链表)。这个链表称为进程控制块(task_struct)。
4、进度块的内容很多,其中最重要的有两个段:进程状态(state)、进程标识符(pid)。
>>进程状态:
进程状态就是进程运行的阶段。可以分为三大类。
1、运行类状态:
1.1、正在运行。
1.2、即将开始运行:已经具备执行条件正在运行队列中等待内核调度——也称“就绪状态”。
2、阻塞类状态:
就是进程在“睡眠”,等待它所需要的资源可以满足其使用条件时,被“唤醒”进入运行状态。
2.1、可中断的阻塞状态:既能被显示地唤醒(一般调用wake_up系列宏)唤醒,又能被其他进程的信号唤醒(主动进入用sleep_on()函数)。
2.2、不可唤醒的阻塞状态:只能被指定的事件唤醒(如定时时间到、键盘有输入等),不能被其他进程的信号唤醒(主动进入用interruptible_sleep_on()函数)。
3、停止类状态:
3.1、暂停状态:当运行中的进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时,就会进入暂停状态。暂停状态的进程收到信号SIGCONT就会转为运行状态。
3.2、僵死状态:子进程运行结束,父进程(由于父进程正忙、父进程已亡等原因)尚未使用wait函数族回收其退出状态。
创建当前进程的进程称为其父进程。当前进程称为子进程。一般来讲,子进程结束运行时要向父进程返回一些信息已供父进程使用。
3.3、消亡状态:子进程运行结束,父进程已回收其结束状态,这时子进程将会被彻底删除,进入消亡状态。
进程子创建时起,就在不同状态间进行转换,直至最终消亡。
>>进程标识符(PID:process identity):
1、进程标识符就是进程的身份证号,它唯一的标识一个进程(用getpid()获得)。
2、父进程标识符(PPID:parent process identity)(用getppid()获得)。
3、对进程的操作一般都通过进程标识符。
>>进程的创建函数fork():
1、在当前进程中调用fork()函数,就会创建一个新的子进程。当前进程自动成为新进程的父进程。
2、fork()创建的子进程是对父进程的复制(几乎是父进程的所有相关数据都后被子进程复制下来,包括程序执行的进度等)。
3、父子进程的区分是通过fork()函数的返回值:
返回0:是子进程。
返回正整数(子进程的ID):是父进程。
4、例程:
int main(void)
{
pid_t child1;
child1 = fork();
if(ciild1 == -1)
{
error handling;
exit(1);
}
else if(child1 == 0)
{
subprocess program;
}
else
{
parentprocess program;
}
exit(0);
}
说明:
4.1、子进程一旦创建就会进入执行状态,进而和父进程竞争CUP的执行时间片(内核通过进程控制块来调度协调,用户也可根据需要调整父子进程间的执行顺序,如使父进程阻塞等手段)。
4.2、子进程从child1==0条件下开始执行,父进程从else下开始执行,两者并行(轮流获得CPU的执行权)。
>>子进程执行新程序的函数族exec
既然子进程时父进程的复制品,那么子进程存在的价值是什么呢?可以通过调用exec函数族来使子进程执行新的程序(一个典型的应用就是父进程监测标准输入,一旦有命令就创建一个子进程去响应,父进程本身继续监测)。
cexc函数族有6个原型(只有execve才是系统调用,其他都是在其基础上进行的二次封装):
1、int execl(const char *path, const char *arg, ......);
2、int execle(const char *path, const char *arg, ...... , char * const envp[]);
3、int execv(const char *path, char *const argv[]);
4、int execve(const char *filename, char *const argv[], char *const envp[]);
5、int execvp(const char *file, char * const argv[]);
6、int execlp(const char *file, const char *arg, ......);
以上函数族成功都返回文件的当前读写位置,失败返回-1。
根据函数名第5、6位的不同可以分为三类:
1、文件的查找方式不同:
有“P”时,只给出文件名即可;
无“P”时,要给出文件的“全路径名”。
2、命令行参数给出方式不同:
“v”,以数组指针形式给出(具体参数存储在指针指向的数组中)。
“l”,以列表方式给出参数;
3、环境变量的引用方式不同:
有“e”时,以数组指针形式给出(具体参数存储在指针指向的数组中);
无“e”时,默认引用当前环境变量。
例如:execve();用户给出执行文件的“全路径名”,以数组指针的方式给出所有执行参数,用户给定所有环境变量,所以只有execve才是基本函数。
使用exec函数族有两点需要注意:
1、无论以何种方式给出参数与环境变量,这两者都应以“NULL”结尾。
2、exec函数族的调用很容易失败,使用时一定要进行失败检查。
>>进程的退出:
1、exit()函数与return语句的区别:
return语句只是返回当前执行的函数(回到调用当前函数的母函数中继续执行)。
而exit()函数是退出整个进程(一般是退出一个完整的程序,可能包含多个函数的全部退出)。
2、exit()与_exit()的区别:
exit()是正常的进程退出,它要清理用户的I/O缓冲,吧用户缓冲区内的内容写回文件,并关闭所有打开的文件。
_exit()是暴力退出,什么都不处理,直接退出调用它的进程。
比如:标准输出采用行缓冲,遇到换行符"\n"时才会将缓冲区内容写入终端进行输出显示等。
一个没有换行符的printf("abc");语句,当遇到exit();时仍然会显示出来,而遇到_exit();就直接掉丢不会显示了。
3、参数的含义:
exit()与_exit()的参数都是整数,用以向父进程传递退出原因。参数0表示正常退出,其他数值对应各种错误(由用户定义,可以用正整数表示警告退出,负整数表示错误退出)。
>>进程的回收:
有些进程不是运行完了就算了,是要向父进程返回一些结果的。当然,父进程也可能“主动等待”子进程返回数据,这时候需要调用wait()函数族。
wait()是waitpid()的一个特例。
1、pid_t wait(int *status);
功能:阻塞父进程(调用其的进程),直到一个子进程结束或者父进程接到一个指定的信号为止。
返回值:成功返回第一个结束的子进程ID;失败返回-1(没有尚未结束的子进程)。
参数:status指向一个用来保存子进程结束时状态的整形对象。
2、pid_t waitpid(pid_t pid, int *status, int options);
功能:
返回值:>0表示已结束运行的子进程ID;0表示使用选项WNOHANG且没有子进程结束;-1出错(并设置errno)。
参数pid:>0表示待回收的子进程ID;=0表示回收同组的任意一个子进程;=-1表示回收任何一个子进程(此时等同于wait()函数);<-1表示回收进程组号等于-pid的任意子进程。
参数status:同wait()。
参数options:=WNOHANG表示若没有子进程结束,也不阻塞父进程,而是立即返回0;=WUNTRACED返回已暂停但尚未报告的子进程状态。
3、如果一个子进程已经结束,但父进程还没有调用wait()函数族来对其进行回收,那么这个子进程的结束状态将会被保留,此时子进程就变成了一个“僵尸进程”(未完全死掉)。
>>守护进程(daemon):
1、守护进程不受用户、终端或者其他变化的影响(用户可以把一个普通进程变成守护进程)。
2、linux的很多系统服务都是通过守护进程实现的。
3、编写守护进程需要5步:
1>创建子进程,父进程退出
此时子进程已经摆脱了父进程的管控,成为了“孤儿进程”,系统检测到孤儿进程会将其作为1号进程(init进程)收养。
2>在子进程中创建一个新会话(调用setsid()函数)
由于子进程从父进程那里继承了会话、进程组和控制终端,为了摆脱这些对子进程的控制,需要创建一个新的会话来摆脱它们。
3>改变当前目录(调用chdir()函数)
由于子进程的存在,其所在的文件系统可能无法卸载(比如子进程从属于/mnt/usb等),所以需要将当前目录改为根目录"/"或者临时目录"/tmp"。
4>重设文件权限掩码(调用umask(0)函数)
子进程从父进程那里继承来的文件权限可能是只读,这样对子进程的后续操作带来了隐患(文件的实际权限=创建权限-掩码权限)。
5>关闭文件描述符
如果父进程里已经打开了没写文件,子进程也一并将其继承,而这些打开的文件对子进程来说可能是一种多余(这些打开的文件也会导致所在的文件系统不能被卸载),需将其关闭。
for(i=0; i<MAXFILE; i++)
{
close(i);
}
>>进程间的通信概述:
1、管道进行通信
有名管道:可以在本地的任何进程间进行通信。
无名管道:只能在有亲缘关系的管道间进行通信(父子进程间、兄弟进程间)。
2、共享内存
是最高效的进程间通信方式,需要考虑同步与互斥问题。
3、其他进程间的通信方式
消息队列、信号量、信号、套接字(用于网络通信)。
>>多任务机制:
Linux允许用户在同一时间处理多个应用程序,这种机制称为“多任务”机制。
>>"多任务"的实现原理:
建立多个进程或线程,彼此之间可以独立运行不同的应用程序(也可以相互联系)。
>>进程分类:
1、交互式进程:与用户进行互动,及时响应用户的操作和命令(如监测鼠标键盘的进程、shell命令进程、文本编辑器等)。
2、守护进程:不与任何终端联系,一直在后台运行,一般在系统启动时开始执行,待系统关闭时才结束(如各类系统进程)。
3、批处理进程:不需要与用户进行及时互动,也不是系统运行所必须的,调度优先权很低,通常在后台运行(如数据搜索引擎、编译器等)。
>>进程组、会话:
1、进程组是一个或多个进程的集合,每个进程组都会有一个进程组长(组长进程ID=进程组ID,但进程组ID不会因组长进程的退出而收到影响)。
2、会话是一个或多个进程组的集合,会话的第一个进程组称为会话组长。
>>进程控制块:
1、对于多CPU的处理器来说,每个CPU可以运行不同的进程,这是真正意义上的“并行执行”。
2、实际应用中,进程数往往远多于CPU的个数,所以同一个CUP上各进程间实际上是按照时间片(一般是ms级)轮流执行的。
3、为了方便对进程的调度管理,所有进程的相关信息都被放在一个双向循环链表中(进程链表)。这个链表称为进程控制块(task_struct)。
4、进度块的内容很多,其中最重要的有两个段:进程状态(state)、进程标识符(pid)。
>>进程状态:
进程状态就是进程运行的阶段。可以分为三大类。
1、运行类状态:
1.1、正在运行。
1.2、即将开始运行:已经具备执行条件正在运行队列中等待内核调度——也称“就绪状态”。
2、阻塞类状态:
就是进程在“睡眠”,等待它所需要的资源可以满足其使用条件时,被“唤醒”进入运行状态。
2.1、可中断的阻塞状态:既能被显示地唤醒(一般调用wake_up系列宏)唤醒,又能被其他进程的信号唤醒(主动进入用sleep_on()函数)。
2.2、不可唤醒的阻塞状态:只能被指定的事件唤醒(如定时时间到、键盘有输入等),不能被其他进程的信号唤醒(主动进入用interruptible_sleep_on()函数)。
3、停止类状态:
3.1、暂停状态:当运行中的进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时,就会进入暂停状态。暂停状态的进程收到信号SIGCONT就会转为运行状态。
3.2、僵死状态:子进程运行结束,父进程(由于父进程正忙、父进程已亡等原因)尚未使用wait函数族回收其退出状态。
创建当前进程的进程称为其父进程。当前进程称为子进程。一般来讲,子进程结束运行时要向父进程返回一些信息已供父进程使用。
3.3、消亡状态:子进程运行结束,父进程已回收其结束状态,这时子进程将会被彻底删除,进入消亡状态。
进程子创建时起,就在不同状态间进行转换,直至最终消亡。
>>进程标识符(PID:process identity):
1、进程标识符就是进程的身份证号,它唯一的标识一个进程(用getpid()获得)。
2、父进程标识符(PPID:parent process identity)(用getppid()获得)。
3、对进程的操作一般都通过进程标识符。
>>进程的创建函数fork():
1、在当前进程中调用fork()函数,就会创建一个新的子进程。当前进程自动成为新进程的父进程。
2、fork()创建的子进程是对父进程的复制(几乎是父进程的所有相关数据都后被子进程复制下来,包括程序执行的进度等)。
3、父子进程的区分是通过fork()函数的返回值:
返回0:是子进程。
返回正整数(子进程的ID):是父进程。
4、例程:
int main(void)
{
pid_t child1;
child1 = fork();
if(ciild1 == -1)
{
error handling;
exit(1);
}
else if(child1 == 0)
{
subprocess program;
}
else
{
parentprocess program;
}
exit(0);
}
说明:
4.1、子进程一旦创建就会进入执行状态,进而和父进程竞争CUP的执行时间片(内核通过进程控制块来调度协调,用户也可根据需要调整父子进程间的执行顺序,如使父进程阻塞等手段)。
4.2、子进程从child1==0条件下开始执行,父进程从else下开始执行,两者并行(轮流获得CPU的执行权)。
>>子进程执行新程序的函数族exec
既然子进程时父进程的复制品,那么子进程存在的价值是什么呢?可以通过调用exec函数族来使子进程执行新的程序(一个典型的应用就是父进程监测标准输入,一旦有命令就创建一个子进程去响应,父进程本身继续监测)。
cexc函数族有6个原型(只有execve才是系统调用,其他都是在其基础上进行的二次封装):
1、int execl(const char *path, const char *arg, ......);
2、int execle(const char *path, const char *arg, ...... , char * const envp[]);
3、int execv(const char *path, char *const argv[]);
4、int execve(const char *filename, char *const argv[], char *const envp[]);
5、int execvp(const char *file, char * const argv[]);
6、int execlp(const char *file, const char *arg, ......);
以上函数族成功都返回文件的当前读写位置,失败返回-1。
根据函数名第5、6位的不同可以分为三类:
1、文件的查找方式不同:
有“P”时,只给出文件名即可;
无“P”时,要给出文件的“全路径名”。
2、命令行参数给出方式不同:
“v”,以数组指针形式给出(具体参数存储在指针指向的数组中)。
“l”,以列表方式给出参数;
3、环境变量的引用方式不同:
有“e”时,以数组指针形式给出(具体参数存储在指针指向的数组中);
无“e”时,默认引用当前环境变量。
例如:execve();用户给出执行文件的“全路径名”,以数组指针的方式给出所有执行参数,用户给定所有环境变量,所以只有execve才是基本函数。
使用exec函数族有两点需要注意:
1、无论以何种方式给出参数与环境变量,这两者都应以“NULL”结尾。
2、exec函数族的调用很容易失败,使用时一定要进行失败检查。
>>进程的退出:
1、exit()函数与return语句的区别:
return语句只是返回当前执行的函数(回到调用当前函数的母函数中继续执行)。
而exit()函数是退出整个进程(一般是退出一个完整的程序,可能包含多个函数的全部退出)。
2、exit()与_exit()的区别:
exit()是正常的进程退出,它要清理用户的I/O缓冲,吧用户缓冲区内的内容写回文件,并关闭所有打开的文件。
_exit()是暴力退出,什么都不处理,直接退出调用它的进程。
比如:标准输出采用行缓冲,遇到换行符"\n"时才会将缓冲区内容写入终端进行输出显示等。
一个没有换行符的printf("abc");语句,当遇到exit();时仍然会显示出来,而遇到_exit();就直接掉丢不会显示了。
3、参数的含义:
exit()与_exit()的参数都是整数,用以向父进程传递退出原因。参数0表示正常退出,其他数值对应各种错误(由用户定义,可以用正整数表示警告退出,负整数表示错误退出)。
>>进程的回收:
有些进程不是运行完了就算了,是要向父进程返回一些结果的。当然,父进程也可能“主动等待”子进程返回数据,这时候需要调用wait()函数族。
wait()是waitpid()的一个特例。
1、pid_t wait(int *status);
功能:阻塞父进程(调用其的进程),直到一个子进程结束或者父进程接到一个指定的信号为止。
返回值:成功返回第一个结束的子进程ID;失败返回-1(没有尚未结束的子进程)。
参数:status指向一个用来保存子进程结束时状态的整形对象。
2、pid_t waitpid(pid_t pid, int *status, int options);
功能:
返回值:>0表示已结束运行的子进程ID;0表示使用选项WNOHANG且没有子进程结束;-1出错(并设置errno)。
参数pid:>0表示待回收的子进程ID;=0表示回收同组的任意一个子进程;=-1表示回收任何一个子进程(此时等同于wait()函数);<-1表示回收进程组号等于-pid的任意子进程。
参数status:同wait()。
参数options:=WNOHANG表示若没有子进程结束,也不阻塞父进程,而是立即返回0;=WUNTRACED返回已暂停但尚未报告的子进程状态。
3、如果一个子进程已经结束,但父进程还没有调用wait()函数族来对其进行回收,那么这个子进程的结束状态将会被保留,此时子进程就变成了一个“僵尸进程”(未完全死掉)。
>>守护进程(daemon):
1、守护进程不受用户、终端或者其他变化的影响(用户可以把一个普通进程变成守护进程)。
2、linux的很多系统服务都是通过守护进程实现的。
3、编写守护进程需要5步:
1>创建子进程,父进程退出
此时子进程已经摆脱了父进程的管控,成为了“孤儿进程”,系统检测到孤儿进程会将其作为1号进程(init进程)收养。
2>在子进程中创建一个新会话(调用setsid()函数)
由于子进程从父进程那里继承了会话、进程组和控制终端,为了摆脱这些对子进程的控制,需要创建一个新的会话来摆脱它们。
3>改变当前目录(调用chdir()函数)
由于子进程的存在,其所在的文件系统可能无法卸载(比如子进程从属于/mnt/usb等),所以需要将当前目录改为根目录"/"或者临时目录"/tmp"。
4>重设文件权限掩码(调用umask(0)函数)
子进程从父进程那里继承来的文件权限可能是只读,这样对子进程的后续操作带来了隐患(文件的实际权限=创建权限-掩码权限)。
5>关闭文件描述符
如果父进程里已经打开了没写文件,子进程也一并将其继承,而这些打开的文件对子进程来说可能是一种多余(这些打开的文件也会导致所在的文件系统不能被卸载),需将其关闭。
for(i=0; i<MAXFILE; i++)
{
close(i);
}
>>进程间的通信概述:
1、管道进行通信
有名管道:可以在本地的任何进程间进行通信。
无名管道:只能在有亲缘关系的管道间进行通信(父子进程间、兄弟进程间)。
2、共享内存
是最高效的进程间通信方式,需要考虑同步与互斥问题。
3、其他进程间的通信方式
消息队列、信号量、信号、套接字(用于网络通信)。
阅读全文