版权声明:本文为Trinity原创文章,未经Trinity允许不得转载 https://blog.csdn.net/Caoyang_He/article/details/84946848
进程环境与进程属性
什么是进程
- 简单说来,进程就是程序的一次执行过程。
- 进程至少要有三种基本状态。这三种基本状态是:
- 运行态
- 就绪态
- 封锁态(或等待态)
- 进程的状态可依据一定的条件和原因而变化
进程的状态
进程的模式和类型
- 什么是孤儿进程
因父亲进程先退出而导致一个子进程被init进程收养的进程为孤儿进程。
举例:orphan.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;
if((pid=fork())==-1)
perror("fork");
else if(pid==0)
{
printf("pid=%d,ppid=%d\n",getpid(),getppid());
sleep(2);
printf("pid=%d,ppid=%d\n",getpid(),getppid());
}
else
exit(0);
}
- 什么是僵死进程
已经退出但还没有回收资源的进程为僵死进程。
举例:dead.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
if((pid=fork())==-1)
perror("fork");
else if(pid==0)
{
printf("child_pid pid=%d\n",getpid());
exit(0);
}
sleep(3);
system("ps");
exit(0);
}
-
进程的模式
在Linux系统中,进程的执行模式划分为用户模式和内核模式 -
进程的类型
按照进程的功能和运行的程序来分,进程划分为两大类:一类是系统进程,另一类是用户进程 。
进程的属性
- 进程号(PID)
函数:extern pid_t getpid(void);
举例:getpid_example.c
#include<stdio.h>
#include<unistd.h>
int main(int argc,char *argv[])
{
printf("the current program's pid is %d\n",getpid());
return 0;
}
- 父进程号(PPID)
函数:extern pid_t getppid(void);
举例:getppid_example.c
#include<stdio.h>
#include<unistd.h>
int main(int argc,char *argv[])
{
printf("the current program's ppid is %d\n",getppid());
return 0;
}
-
会话:一个或多个进程组的集合
函数:- extern pid_t getsid(pid_t pid)
- extern pid_t setsid(void)
-
控制终端
函数:- pid_t tcgetpgrp(int filedes);
- pid_t tcsetpgrp(int filedes,pid_t pgrpid);
- pid_t tcgetsid(int filedes);
-
会话与进程组的特点
- 一个会话可以由一个控制终端,建立于控制终端连接的会话首进程被称为控制进程;
- 一个会话中的几个进程组可分为一个前台进程组合几个后台进程组,如果会话有一个控制终端,则有一个前台进程组;
- 无论何时在控制终端键入或监测,将会有信号发送给前台进程组的所有进程。
-
进程用户属性
- 真实用户号(RUID)
创建该进程的用户UID
函数:extern uid_t getuid(void) - 有效用户号(EUID)
有效UID一般与UID相同,但是在设置setuid位后,则不同。
函数:extern uid_t geteuid(void) - 进程用户组号(GID)
创建该进程的用户所在的组号GID。
函数:extern uid_t getgid(void) - 有效进程用户组号(EGID)
有效GID一般与GID相同,但是在设置setgid位后,则不同。
函数:extern uid_t getegid(void)
- 真实用户号(RUID)
进程的调度
调度算法
- FIFO先入先出原则:首先请求服务的对象首先得到CPU的处理
- 最短作业优先原则:需要最少时间的服务首先得到处理
- 最高优先级优先原则:优先级最高的服务首先得到处理
- 时间轮片原则:每个任务分配一个系统时间片,轮流执行
调度时机
- 定义
在什么情况下要执行调度程序,则称为调度时机。 - 调度时机
- 当前进程调用系统调用sleep( )或者exit( )。
- 进程终止,永久地放弃对CPU的使用。
- 在时钟中断处理程序执行过程中,发现当前进程连续运行的时间过长。
- 当唤醒一个睡眠进程时,发现被唤醒的进程比当前进程更有资格运行。
- 一个进程通过执行系统调用来改变调度策略或者降低自身的优先权(如nice命令),从而引起立即调度。
进程的管理
创建进程——fork和vfork
- 功能:创建进程。
- 函数原型 pid_t fork(void);
- 返回值:
如果调用成功,父进程调用返回新子进程ID,新进程继续执行;在子进程返回0。
如果调用失败,在父进程返回-1,错误原因存于errno中。 - 说明:这个系统调用对父进程进行复制,在进程表里创建出一个项目,子进程与父进程几乎一样,执行的是相同的代码,只是新进程有自己的数据空间、环境和文件描述符,而对文件描述符关联的内核文件表项,采用共享方式。
- 典型用法
pid_t new_pid;
new_pid = fork();
switch(new_pid)
{
case -1:break;
case 0:break;
default:break;
}
- 举例:资源抢占
fork_example01.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc,char *argv[])
{
pid_t pid;
if((pid=fork())==-1)
printf("fork error");
printf("bye!\n");
return 0;
}
- 举例:父子进程典型结构
fork_example02.c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(void)
{
pid_t pid;
if((pid=fork())==-1)
printf("fork error");
else if(pid==0)
{
printf("in the child process\n");
}
else
{
printf("in the parent process\n");
}
return 0;
}
- 举例:父子进程的复制
fork_descriptor.c
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
pid_t pid;
int fd;
int i=1;
int status;
char *ch1="hello";
char *ch2="world";
char *ch3="IN";
if((fd=open("test.txt",O_RDWR|O_CREAT,0644))==-1)
{
perror("parent open");
exit(EXIT_FAILURE);
}
if(write(fd,ch1,strlen(ch1))==-1)
{
perror("parent write");
exit(EXIT_FAILURE);
}
if((pid=fork())==-1)
{
perror("fork");
exit(EXIT_FAILURE);
}
else if(pid==0)
{
i=2;
printf("in child\n");
printf("i=%d\n",i);
if(write(fd,ch2,strlen(ch2))==-1)
perror("child write");
return 0;
}
else
{
sleep(1);
printf("in parent\n");
printf("i=%d\n",i);
if(write(fd,ch3,strlen(ch3))==-1)
perror("parent,write");
wait(&status);
return 0;
}
}
vfork
- 出现原因
当派生进程只是执行exec()函数,则使用fork()从父进程复制到子进程的数据空间将不被使用,为了改进这种情况下效率低下的问题,出现了vfork()。 - vfork函数特点
与父进程共享数据空间 - 执行顺序:子先父后
- 与fork区分
- 举例:vfork函数的进程创建
fork_example.c
#include<unistd.h>
#include<error.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
int glob=6;
int main()
{
int var;
pid_t pid;
var=88;
printf("in beginning:\tglob=%d\tvar=%d\n",glob,var);
if((pid=fork())<0)
{
perror("fork");
exit(EXIT_FAILURE);
}
else if(pid==0)
{
printf("in child,modify the var:glob++,var++\n");
glob++;
var++;
printf("in child:\tglob=%d\tvar=%d\n",glob,var);
_exit(0);
}
else
{
printf("in parent:\tglob=%d\tvar=%d\n",glob,var);
return 0;
}
}
vfork_example.c
#include<unistd.h>
#include<error.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
int glob=6;
int main()
{
int var;
pid_t pid;
var=88;
printf("in beginning:\tglob=%d\tvar=%d\n",glob,var);
if((pid=vfork())<0)
{
perror("vfork");
exit(EXIT_FAILURE);
}
else if(pid==0)
{
printf("in child,modify the var:glob++,var++\n");
glob++;
var++;
printf("in child:\tglob=%d\tvar=%d\n",glob,var);
_exit(0);
}
else
{
printf("in parent:\tglob=%d\tvar=%d\n",glob,var);
return 0;
}
}
- 举例:vfork在子函数中
vfork_return.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
void test()
{
pid_t pid;
pid=vfork();
if(pid==-1)
{
perror("vfork");
exit(EXIT_FAILURE);
}
else if(pid==0)
{
printf("1:child pid=%d,ppid=%d\n",getpid(),getppid());
return;
}
else
printf("2:parent pid=%d,ppid=%d\n",getpid(),getppid());
}
void fun()
{
int i;
int buf[100];
for(i=0;i<100;i++)
buf[i]=0;
printf("3:child pid=%d,ppid=%d\n",getpid(),getppid());
}
int main()
{
pid_t pid;
test();
fun();
}
等待进程—wait
- 功能:等待子进程中断或结束
- 头文件
#include<sys/types.h>
#include<sys/wait.h> - 函数原型 pid_t wait (int * status);
- 说明:函数会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会同时返回。如果不在意结束状态值,则参数 status可以设成NULL。子进程的结束状态值请参考宏测试。
- 返回值:如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。
- 函数处理的基本过程
- 如果父进程没有子进程,则出错返回。
- 如果发现有一个终止的子进程,则取出子进程的进程号,把子进程的CPU使用时间等加到父进程上,释放子进程占用的task_struct和系统空间堆栈,以供新进程使用。
- 如果发现有子进程,但都不处于终止态,则父进程睡眠,等待由相应的信号唤醒
- 举例
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
main()
{
pid_t pid;
int status,i;
if(fork()==0)
{
printf("This is the child process .pid =%d\n",getpid());
exit(5);
}
else{
sleep(1);
printf("This is the parent process ,wait for child...\n");
pid=wait(&status);
i=WEXITSTATUS(status);
printf("child’s pid =%d .exit status=%d\n",pid,i);
}
}
退出进程——on_exit和exit
on_exit
- 功能:正常结束当前调用函数
- 举例
exit
- 功能:正常结束进程
- 头文件:#include<stdlib.h>
- 函数原型:void _exit(int status)
- 返回值:无
- 说明:正常终结目前进程的执行,并把参数status返回给父进程,而进程所有的缓冲区数据不会自动写回。
举例:比较exit和_exit的区别
#include <stdlib.h>
int main(int argc,char **argv)
{
printf(“output\n”);
printf(“content in buffer”);
_exit(0);
//exit(0);
}
替换当前进程——execl,execlp,execv ,execvp,execve
execl
- 头文件:#include<unistd.h>
- 函数原型
int execl(const char * path,const char * arg,…); - 返回值:成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
- 说明
execl()用来执行参数path字符串所代表的文件路径,参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。 - 举例
#include<unistd.h>
main()
{
execl(“/bin/ls”,”ls”,”-l”,”/etc/passwd”(char * )0);
}
execlp
- 头文件:#include<unistd.h>
- 函数原型
int execlp(const char * file,const char * arg,……); - 返回值:成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
- 说明
execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。 - 举例
#include<unistd.h>
main()
{
execlp(“ls”,”ls”,”-l”,”/etc/passwd”,
(char * )0);
}
execv
- 头文件:#include<unistd.h>
- 函数原型
int execv (const char * path, char * const argv[ ]); - 返回值:成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
- 说明
execv()用来执行参数path字符串所代表的文件路径,与execl()不同的地方在于execv()只需两个参数,第二个参数利用数组指针来传递给执行文件。 - 举例
#include<unistd.h>
main(){
char * argv[ ]={“ls”,”l”,
”/etc/passwd”,(char*)0 };
execv(“/bin/ls”,argv);
}
execvp
- 头文件:#include<unistd.h>
- 函数原型
int execvp(const char *file ,char * const argv []); - 返回值:成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
- 说明
execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。 - 举例
#include<unistd.h>
main(){
char * argv[ ] ={ “ls”,”l”,
”/etc/passwd”,0};
execvp(“ls”,argv);
}
execve
- 头文件:#include<unistd.h>
- 函数原型
int execve(const char * filename,char * const argv[ ],char * const envp[ ]); - 返回值:成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
- 说明
execve()用来执行参数filename字符串所代表的文件路径,第二个参数系利用数组指针来传递给执行文件,最后一个参数则为传递给执行文件的新环境变量数组。 - 举例
#include<unistd.h>
main()
{
char * argv[ ]={“ls”,”l”,
”/etc/passwd”,(char *)0};
char * envp[ ]={“PATH=/bin”,0}
execve(“/bin/ls”,argv,envp);
}
补充
fatherFork.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i;
for( i= 0; i< 3; i++)
{
int pid= fork();
if(pid== 0)
{
printf("son\n");
}
else
{
printf("father\n");
}
}
return 0;
}
waitpid_example.c
#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/errno.h>
#include<stdlib.h>
extern int errno;
int main(int argc,char *argv[])
{
pid_t pid_one,pid_wait;
int status;
if((pid_one=fork())==-1)
perror("fork");
if(pid_one==0)
{
printf("my pid is %d\n",getpid());
sleep(1);
exit(EXIT_SUCCESS);
}
pid_wait=waitpid(pid_one,&status,0);
if(WIFEXITED(status))
printf("wait on pid:%d,return value is:%4x\n",pid_wait,WEXITSTATUS(status));
else if(WIFSIGNALED(status))
printf("wait on pid:%d,return value is:%4x\n",pid_wait,WIFSIGNALED(status));
return 0;
}