1、 进程的基本概念
进程的分类
交互进程
批处理进程
守护进程:一般在后台运行,一般由操作系统在开机时通过脚本自动激活启动或由超级管理用户root来启动
进程的属性
进程ID:进程的唯一数值,用来区分进程
启动进程的用户ID(UID)和归属的组(GID)
进程状态:运行(R)、休眠(S)、僵尸(Z)
进程执行优先级
进程所连接的终端名
进程资源占用
进程监视工具:ps
一般用法:ps -aux|more //用管道和more命令连接起来分页查看
其他用法:ps aux|grep 程序名 //提取指定程序的进程
查询进程工具:pgrep
ps 参数选项 程序名
常用参数如下:
-1: 列出程序名和进程ID
-o: 进程起始的ID
-n: 进程终止的ID
Linux进程的三态
就绪状态:若进程已被分配到除CPU以外所有必要的资源,只要获得处理器便可立即执行
执行状态:进程已获得处理器,其进程正在处理器上执行
阻塞状态:当正在执行的进程,由于等待某个事件发生而无法执行时,便处于阻塞状态
Linux进程调度算法
FCFS算法:也叫FIFO算法,先来先处理
时间轮片算法:对FIFO算法的改进,周期性的进行进程切换
STCF算法:短任务优先算法,核心是所有程序都有一个优先级,短任务的优先级比长任务的高,OS总是安排优先级高的进程先运行
2、 进程创建
2.1 getgid函数和getpid()函数
函数详解
cpp
表头文件
#include <unistd.h>
#include <sys/types.h>
定义函数
gid_t getgid(void)
函数说明
用来获得执行目前进程的组识别码
返回值
组识别码(进程的ID)
pid_t getpid(void)
获取当前进程ID
2.2 getppid函数
函数详解
cpp
表头文件
#include <unistd.h>
定义函数
pid_t getppid(void)
函数说明
获得目前进程的父进程识别码
返回值
目前进程的父进程识别码
综合案例
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char *argv[])
{
printf("My parent 'pid=%d'\n",getppid());
printf("My 'pid=%d'\n",getpid());
return 0;
}
运行结果
Kotlin
linux@ubuntu:~/test$ gcc get_dome.c -o get_dome -Wall
linux@ubuntu:~/test$ ./get_dome
My parent 'pid=14743'
My 'pid=14846'
2.3 fork函数(创建子进程)
函数详解
cpp
表头函数
#include <sys/types.h>
#include <unistd.h>
定义函数
pid_t fork(void)
函数说明
创建子进程,子进程完全复制父进程的资源,复制出的子进程有自己的task_struct结构和PID,复制父进程其他所有的资源
现在Linux中采用写时复制技术,即并不会立即复制,若后来确实发生了写入,父子进程的数据不一致,于是产生了复制动作,若直接调用exec执行另一个可执行文件,那将不会进行复制
父子进程谁先运行不确定,进行是是同步的
返回值
在父进程中返回子进程号,在子进程中返回0,错误返回-1
综合案例
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
pid_t child;
int i = 0;
/*创建子进程*/
if((child = fork()) == -1)
{
printf("fork ERRO\n");
exit(1);
}
else if(child == 0)
{
printf("my is child:%d\n",getpid());
i = 18;
printf("child i exit with %d\n",i);
}
else
{
printf("my is father:%d\n",getpid());
i = 41;
sleep(1);
printf("father i exit with %d\n",i);
}
printf("father and child have %d\n",getpid());
return 0;
}
运行结果
cpp
linux@ubuntu:~/test$ gcc fork_dome.c -o fork_dome -Wall
linux@ubuntu:~/test$ ./fork_dome
my is father:15057 //父进程执行打印,给i赋值,然后睡眠
my is child:15058 //子进程执行打印
child i exit with 18 //子进程给i赋值,打印
father and child have 15058 //子进程出了if判断后的打印
father i exit with 41 //父进程睡眠完了,虽然子进程给i另外赋值了,但fork创建的进程有自己独立的空间,所以子进程i的赋值不影响父进程
father and child have 15057
linux@ubuntu:~/test$
2.4 vfork函数
函数详解
cpp
表头函数
#include <unistd.h>
定义函数
pid_t vfork(void)
函数说明
创建子进程,与fork不同的是,进程是调用进程的一个副本,共享父进程的地址空间,
它会保证子进程先运行,子进程运行的时候会暂停父进程的执行,
直到子进程调用子进程调用exec族,或者exit()结束,父进程才会继续执行
返回值
成功在父进程会返回新建立的子进程代码(PID),而在新建立的子进程中返回0。如果失败直接返回-1
综合案例
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
pid_t child;
int i = 0;
/*创建子进程*/
if((child = vfork()) == -1)
{
printf("fork ERRO\n");
exit(1);
}
else if(child == 0)
{
printf("my is child:%d\n",getpid());
i = 18;
sleep(1);
printf("child i exit with %d\n",i);
exit(1);
}
else
{
printf("my is father:%d\n",getpid());
sleep(2);
printf("father i exit with %d\n",i); //i的值会因为子进程的中的改变而改变,在fork中会是0
}
printf("father and child have %d\n",getpid());
return 0;
}
运行结果
Kotlin
linux@ubuntu:~/test$ gcc vfork_dome.c -o vfork_dome -Wall
linux@ubuntu:~/test$ ./vfork_dome
my is child:15154 //先执行子进程,父进程挂起
child i exit with 18
my is father:15153
father i exit with 18 //父进程会因为子进程改变变量而改变
father and child have 15153
2.5 启动进程:exec族
int execl(const char *path,const char *arg,...);
int execlp(const char *file,const char *arg,...);
int execle(const char *path,const char *arg,...,char *const envp[]);
int execv(const char *path,char * const argv[]);
int execvp(const char *file,char * const argv[]);
int execve(const char *path,char * const argv[],char *const envp[]); //真正意义上的系统调用,其他都是再次基础上封装
2.5.1 execl函数
函数详解
cpp
头文件
#include <unistd.h>
定义函数
int execl(const char *path,const char *arg,...)
函数说明
path 函数所代表的文件路径 接下的代表执行该文件传送过去的参数
返回值
执行成功函数不会返回,执行失败则返回-1
2.5.2 execlp函数
函数详解
cpp
头文件
#include <unistd.h>
定义函数
int execlp(const char *file,const char *arg,...)
函数说明
从PATH环境变量所指目录中查找符合参数file的文件名 接下来的代表执行该文件传送过去的参数
返回值
成功不会返回 执行失败则直接返回-1
综合案例
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
pid_t child;
if((child = fork()) < 0)
{
printf("vfork error!\n");
exit(-1);
}
if(child == 0)
{
printf("my is child\n");
if(-1 == execlp("./a","hello","nihao",NULL))
{
printf("execlp error!\n");
}
}
else
{
printf("my is father,a.c comletion\n");
}
return 0;
}
运行结果
Kotlin
linux@ubuntu:~/test$ gcc a.c -o a -Wall
linux@ubuntu:~/test$ ./exec_dome
my is father,a.c comletion
my is child
linux@ubuntu:~/test$ 我是a程序,将打印传入的参数.
0:hello
1:nihao
2.5.3 execve函数(真正意义上的系统调用)
cpp
表头文件
#include <unistd.h>
定义函数
int execve(const char *path,char * const argv[],char *const envp[])
函数说明
filename 文件路径包含文件名
参数利用数组指针来传递给执行文件 末尾为(char*)0
传递给执行文件新的环境变量,末尾为0
返回值
成功不会返回 执行失败则直接返回-1
2.5.4 execvp函数
cpp
表头文件
#include <unistd.h>
定义函数
int execvp(const char *file,char * const argv[])
函数说明
从PATH环境变量所指目录中查找符合参数file的文件名
argv数组 存储可执行文件执行时需要的参数
返回值
成功不会返回 执行失败则直接返回-1
2.6 system函数
函数详解
cpp
头文件
#include <stdlib.h>
定义函数
int system(const char * string)
函数说明
用于执行shell命令
system()会调用fork()产生子进程,由子进程调用/bin/sh-c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程
在system()调用期间,SIGCHLD信号会被暂时搁置,SIGINT和SIGOUIT信号则会被忽略
返回值
当在调用/bin/sh时失败返回127,其他原因失败返回-1,若string为空指针,返回非零值
综合案例
cpp
#include <stdlib.h>
int main(int argc,char *argv[])
{
system("ls -al");
return 0;
}
运行结果
cpp
linux@ubuntu:~/test$ gcc system_dome.c -o system_dome
linux@ubuntu:~/test$ ./system_dome
total 92
drwxrwxr-x 2 linux linux 4096 9月 27 16:09 .
drwxr-xr-x 32 linux linux 4096 9月 27 14:23 ..
-rwxrwxr-x 1 linux linux 7193 9月 27 14:22 a
-rw-rw-r-- 1 linux linux 197 9月 27 14:22 a.c
-rwxrwxr-x 1 linux linux 7273 9月 27 14:20 exec_dome
-rw-rw-r-- 1 linux linux 382 9月 27 14:23 exec_dome.c
-rwxrwxr-x 1 linux linux 7348 9月 27 13:23 fork_dome
-rw-rw-r-- 1 linux linux 543 9月 27 13:23 fork_dome.c
-rwxrwxr-x 1 linux linux 7241 9月 27 12:58 get_dome
-rw-rw-r-- 1 linux linux 188 9月 27 12:59 get_dome.c
-rwxrwxr-x 1 linux linux 7167 9月 27 16:09 system_dome
-rw-rw-r-- 1 linux linux 87 9月 27 16:09 system_dome.c
-rw------- 1 linux linux 12288 9月 27 16:09 .system_dome.c.swp
-rwxrwxr-x 1 linux linux 7350 9月 27 13:52 vfork_dome
-rw-rw-r-- 1 linux linux 557 9月 27 13:53 vfork_dome.c
2.7 wait函数和waitpid函数
函数详解
cpp
头文件
#include <sys/types.h>
#include <sys/wait.h>
定义函数
pid_t wait(int *status)
函数说明
会暂停目前进程的执行,直到有信号到来或子进程结束为止。
status 返回子进程的结束状态值,可设置为NULL
通过WEXITSTATUS(status)来获取结束值
通过WIFEXITED(status) 判断是否为正常结束,正常结束为非0值
返回值
执行成功返回值子进程识别码(PID),错误返回-1
综合案例
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
pid_t pid;
int status,i;
if(fork() == 0)
{
printf("tis 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.eixt status=%d\n",pid,i);
}
return 0;
}
运行结果
Kotlin
linux@ubuntu:~/test$ gcc wait_dome.c -o wait_dome
linux@ubuntu:~/test$ ./wait_dome
tis is the child process .pid=24163
this is the parent process.wait for child...
child's pid=24163.eixt status=5
函数详解
cpp
头文件
#include <sys/types.h>
#include <sys/wait.h>
定义函数
pid_t waitpid(pid_t pid,int *status,int options)
函数说明
用于等待指定ID子进程中断或结束
pid 欲等待的子进程识别码
pid<-1:等待进程组识别码为pid绝对值的任何进程
pid=-1:等待任何子进程,相当于wait函数
pid=0: 等待进程组识别与目前进程相同的任何子进程
pid>0: 等待任何子进程识别码为pid的子进程
status 返回子进程的结束状态值
options 为0或下面数值的OR运算(|)组合
WNOHANG: 没有任何已经结束的子进程则马上返回,不予等待
WUNTRACED: 如果子进程进入暂停执行状态则马上返回,但结束状态不予理会
返回值
执行成功返回子进程识别码(PID),错误返回-1
2.8 exit函数和_exit函数
cpp
表头文件
#include <stdlib.h>
定义函数
void exit(int status)
函数说明
用于正常结束目前进程的执行,并把参数status返回给父进程
而所有的缓冲区数据会自动写回并关闭未关闭的文件
返回值
无
_exit同exit函数
不同点:
传递SIGCHLD信号给父进程,不处理标准I/O缓冲区
3、 守护进程
3.1 守护进程的基本概念
cpp
进程组:
一个或多个进程的集合,每个进程都有一个进程组ID,这个ID就是进程组长的ID
会话期:
一个或多个进程组的集合,每个会话有唯一一个会话首进程,会话ID为会话进程ID
控制终端:
每一个会话可以有一个单独的控制终端,与控制终端连接的会话首进程就是控制进程
创建守护进程过程中用到的一个关键函数------setsid
#include <unistd.h>
pid_t setsid(void);
用于创建一个新的会话期,实现以下效果:
摆脱原会话的控制,该进程变成新会话期的首进程
摆脱原进程组,称为一个新进程组的组长
摆脱终端控制
3.2 创建守护进程的一般步骤
cpp
(1) 通过fork函数创建子进程,父进程通过exit函数退出
(2) 在子进程中调用setsid函数,创建新的会话
(3) 再次通过fork函数创建一个子进程并让父进程退出
(4) 在子进程中调用chdir函数,让根目录"/"称为子进程的工作目录
(5) 在子进程中调用umask函数,设置进程的文件权限掩码为0
(6) 在子进程中关闭任何不需要的文件描述符
(7) 守护进程退出处理(结束)
利用库函数deamon创建守护进程
#include <unistd.h>
int daemon(int nochdir,int noclose);
综合案例
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
static int flag = 1;
void create_deamon(); //守护进程创建函数
void handler(int); //接受信号结束句柄
int main(int argc,char *argv[])
{
time_t t;
int fd;
create_deamon();
struct sigaction act;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(sigaction(SIGQUIT,&act,NULL))
{
printf("sigaction error.\n");
exit(0);
}
while(flag)
{
fd = open("/home/linux/test/dae.log",O_WRONLY|O_CREAT|O_APPEND,0644);
if(fd == -1)
{
printf("open error\n");
}
t = time(0);
char *buf = asctime(localtime(&t));
write(fd,buf,strlen(buf));
close(fd);
sleep(10);
}
return 0;
}
void handler(int sig)
{
printf("I got a signal %d\nI'm quitting.\n",sig);
flag = 0;
}
void create_deamon(){ //守护进程创建函数
pid_t pid;
pid = fork();
if(pid == -1)
{
printf("fork error\n");
exit(1);
}
else if(pid)
{
exit(0);
}
if(-1 == setsid())
{
printf("setsid error\n");
exit(1);
}
pid = fork();
if(pid == -1)
{
printf("fork error\n");
exit(1);
}
else if(pid)
{
exit(0);
}
chdir("/");
int i;
for(i = 0;i < 3;++i)
{
close(i);
}
umask(0);
return;
}
运行结果
cpp
linux@ubuntu:~/test$ gcc deamon_dome.c -o deamon_dome -Wall
linux@ubuntu:~/test$ ./deamon_dome
linux@ubuntu:~/test$ cat dae.log
Sun Sep 29 13:22:50 2024
Sun Sep 29 13:23:00 2024
Sun Sep 29 13:23:10 2024
linux@ubuntu:~/test$ ps -ef|grep 'deamon_dome'
linux 24622 1 0 13:22 ? 00:00:00 ./deamon_dome