1、Linux系统中主要包括下面几种类型的进程。
(1)交互进程:这类进程经常与用户进行交互,与等待用户输入(键盘、鼠标操作等)。当接收到用户输入后,这类进程能够立即响应。典型的交互进程有shell命令进程、文本编辑器等。
(2)批处理进程:这类进程不用于与用户交互,故大都处于后台运行。这类进程通常不必跟很快响应,因此往往不会优先调度。典型的批处理进程有编译器的编译操作、数据库索引等。
(3)守护进程:这类进程一直在后台运行,和任何终端都不关联。通常系统启动时开始执行,系统关闭结束。很多系统进程都是以守护进程的方式存在。
2、Linux下的进程结构
进程不但包括程序的指令的数据,而且还包括程序计数器和处理器的所有寄存器以及存储临时数据的进程堆栈。
(1)进程状态
① 运行态(TASK_RUNNING):进程当前正在运行,后者正在运行队列中等待调度。
②可中断的阻塞态(TASK_INTERRUPTIBLE):进程处于阻塞(睡眠)状态,等待再次有资源和某些事件发生。这种情况下的进程可以被信号中断,当被信号唤醒后该进程可转变为运行态。
③不可中断的阻塞态(TASK_UNNTERRUPTIBLE):该进程不可被信号唤醒,只有在他等待的事件发生时该进程才可被唤醒。
④暂停态(TASK_STOPPED):进程的执行被暂停,当进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号时会进入到暂停态。
⑤僵尸态(EXIT_ZOMBIE):子进程运行结束退出后父进程没有退出,并且没有使用wait函数族等系统调用来将子进程进行回收,处于该状态的进程内部存储空间都已释放,没有任何可执行代码,也不能被调度,仅仅在进程中保留一个位置,故而占据着内核空间但是没有什么用处,在进程的创建过程中我们应该避免僵尸进程的产生
⑥消亡态:消亡态是一个进程的最终状态,父进程调用wait函数族回收后,子进程彻底有系统删除,不可见。
(2)进程标识符
Linux内核通过唯一标识进程标识符PID来表示每一个进程。
(3)进程的创建 —fork()
(1)进程的创建:进程的创建通过fork()函数来进行创建,fork()函数时Linux中一个重要的函数下面时对fork()函数的一个简单描述
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回值:
成功创建一个新的子进程,父进程返回子进程的PID号,子进程返回0
失败父进程返回-1,没有子进程被创建
该函数有两个返回值,不同的返回值代表不同的进程,返回值为0,则表示为子进程,父进程的返回值时子进程的进程号(大于0),可以通过返回值来判定是子进程还是父进程。下面是一个简单示例:
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork(); //用于接收返回值
if(pid < 0) //判断返回值小于0则表示进程创建失败
{
perror("fork");
exit(-1);
}
if(pid == 0) // 等于0表示该进程是子进程
{
printf("This is the child process!\n");
printf("pid = %d ,PID = %d\n",pid,getpid()); //打印进程号
exit(0);
}
else //大于0,表示该进程是父进程
{
printf("This is the parent process!\n");
printf("pid = %d ,PID = %d\n",getpid()); //打印进程号
exit(0);
}
return 0;
}
运行结果如下,可以看出父进程返回子进程的进程号,子进程的返回值为0;
(getpid()是获取当前进行的进程号的函数)
(4)exec函数族
fork()函数用于创建一个子进程,子进程几乎复制了父进程的全部内容。当我们想用子进程执行一个新的程序时,我们可以使用exec函数族来实现。
exec函数族可以根据指定的文件名或目录名找到可执行文件,并且用该文件来替换当前进程的数据段、代码段的堆栈段。
exec函数族并不是函数,而是以exec开头的几个函数。他们之间有着细微的差别,我们这里介绍其中几个
#include <unistd.h>
/*
有以一下字母结尾,分别为
l:list列表
v: argv
p: PATH环境变量
*/
int execl(const char *pathname, const char *arg, .../* (char *) NULL */);
参数:
pathname:执行程序的文件名(包含路径)
arg:执行程序的命令行参数,命令行参数列表以NULL结尾
返回值:
失败返回-1;
其中参数pathname应当包含路径,如不添加路径则默认为当前路径。具体使用如下所示:
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork(); //用于接收返回值
if(pid < 0) //判断返回值小于0则表示进程创建失败
{
perror("fork");
exit(-1);
}
if(pid == 0) // 等于0表示该进程是子进程
{
if((pid = execl("/bin/ls","ls","-l",NULL)) < 0)
{
perror("execl");
}
}
else //大于0,表示该进程是父进程
{
}
return 0;
}
运行结果如下图可见,子进程中运行结果与在shell中直接输入“ls -l”是一样的。
其中execlp与execl区别在于参数pathname,execlp可以不用自己加可执行文件所在的路径,他会自动帮我们进行查找,而execl需要在前面添加路径。
(4)、回收子进程 — wait()
说明:wait()函数用于是父进程(调用wait()进程)阻塞,知道一个子进程结束或者该进程接收到一个指定信号为止。下面是 函数说明
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
参数:
wstatus:进程结束时,状态信息的首地址
返回值:
成功返回结束子进程的pid号,失败返回-1
如果想要得到子进程结束的状态信息,可以用以下宏来得到:
WIFEXITED(wstatus) -- 判断一个子进程是否是正常退出,正常退出为真,非正常退出为假
WEXITSTATUS(wstatus) -- 返回子进程结束的返回值
WIFSIGNALED(wstatus) -- 判断是否被信号终止
WTERMSIG(wstatus) -- 打印终止进程信号的编号
这里参数wstatus用于接收状态信息,通常我们定义一个int *的变量,将其地址传入,状态信息就保存在该变量中,我们也可以输入宏,可直接返回相应的内容。
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
int main()
{
pid_t pid;
pid = fork(); //用于接收返回值
if(pid < 0) //判断返回值小于0则表示进程创建失败
{
perror("fork");
exit(-1);
}
if(pid == 0) // 等于0表示该进程是子进程
{
int n=5;
while(n--)//每隔1秒打印5行*
{
printf("***************\n");
sleep(1); //延时1秒
}
exit(0);
}
else //大于0,表示该进程是父进程
{
int i=5;
int status;
pid = wait(&status);//用于接收子进程的结束的状态,并且堵塞父进程,在子进程结束后执行父进程
printf("pid = %d",pid);
while(i--)
{
printf("#################\n");
sleep(1);
}
}
return 0;
}
运行结果如下