一、进程
1、进程的定义
进程是程序执行过程中所有资源的总称。
进程是一个独立的可调度的任务
进程是一个抽象实体。当系统在执行某个程序时,分配和释放的各种资源
进程是一个程序的一次执行的过程
2、进程和程序的区别
程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念
进程是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡。
进程是程序执行和资源管理的最小单位
3、主要的进程标识:
进程号(Process Identity Number,PID)
父进程号(Parent Process ID,PPID)
PID唯一地标识一个进程
4、Linux中的进程包含三个段:
“数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。
“正文段”存放的是程序中的代码
“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量
5、进程的类型:
交互进程:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。
批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
6、进程调度指令:
ps 查看系统中的进程
top 动态显示系统中的进程
nice 按用户指定的优先级运行进程
renice 改变正在运行进程的优先级
kill 向进程发信号
bg 将挂起的进程在后台执行
fg 把后台运行的进程放到前台运行
二、进程的系统调用fork
1、fork创建一个子进程
pid_t fork(void);
返回值:-1,表示失败
0,表示是子进程
>0,表示是父进程,但是此刻的返回值代表子进程的pid
如果父进程先死亡,此时子进程还存在,那么子进程变为孤儿进程,会后台运行,被init进程收养。
如果子进程先死亡并且父进程存在且未回收子进程资源,那么子进程变成僵尸进程。
由于之前的fork完整地拷贝了父进程的整个地址空间,因此执行速度是比较慢的。为了提高效率,Unix
系统设计者创建了现代unix版的fork。现代unix版的fork也创建新进程,但不产生父进程的副本。它通过
允许父子进程可访问相同物理内存从而伪装了对进程地址空间的真实拷贝,当子进程需要改变内存中数
据时才拷贝父进程。这就是著名的“写操作时拷贝”(copy-on-write)技术。
fork示例
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid = fork(); //创建子进程
if( -1 == pid )//pid为-1表示创建子进程失败
{
perror("fork");
return -1;
}
else if( 0 == pid )//pid为0表示子进程
{
puts("This is son subprocess");
}
else //pid为其他则表示父进程
{
puts("This is father Parent process ");
sleep(1);//让父进程延迟一秒结束,防止父进程在子进程之前结
}
return 0;
}
运行结果:
2、exec函数族
exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可
执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完之后,原调用进程的内容除
了进程号外,其他全部都被替换了。
可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
execl:以列表的形式传参
int execl(const char *path, const char *arg, ...);
返回值:-1表示失败
const char *path:要执行程序的路径(包括要执行的文件名)
const char *arg:要执行的文件名
...:不定参数,根据要执行文件的命令行参数确定,最后一定用NULL
execl示例
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t pid = fork(); //创建子进程
if( -1 == pid )
{
perror("fork");
exit(-1);
}
else if( 0 == pid )
{
if( -1 == execl("/bin/ls","ls",NULL )) // 子进程调用execl函数使用ls命
{
perror("execl");
return -2;
}
}
else
{
puts("I'm father!");//父进程
sleep(1);
}
puts("over");//子进程使用;execl函数,不会运行此语句
return 0;
}
运行结果:
execvp:以指针数组的形式传参
int execvp(const char *file, char *const argv[]);
返回值:-1表示失败
const char *file:要执行的文件名
char *const argv[]:执行程序名和参数最后必须NULL
execvp示例
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
if( -1 == pid )
{
perror("fork");
return -1;
}
else if( 0 == pid )
{
//子进程调用cat
char *buf[] = {"cat", "fork_execvp.c", NULL};
if( -1 == execvp("cat",buf))
{
perror("execvp");
return -2;
}
}
else
{
puts("I'm father ");
sleep(1);
}
puts("over");
return 0;
}
execv
定义:
int execv (const char * path, char * const argv[ ]);
表头文件:
#include<unistd.h>
说明:
execv()用来执行参数path字符串所代表的文件路径, 与execl()不同的地方在于execve()只需两个参数, 第二个参数利用数组指针来传递给执行文件。
返回值:
如果执行成功则函数不会返回, 执行失败则直接返回-1, 失败原因存于errno 中。
execv示例
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();//创建子进程
if( -1 == pid )
{
perror("fork");
exit(-1);
}
else if( 0 == pid )
{
char * son[] = {"cat","text.txt",NULL};
if( -1 == execv("/bin/cat",son))
{
perror("son execv");
return -1;
}
}
else
{
sleep(1);
char * father[] = {"ls","-al",NULL};
if( -1 == execv("/bin/ls",father))
{
perror("father execv");
return -1;
}
}
return 0;
execlp
定义:
int execlp(const char * file,const char * arg,……);
表头文件:
#include<unistd.h>
说明:
execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0]、argv[1]……, 最后一个参数必须用空指针(NULL)作结束。
返回值:
如果执行成功则函数不会返回, 执行失败则直接返回-1, 失败原因存于errno 中。
execlp示例
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();//创建子进程
if( -1 == pid )
{
perror("fork");
exit(-1);
}
else if( 0 == pid )
{
//子进程调用cat
if( -1 == execlp("cat","cat","text.txt",NULL))
{
perror("son execlp");
return -1;
}
}
else
{
//父进程调用ls
sleep(1);
if( -1 == execlp("ls","ls","-al",NULL))
{
perror("father execlp");
return -1;
}
}
return 0;
}
一、退出进程的函数
1、exit()退出进程
exit()是一个标准库函数。
结束当前进程,并且刷新所有缓存
void exit(int status);
参数虽然是int类型,但是它只有低8位有效
_exit()函数的作用最为简单:直接使进程终止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;
exit()函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序。
exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的"清理I/O缓冲"一项。
代码示例
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("hello world!\n");
printf("welocme!");
exit(0); //结束当前进程
return 0;
}
2、_exit()退出程序
_exit()函数是系统调用函数。
_exit()结束进程,但是不会刷新缓存,而是清空缓存
_exit()函数的作用最为简单:直接使进程终止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;
代码示例
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("hello world!\n");
printf("welocme!");
_exit(0); //结束当前进程,不会刷新缓存
return 0;
}
二、回收进程的函数
1、wait()回收子进程
调用该函数使进程阻塞,直到一个子进程结束或者是该进程接收到了一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
int *status: 为NULL时对退出进程的状态不关心;不为NULL时会接收子进程退出的状态
返回值:-1表示失败,成功返回结束的子进程的进程号
代码示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
if( -1 == pid )
{
perror("fork");
return -1 ;
}
else if( 0 == pid )
{
//子进程
puts("I'm 1 son!");
exit(0);
}
else
{
//父进程
pid_t pid1 = fork();
if(-1 == pid1)
{
exit(0);
}
else if( 0 == pid1 )
{
puts("I'm second son ");
exit(1);
}
else
{
int staus ;
/*一个一个回收*/
#if 0
pid_t end = wait(&status);
printf("id:%d gameover! end_st:%d\n", end, status);
pid_end1 = wait(&status);
printf("id:%d gameover! end_st:%d\n", end, status);
#else
//使用while循环回收
pid_t end ;
while(1)
{
wait(NULL);
if( -1 == end )
{
break;
}
}
#endif
}
}
return 0;
}
2、waitpid()可以指定回收子进程
功能和wait函数类似。可以指定等待某个子进程结束以及等待的方式(阻塞或非阻塞)
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
pid_t pid:>0回收指定子进程 =-1回收任意子进程 <-1回收其绝对值相等的子进程
int *status:为NULL时对退出进程的状态不关心;不为NULL时会接收子进程退出的状态
int options:0表示阻塞 WNOHANG表示非阻塞
返回值:使用选项WNOHANG且没有子进程结束时:0
三、守护进程
守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件
守护进程常常在系统启动时开始运行,在系统关闭时终止
Linux系统有很多守护进程,大多数服务都是用守护进程实现的 :一些网络服务、HTTP、NFS...
在Linux中,每一个系统与用户进行交流的界面称为终端。从该终端开始运行的进程都会依附于这个终端,这个终端称为这些进程的控制终端。当控制终端被关闭时,相应的进程都会被自动关闭。
守护进程能够突破这种限制,它从开始运行,直到整个系统关闭才会退出。如果想让某个进程不会因为用户或终端的变化而受到影响,就必须把这个进程变成一个守护进程。
进程组:
进程组是一个或多个进程的集合。进程组由进程组ID来唯一标识。
每个进程组都有一个组长进程,进程组ID就是组长进程的进程号。(比如一个终端里面运行的进程都在一个进程组中,组长是运行的shell进程)
会话期:
会话组是一个或多个进程组的集合
守护进程创建的流程:
守护进程创建的流程:
1.创建子进程,父进程退出 :变成孤儿进程(后台) ----------------------------------->fork()
2.在子进程中创建新会话 :脱离shell(与终端无关) ----------------------------------->setsid()
3.改变当前目录为根目录 :避免守护进程的工作目录别卸载 -------------------------->chdir()
4.重设文件权限掩码 :用户可以任意指定文件的权限 --------------------------------->umask()
5.关闭文件描述符 :关闭已经打开的文件描述符 ----------------------------------------->close()
代码示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
//1 创建子进程
pid_t pid = fork();
if( -1 == pid )
{
perror("fork");
exit(-1);
}
//关闭父进程
else if( 0 < pid )
{
exit(0);
}
//2 创建新会话
if( 0 == setsid() )
{
perror("setsid");
exit(-2);
}
//3 改变当前目录为根目录下tmp目录
if( 0 != chdir("/tmp"))
{
perror("chdir");
exit(-3);
}
//4 重设文件权限掩码
umask(0);
//5 关闭文件描述符
int n = getdtablesize();
for( int i = 0 ; i < n ; i++ )
{
close(i);
}
//6 创建一个守护进程实现记录偶数秒时间
int fd = open("time.log",O_WRONLY|O_CREAT,0777);
while(1)
{
time_t t = time(&t) ;
int n = strlen(ctime(&t));
if((*(ctime(&t)+n-1)) % 2 == 0 )
{
write(fd,ctime(&t),n);
}
sleep(2);
}
return 0;
}