进程的创建
一些基础知识:
什么是文件描述符表?参照stdin,stdout,stderr
什么是文件表项?包含文件状态标志、当前偏移量,i-node指针,引用计数器
什么是i节点? 包含文件长度、文件所有者、指向磁盘块的指针。
什么是进程表项?
1.进程的创建函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回:子进程中返回0, 父进程中返回子进程ID,出错返回-1
pid_t vfork(void);
返回:子进程中返回0, 父进程中返回子进程ID,出错返回-1
- fork创建的新进程被称为子进程,该函数被调用一次,但返回两次。两次返回的区别是:在子进程中的返回值是0,而在父进程中的返回值则是新子进程的进程ID。
- fork创建子进程,父子进程哪个先运行根据系统调度,且复制父进程的内存空间
- vfork创建子进程,但子进程先运行且不复制父进程的内存空间
注意:
main()
{
...
fork();
fork();
fork();
...
}
这里会产生 2^3个子进程, 一般不会用这种方式创建
案例1:父进程和子进程
/*
* 创建进程.c
*
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
printf("pid:%d\n",getpid());
pid_t pid;
pid=fork(); //创建子进程
//在fork之后会运行两个进程(父进程和子进程)
if(pid<0){
perror("fork error");
}
else if(pid>0){
//在父进程中fork返回的是子进程的ID
//这里写父进程执行代码
printf("I am parent process, pid is %d,ppid is %d, fork return is %d\n",\
getpid(),getppid(),pid);
}else{
//子进程(在子进程中fork返回0)
//这里写子进程执行代码
printf("I am child process, pid is %d,ppid is %d, fork return is %d\n",\
getpid(),getppid(),pid);
}
printf("pid: %d\n",getpid());
sleep(1);
exit(0);
}
案例2:进程的调度
- 父进程和子进程交替运行
- 父进程sleep时运行子进程,子进程sleep时运行父进程
- 使用fork(),父子交替运行
- 使用vfork(),子进程运行完毕,再运行父进程
/*
* 进程调度.c
*
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
printf("current pid:%d\n",getpid());
pid_t pid = fork(); //创建子进程
//pid_t pid = vfork(); //先运行子进程(直到运行完毕)再运行父进程
//在fork之后会运行两个进程(父进程和子进程)
if(pid<0){
perror("fork error");
}
else if(pid>0){
//在父进程中fork返回的是子进程的ID
//这里写父进程执行代码
for(int i=0;i<5;++i){
printf("I am parent process, pid is %d\n",getpid());
sleep(1);
}
}
else{
//子进程(在子进程中fork返回0)
//这里写子进程执行代码
for(int i=0;i<5;++i){
printf("I am child process, pid is %d\n",getpid());
sleep(1);
}
}
exit(0);
}
输出:
fork()输出:
current pid:7282
I am parent process, pid is 7282
I am child process, pid is 7283
I am parent process, pid is 7282
I am child process, pid is 7283
I am parent process, pid is 7282
I am child process, pid is 7283
I am parent process, pid is 7282
I am child process, pid is 7283
I am parent process, pid is 7282
I am child process, pid is 7283
vfork()输出:
current pid:7316
I am child process, pid is 7317
I am child process, pid is 7317
I am child process, pid is 7317
I am child process, pid is 7317
I am child process, pid is 7317
I am parent process, pid is 7316
I am parent process, pid is 7316
I am parent process, pid is 7316
I am parent process, pid is 7316
I am parent process, pid is 7316
2.继承的概念
- 子进程的继承属性
- 用户信息和权限、目录信息、信号信息、环境、共享存储段、资源限制、堆、栈和数据段,共享代码段。,虚拟地址和父进程一样
正文段物理内存共享,其他都独立物理内存
- 用户信息和权限、目录信息、信号信息、环境、共享存储段、资源限制、堆、栈和数据段,共享代码段。,虚拟地址和父进程一样
- 子进程特有属性
- 进程ID、锁信息、运行时间、未决信号
- 操作文件时的内核结构变化
- 子进程只继承父进程的文件描述表,不继承但共享文件表项和i-node
- 父进程创建一个子进程后,文件表项中的引用计数器加1变成2,当父进程作close操作后,计数器减1,子进程还是可以使用文件表项,只有当计数器为0时才会释放文件表项。
案例3:fprint和write的缓存操作区别
/*
* 子进程继承.c
* fprint和write的缓存操作区别
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
int global_v = 30;
int main(int argc, char **argv)
{
int auto_v = 30;
static int static_v = 30;
printf("pid:%d\n",getpid());
//标准IO调用
FILE* fp = fopen("s.txt","w");
//IO系统调用,不带缓存功能,直接操作
int fd = open("s_fd.txt",
O_WRONLY|O_CREAT|O_TRUNC,S_IRWXU|S_IRWXG);
//O_WRONLY: write only
//S_IRWXU:00700 user (file owner) has read, write, and execute permission
// S_IRWXG 00070 group has read, write, and execute permission
char *s = "hello world";
ssize_t size = strlen(s)*sizeof(char);
/*父进程调用*/
//标准IO函数(带缓存-->全缓存)
fprintf(fp,"s: %s, pid: %d\n", s, getpid()); //写入缓存
//内核提供的IO系统调用(不带缓存)
write(fd, s, size); //直接写入文件
pid_t pid;
pid=fork(); //创建子进程
//在fork之后会运行两个进程(父进程和子进程)
if(pid<0){
perror("fork error");
}
else if(pid>0){
//在父进程中fork返回的是子进程的ID
//这里写父进程执行代码
global_v=40, auto_v=40, static_v=40;
printf("I am parent process, pid is %d,ppid is %d, fork return is %d\n",getpid(),getppid(),pid);
printf("g_v: %p, a_v: %p, s_v: %p\n",&global_v, &auto_v, &static_v);
}else{
//子进程(在子进程中fork返回0)
//这里写子进程执行代码
global_v=50, auto_v=50, static_v=50;
printf("I am child process, pid is %d,ppid is %d, fork return is %d\n",getpid(),getppid(),pid);
printf("g_v: %p, a_v: %p, s_v: %p\n",&global_v, &auto_v, &static_v);
}
//父子进程都要执行(写入缓存)
fprintf(fp, "pid: %d, g_v: %d, a_v: %d, s_v: %d\n",getpid(),global_v, auto_v, static_v);
sleep(1);
fclose(fp);
close(fd);
exit(0);
}
输出:
pid:8859
I am parent process, pid is 8859,ppid is 8849, fork return is 8860
g_v: 0x21054, a_v: 0xbee9f750, s_v: 0x21058
I am child process, pid is 8860,ppid is 8859, fork return is 0
g_v: 0x21054, a_v: 0xbee9f750, s_v: 0x21058
-------------------------------------------------------------
s.txt:
s: hello world, pid: 8859
pid: 8859, g_v: 40, a_v: 40, s_v: 40
s: hello world, pid: 8859
pid: 8860, g_v: 50, a_v: 50, s_v: 50
--------------------------------------------------------------
s_fd.txt:
hello world
案例4:父进程调节文件偏移量,子进程追加写入
/*
* process_append.c
* 父进程调节文件偏移量,子进程追加写入
*/
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
if(argc < 2){
fprintf(stderr,"usage: %s file\n", argv[0]);
exit(1); //exit(int status); status -- 返回给父进程的状态值。
}
int fd = open(argv[1], O_WRONLY);
//fd=file descriptor文件描述符
if(fd<2){
perror("open error");
exit(1);
}
pid_t pid = fork(); //创建子进程
if(pid>0){ //parent process
//父进程将文件偏移量调整到文件尾部
if(lseek(fd, 0L, SEEK_END) <0 ){ //off_t lseek(int fd, off_t offset, int whence);
perror("lseek error");
exit(1);
}
}
else if(pid==0){ //child process
//子进程从文件尾部追加内容
char* str = "\nhello world";
ssize_t size = strlen(str) * sizeof(char);
sleep(3); //子进程睡眠3秒以上,保证父进程执行完毕
//此处的fd是从父进程中复制过来的
//但是和父进程中的fd都是指向同一个文件
if(write(fd, str, size) != size){
perror("write error");
exit(1);
}
}
else{
perror("fork error");
exit(1);
}
printf("pid: %d finish\n",getpid());
sleep(1);
//父子进程都要去关闭文件描述符
close(fd);
return 0;
}