进程是一个独立的可调度的任务
进程是一个抽象实体。当系统在执行某个程序时,分配和释放的各种资源
进程是一个程序的一次执行的过程
主要的进程标识:
进程号(Process Identity Number,PID)
父进程号(Parent Process ID,PPID)
PID唯一地标识一个进程
Linux中的进程包含三个段:
“数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。
“正文段”存放的是程序中的代码
“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量
进程的类型:
交互进程:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。
批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
进程调度指令:
ps 查看系统中的进程 : ps -aux|grep a.out
top 动态显示系统中的进程: top
nice 按用户指定的优先级运行进程(-20~19): nice -n 2 ./a.out
renice 改变正在运行进程的优先级(-20~19):renice -n 2 pid
kill 向进程发信号:kill -9 pid
bg 将挂起的进程在后台执行(n是挂起的编号):bg [n]
fg 把后台运行的进程放到前台运行(n是挂起的编号):fg [n]
1、进程的系统调用fork
fork创建一个子进程
pid_t fork(void); 返回值:-1,表示失败 0,表示是子进程 >0,表示是父进程,但是此刻的返回值代表子进程的pid 如果父进程先死亡,此时子进程还存在,那么子进程变为孤儿进程,会后台运行,被init进程收养。 如果子进程先死亡并且父进程存在且未回收子进程资源,那么子进程变成僵尸进程。
由于之前的fork完整地拷贝了父进程的整个地址空间,因此执行速度是比较慢的。为了提高效率,Unix系统设计者创建了现代unix版的fork。现代unix版的fork也创建新进程,但不产生父进程的副本。它通过允许父子进程可访问相同物理内存从而伪装了对进程地址空间的真实拷贝,当子进程需要改变内存中数据时才拷贝父进程。这就是著名的“写操作时拷贝”(copy-on-write)技术。
2、exec函数族
exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。
可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
1、execl:以列表的形式传参
int execl(const char *path, const char *arg, ...); 返回值:-1表示失败 const char *path:要执行程序的路径(包括要执行的文件名) const char *arg:要执行的文件名 ...:不定参数,根据要执行文件的命令行参数确定,最后一定用NULL
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
if(-1 == pid){
perror("fork");
return -1;
}else if(0 == pid){
if(0 != execl("/bin/ls", "ls", "-la", NULL)){
perror("execl");
return -1;
}
puts("hello");
}else{
sleep(1);
}
printf("I'm %d\n", getpid());
return 0;
}
2、 execvp:以指针数组的形式传参
int execvp(const char *file, char *const argv[]); 返回值:-1表示失败 const char *file:要执行的文件名 char *const argv[]:执行程序名和参数最后必须NULL
exec是一个用于在Unix和类Unix操作系统上执行新程序的系列系统调用,它被称为"exec调用族",因为它包括了多个不同的调用,如execl、execv、execle、execve等。这些调用允许进程加载并执行一个新的可执行程序,从而替换当前进程的代码和数据,实现了进程的程序替换。
下面是一些常见的exec调用:
1.execl和execle:这些调用接受可执行程序的路径名和一系列的参数作为参数,然后加载并执行指定的可执行程序。execle还允许在加载新程序时同时指定环境变量。
2.execv和execve:与前面的调用类似,但是参数以数组形式传递,而不是作为单独的参数传递。
3.execvp和execvpe:这些调用与前面的调用类似,但是可以通过搜索PATH环境变量来查找可执行程序的路径,而不需要提供完整的路径。
通过使用exec调用族,一个进程可以在运行过程中将自己的代码和数据替换为另一个可执行程序,从而实现程序的动态加载和替换。这在各种应用场景中都非常有用,比如在Unix系统中,一个shell进程可以使用exec调用来运行用户输入的命令。
需要注意的是,exec调用并不会创建新的进程,而只是替换当前进程的代码和数据。如果想要在创建新进程的同时加载新程序,通常会将fork和exec结合使用。首先使用fork创建一个子进程,然后在子进程中使用exec调用加载新程序。这种方式可以实现创建一个新的执行环境,同时保留父进程的状态。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *buf[] = {"cat", "/1.txt", NULL};
char cmd[100];
fgets(cmd, 100, stdin);
cmd[strlen(cmd)-1] = 0;
buf[1] = cmd;
pid_t pid = fork();
if(-1 == pid){
perror("fork");
return -1;
}else if(0 == pid){
if(0 != execvp("cat", buf)){
perror("execl");
return -1;
}
puts("hello");
}else{
sleep(1);
}
printf("I'm %d\n", getpid());
return 0;
}
其他如:execlp、execv
execlp
#include <stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(int argc, char *argv[])
{
pid_t pid=fork();
if(-1==pid){
perror("fork");
return -1;
}
else if(0==pid)
{
if(0!=execlp("ls","ls","-la","/home/hqyj",NULL))
{ perror("execlp");
return -1;
}
}
else
{
sleep(1);
}
return 0;
}
execv
#include <stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
int main(int argc, char *argv[])
{
char *arr1[]={"cat","./execlp.c",NULL};
char *arr2[]={"ls","-la",".",NULL};
// char cmd[100]={0};
// fgets(cmd,100,stdin);
// cmd[strlen(cmd)-1]='\0';//去除反斜杠n。
// arr1[1]=cmd;
pid_t pid=fork();
if(-1==pid){
perror("fork");
return -1;
}
else if(0==pid)
{
if(0!=execv("/bin/cat",arr1))
{ perror("execlp");
return -1;
}
}
else
{
sleep(1);
if(0!=execvp("ls",arr2))
{ perror("execlp");
return -1;
}
}
return 0;
}