进程控制
进程号
每个进程都由一个进程号来标识, 其类型为 pid_t, 进程号的范围: 0~32767( 2^15 )
进程号总是唯一的, 但进程号可以重用
当一个进程终止后, 其进程号就可以再次使用了
在 linux 系统中进程号由 0 开始
进程号为 0 及 1 的进程由内核创建
进程号为 0 的进程通常是调度进程, 常被称为交换进程(swapper)
进程号为 1 的进程通常是 init 进程
除调度进程外, 在 linux 下面所有的进程都由 进程 init 进程直接或者间接创建
进程号(PID):
标识进程的一个非负整型数
父进程号(PPID):
任何进程(除 init 进程)都是由另一个进程创建, 该进程称为 被创建进程的父进程, 对应的进程号称为父进
程号(PPID)
进程组号(PGID):
进程组是一个或多个进程的集合
他们之间相互关联, 进程组可以接收同一终端的各种信号,关联的进程有一个进程组号(PGID)
Linux 操作系统提供了三个获得进程号的函数 getpid()、 getppid()、 getpgid()
例子:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid_t ppid;
pid_t pgid;
// 获取本进程号
pid = getpid();
// 获取调用此函数的进程的父进程号
ppid = getppid();
// 获取进程组号,参数为 0 时返回当前 PGID, 否则返回参数指定的进程的 PGID
pgid = getpgid(pid);
printf("pid = %d\n",pid);
printf("ppid = %d\n",ppid);
printf("pgid = %d\n",pgid);
return 0;
}
结果:
进程的创建 fork 函数
#include <sys/types.h>
#include <unistd.h>
/*
* 从一个已存在的进程中创建一个新进程, 新进程称为子进程, 原进程称为父进程
*return:
* 成功:子进程返回0 父进程返回子进程ID
* 错误:-1
*/
pid_t fork(void);
pid_t vfork(void);
地址空间:
进程上下文、 进程堆栈、 打开的文件描述符、 信号控制设定、 进程优先级、 进程组号
子进程所独有的只有它的进程号, 计时器
创建一个子进程实现多任务:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork");
}
if(0 == pid)
{
while(1)
{
printf("this is son process\n");
sleep(1);
}
}
else
{
while(1)
{
printf("this is father process\n");
sleep(1);
}
}
return 0;
}
结果:
验证父子进程分别有各自独立的地址空间
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int var = 10;
int main(int argc, char *argv[])
{
pid_t pid;
int num = 9;
pid = fork();
if(pid < 0)
{
perror("fork");
}
if(0 == pid)
{
var++;
num++;
printf("in son process var = %d, num = %d\n",var, num);
}
else
{
sleep(1);
printf("in father process var= %d, num = %d\n",var, num);
}
printf("common code area\n");
return 0;
}
结果:
结论:
子进程对变量改变并不影响 父进程中该变量的值, 说明父子进程各自拥有自己的地址空间
在 fork 之后是父进程先执行还是子进程先执行是不确定,这取决于内核所使用的调度算法
如 要求父子进程之间相互同步, 则要求某种形式的进程间通信
标准 I/O 提供三种类型的缓冲:
全缓冲:(大小不定)
填满标准 I/O 缓冲区后, 才进行实际的 I/O 操作
术语 冲洗缓冲区的意思:进行标准 I/O 写操作
行缓冲: (大小不定)
在遇到换行符时, 标准 I/O 库执行 I/O 操作
这种情况允许我们一次输入一个字符, 但只有写了一行后才进行实际的 I/O 操作
不带缓冲
验证子进程继承父进程的缓冲区:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
pid_t pid;
int length = 0;
char buf[] = "a write to stdout\n";
length = write(1, buf, strlen(buf));
if(length != strlen(buf))
{
printf("write error\n");
}
printf("before fork\n");
pid = fork();
if(pid < 0)
{
perror("fork");
}
if(0 == pid)
{
printf("in son process\n");
}
else
{
sleep(1);
printf("in father process\n");
}
return 0;
}
打印:
提示:
调用 fork 函数后, 父进程打开的文件描述符都被复制到子进程中
在重定向父进程的标准输出时, 子进程的标准输出也被重定向
write 函数是系统调用, 不带缓冲
标准 I/O 库是带缓冲的, 当以交互方式运行程序时, 标准 I/O 库是是行缓冲的, 否则它是全缓冲的