进程:1、具有独立功能的程序的一次运行活动。
2、资源分配的最小单元。
程序:放到磁盘上的可执行文件。
进程是动态的,程序是静态的。
进程是暂时的,程序是长久的。
二者的组成也不同,一个程序可对应多个进程。
进程的生命周期(可人为感受到的):创建,运行,撤销。
1、创建:每个进程由它的父进程创建,在我们所使用的用户态中,最终的父进程都是1号进程(祖先进程)。
2、运行:也称进程运行的三态(1)、执行状态;
(2)、就绪状态:每个进程都有自己的执行空间比如说5ms,而另一个进程科能需要10ms执行,他们不肯能同时执行,那么其中一个便会处于时刻就绪状态等待到自己的执行时间便可执行;
(3)、等待(阻塞)状态:若在一个进程执行时遇到scanf这样的函数或中断时,它便会进入等待状态,等待你的操作或中断优先级高的那个进程执行完毕后再来执行该进程;
3、撤销:kill命令或者kill函数均可停止该进程的执行,如执行期间按Ctrl C便可取消执行,这便是撤销。
进程ID:标识进程的唯一数字
子进程ID:PID
父进程ID:PPID
进程互斥:有若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用。
临界资源:一次只允许一个进程访问。
临界区:访问临界资源的代码。
进程同步:一组并发进程访问同一共享资源时也是有先后顺序的。
进程调度:方式分为两种:1、抢占式 2、非抢占式
调度算法(4种):1、先来先服务
2、短进程优先
3、高优先级
4、时间片轮转:比如一个进程给5ms执行时间,一般与第三种算法一起使用。
死锁:多个进程因为竞争资源僵持住了,这些进程都将永远不能再向前推进
以下为关于进程的函数:
1、getpid(void);此函数用于查看当前这个进程的子进程标识PID
getppid(void);此函数用于查看当前这个进程的父进程标识PPID
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 printf("PID : %d\n", getpid());
8 printf("PPID : %d\n", getppid());
9
10 return 0;
11 }
2、fork();
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
void filewrite(int fd) // 写入文件函数
{
char a[10] = "zhang sss";
write(fd, a, strlen(a));
}
void fileread(int fd) //从文件中读取信息的函数
{
char b[10] = {0};
lseek(fd, 0, SEEK_SET);
read(fd, b, sizeof(b));
printf("b = %s\n", b);
}
int main()
{
pid_t pid; //定义一个子进程创建的返回值变量,当创建失败时返回-1,成功返回两个
int fd; //返回值,在Linux的red hat 中大部分情况下,第一个返回值为0,即
int a = 0; //子进程返回值,第二个为父进程返回值,返回子进程的PID
fd = open("xxx.c", O_RDWR | O_CREAT, S_IRWXU);
if(fd == -1)
{
perror("open");
exit(1);
}
pid = fork(); //创建子进程
if(-1 == pid)
{
perror("fork");
exit(1);
}
else if(0 == pid)
{
sleep(1); //由于一般先执行子进程,但子进程这里为从文件中
a++; //读取数据,故这里睡眠1s让下面的父进程先运行向文件
printf("a = %d\n", a); //中写入数据
fileread(fd); //此处打印 a = 1;
}
else
{
filewrite(fd);
a++;
printf("a = %d\n", a); //此处打印a = 1;
sleep(2); //睡眠2s避免在子进程结束前父进程就结束,那时子进程
} //未被父进程收回变为孤儿进程
a++;
printf("a = %d\n", a); 此处打印两次 a = 2;
return 0;
}
首先,fork函数创建出来的子进程是独立的进程,它所在的空间与父进程所在的空间并非同一空间,这是什么意思呢?当创建子进程成功时,先执行子进程,此时子进程实际与父进程共享着一段空间,因为你此时如果对父进程进行写操作或修改动作,系统便没有必要再给你复制这个父进程(即创建一个新的子进程),而如果你想往里面写数据或修改数据时(如a++),系统便会帮你复制这个父进程(即创建一个新的子进程),至于复制父进程的哪些内容呢?系统在给进程分配空间时,其实分配了4个G的虚拟内存空间,这段空间里分为两大部分,第一部分为1G内核,这是系统的空间,你无法操作,另一部分就是用户态。而用户态又分为4个部分,分别为栈空间,堆空间,代码段,全局数据区。了解了这个知识,我们回到刚刚那个子进程会复制父进程多少内容的问题上。子进程除了代码段不复制以外,其他的部分都复制。这就是fork函数的原理。
其次,代码中的a值的输出问题在此处作解释。这就要说到代码的执行顺序了,一般情况下,fork函数在red hat中大概率先执行子进程,但子进程执行的过程中,其实父进程同样也开始了。所以,上面的代码段中子进程上来就直接进入1s的睡眠,故这时候就执行父进程,父进程中的a++,是把父进程中代码段的a = 0自增1变为1,故输出1;然后再回去先执行子进程的a++,这是把子进程中的a = 0自增1,输出为1,然后写在两个判断外面的即最后面的这个a++和printf语句其实在父进程和子进程中都是存在的(就和上面定义的a = 0一样),故父子进程中均执行一次a++,故又要分别输出一个a = 2。这就是a的输出值。但是,如果把两个睡眠时间都去掉,那么函数的执行顺序又要变成先执行子进程中的a++(即输出a = 1),再执行子进程中最后面的a++(即输出a = 2),再去执行父进程中的两次a++。执行的顺序一定要弄清楚了。
3、vfork();
这个函数与fork函数用法类似,但又两个最明显的差异。
(1)、vfork创建的进程,一定是子进程运行结束后再运行父进程的,并且子进程要指定推出方式(如exit(1));
(2)、子进程与父进程共享同一段地址空间。这与进程互斥联系起来就直接导致了子进程与父进程不能同时运行。
4、exec族函数(是一个名字以exec开头的函数族群)
以execv为例,代码段如下:
下面为一个程序test.c 我们可以对它进行编译生成一个二进制文件test,它在系统中的绝对路径为/home/class/Jinchen/test
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
for(i = 1; i < argc; i++)
{
printf("%s\n", argv[i]);
}
return 0;
}
下面为是用了execv函数的程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main()
{
pid_t pid;
char *argv[] = {"./test", "123", "add", NULL}; //这里用一个指针数组保存数据
pid = vfork();
if(pid == -1)
{
perror("vfork");
exit(1);
}
else if(0 == pid)
{
execv("/home/class/Jinchen/test", argv); //execv函数的第一个参数为你所要打
sleep(2); //开的进程的绝对路径,第二个参数为数据
} //这里要配合命令行参数一起使用
else
{
printf("This is parent process!\n");
}
return 0;
}
这样便可在这个进程运行时运行另一个进程。
进程等待:即僵尸进程的产生,僵尸进程就是一个已经结束了的进程,却没有从进程列表中删除,它也不占用系统资源,仅仅残留在那儿,这便是一种进程等待。
先解释僵尸进程的产生,我们知道,子进程是由父进程创造产生的,但子进程运行完也必须由父进程回收才能从进程列表删除,比如在父进程中有一个执行很长时间的循环,那么子进程很快运行结束后父进程还久久不结束,这时候便产生了一个僵尸进程。
那么如何结束进程等待,或者说如何避免僵尸进程。我们要用到以下函数。
wait();与waitpid();具体代码段如下:(下明函数中的wait(pid,&status,0)改成waitpid(pid,&status,0))
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
void filewrite(int fd)
{
char a[10] = "zhang sss";
write(fd, a, strlen(a));
}
void fileread(int fd)
{
char b[10] = {0};
lseek(fd, 0, SEEK_SET);
read(fd, b, sizeof(b));
printf("b = %s\n", b);
}
int main()
{
pid_t pid;
int fd;
fd = open("xxx.c", O_RDWR | O_CREAT, S_IRWXU);
if(fd == -1)
{
perror("open");
exit(1);
}
pid = fork();
if(-1 == pid)
{
perror("fork");
exit(1);
}
else if(0 == pid)
{
sleep(1);
fileread(fd);
exit(1);
}
else
{
int status; //这里定义一个变量
filewrite(fd);
// wait(&status); //wait函数有一个参数为int *类型,故对int类型变量取
wait(pid, &status, 0); //地址便可,而waitpid有3个参数,第一个是你指定的某个
if(WIFEXITED(status)) //子进程的PID,第二个也是一个int *类型变量,第三个
{ //一般为0即可
printf("Exit nomally! status = %d\n", WEXITSTATUS(status));
} //WIFEXITED函数是一个用于判断子进程结束状态的函数,正常结束返回1
} //非正常结束返回0,WEXITSTATUS函数返回值为子函数的返回状态,如本
return 0; //程序的子进程以exit(1)结束那么返回值便为1
}
wait函数是任意一个子进程结束便收回该子进程且父进程不会继续等待其他子进程的结束。
waitpid函数是你指定的子进程即PID为第一个参数pid的子进程结束后便回收并结束父进程。
上述两个函数其实都是在暂停并等待子进程的结束。