【操作系统实验】lab1 进程及线程的创建

fork函数

fork() 是一个系统调用函数,用于在一个进程中创建一个新的进程,新进程与原进程具有相同的代码和数据空间,但拥有独立的内存空间、程序计数器(PC)和文件描述符等。在 Unix 和类 Unix 系统中,fork() 函数是创建新进程的基础。新进程被称为子进程,原进程被称为父进程。

fork() 被调用时,操作系统会复制父进程的地址空间,包括所有的代码、数据和堆栈等。新进程和原进程将在fork() 调用处分别继续执行(并发执行)。区别在于fork函数在新进程将返回 0 值,而父进程将返回子进程的进程 ID。子进程会继承父进程打开的文件描述符,但是它们各自拥有独立的文件描述符表,因此对于一个文件的读写操作在两个进程中是相互独立的

如果出现错误,fork返回一个负值。

fork工作流程

【操作系统实验】lab1 进程及线程的创建_第1张图片

实验一、进程及线程创建

1、通过判断fork的返回值让父子进程执行不同的语句。
/*
	pid_t是数据类型,实际上是一个整型,通过typedef重新定义了一个名字,用于存储进程id.
	getpid()函数返回当前进程的id号.
	getppid()函数返回当前进程的父进程的id号.
*/

#include
#include
#include
int main(){
	pid_t cid;   

	printf("Before fork Process id: %d\n",getpid());

	cid = fork();

	if(cid == 0){
        //子进程执行的代码
		printf("Children process id: %d, my parent process id:%d\n",getpid(),getppid()); 
		for(int i = 0;i < 300;i++){
			printf("hello\n");
		}
	}else{
        //父进程执行的代码
		printf("Parent process id: %d\n",getpid());
		for(int i = 0;i < 300;i++){
			printf("world\n");
		}
	}
	return 0;
}

/*
验证性实验:
	验证父子进程id号是否正确。
	验证父子进程是否并发执行。可能结果不是,增大循环打印的次数试试。
*/
2、验证父子进程间的内存空间是相互独立的
/*
	wait函数会挂起调用它的进程,直到任意一个子进程结束(即子进程结束前父进程一直处于等待状态)。
	sleep函数让调用进程睡眠指定时间(单位为second)。
	这样组合可以让子进程执行完printf语句后睡眠3s之后父进程再执行printf语句。
*/

#include 
#include 
#include 
#include 

int main(){
	pid_t cid;
	int x = 100;

	cid = fork();

	if(cid == 0){
		x++;
		printf("In child x=%d\n",x);
		sleep(3);
	}else{
		x++;
		wait(NULL);
		printf("In parent x=%d\n",x);
		
	}
	return 0;
}

/*
	子进程和父进程输出的x的值是一样的。
	因为子进程和父进程的内存空间是相互独立的。
*/
3、创建线程

pthread_create函数

pthread_create()是一个函数,用于创建一个新的线程。它是POSIX线程库(或者称为Pthreads库)提供的函数之一。

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
  • thread:一个指向pthread_t类型变量的指针,用于存储新创建的线程ID。
  • attr:一个指向pthread_attr_t类型变量的指针,用于指定新线程的属性。如果为NULL,则使用默认属性。
  • start_routine:一个指向函数的指针,新线程将从该函数开始执行。函数的参数是一个指向void类型的指针,返回值是一个指向void类型的指针。
  • arg:传递给start_routine函数的参数。arg是一个指向void类型的指针,用于传递给新线程的参数。具体来说,arg指向的内存区域中存储了需要传递给新线程的数据或指针。

pthread_create()函数的返回值是0,如果出现错误则返回一个非零的错误代码。

​ 调用pthread_create()函数后,系统会创建一个新线程,该线程会从指定的函数开始执行。新线程会与原来的线程并发运行,各自独立地执行自己的任务,但是它们共享相同的进程空间。新线程的执行状态和资源都是独立于原来的线程的,包括线程ID、线程栈、寄存器等。

pthread_join函数

pthread_join() 是一个 POSIX 线程库中的函数,它用于等待一个指定的线程结束

int pthread_join(pthread_t thread, void **retval);
  • thread 参数是要等待的线程标识符

  • retval 参数是一个指向指针的指针,用于存储被等待线程的退出状态。

​ 调用 pthread_join() 函数的线程会一直阻塞,直到指定的线程退出并且其资源被释放。如果线程已经退出,那么调用 pthread_join() 会立即返回。如果 retval 参数不是 NULL,那么被等待线程的退出状态将被存储在 *retval 中。

pthread_join() 函数一般用于在多线程程序中同步各个线程的执行。通过等待其他线程的结束,主线程可以保证在这些线程完成它们的任务之前不会继续执行。

#include 
#include 
#include 
#include 

void* threadFunction(void* arg){
	printf("In new thread\n");
}

int main(){
	pthread_t tid;  //这里的pthread_t与上文中pid_t相似

	pthread_create(&tid,NULL,threadFunction,NULL);

	pthread_join(tid,NULL);

	printf("In main thread\n");

	return 0;
}
/*
	因为pthread的库不是linux系统的库,所以在进行编译的时候要加上 -lpthread
	gcc -o threadDemo threadDemo.c -lpthread
*/

​ 若将程序中的15行注释掉,则程序可能不会输出 “In new thread”。
​ 原因:如果没有调用 pthread_join() 函数,那么主线程会继续向下执行,直到程序结束。此时,新线程还没有执行 printf 语句或者根本都没有开始执行,但是主线程已经退出了,因此新线程就会被强制终止。

附录:相关头文件

是 C 和 C++ 语言中的头文件之一,用于多线程编程。在支持多线程的操作系统中,使用线程可以充分利用多核 CPU,提高程序的性能和响应速度。该头文件定义了一些重要的数据类型和函数,以实现线程的创建、管理、同步和通信。

  • pthread_t

pthread_t 数据类型用于标识一个线程,可以通过 pthread_create() 函数创建一个新线程,并返回该线程的 ID。pthread_t 类型的变量通常定义为全局变量,以便多个线程之间共享访问。

  • pthread_attr_t

pthread_attr_t 数据类型用于设置新线程的属性,如线程的栈大小、调度策略等。可以使用 pthread_attr_init() 函数初始化一个线程属性对象,并通过 pthread_create() 函数将其传递给新线程。

  • pthread_mutex_t

pthread_mutex_t 数据类型用于实现互斥锁,以保护共享资源的访问。可以使用 pthread_mutex_init() 函数初始化一个互斥锁,并使用 pthread_mutex_lock()pthread_mutex_unlock() 函数获取和释放锁。

  • pthread_cond_t

pthread_cond_t 数据类型用于实现条件变量,以便线程之间进行同步和通信。可以使用 pthread_cond_init() 函数初始化一个条件变量,并使用 pthread_cond_wait()pthread_cond_signal()pthread_cond_broadcast() 函数等待和唤醒条件变量。

  • pthread_key_t

pthread_key_t 数据类型用于创建线程特定数据的键,以便每个线程可以访问自己的私有数据。可以使用 pthread_key_create() 函数创建一个新的线程特定数据键,并使用 pthread_setspecific()pthread_getspecific() 函数设置和获取线程特定数据。

是一个C语言标准库头文件,主要包含了一些对POSIX操作系统的标准函数和常量的声明。unistd是UNIX Standard的缩写,因此该头文件通常被称为"UNIX Standard Header"。

该头文件包含了一些基本的系统调用和常量,如进程控制、文件操作、系统信息查询等,这些函数和常量可以用于跨平台的C程序开发。

该头文件中常用的函数和常量有:

  • getpid(): 获取当前进程的ID。
  • getppid(): 获取当前进程的父进程ID。
  • fork(): 创建一个新进程。
  • exec*(): 替换当前进程的代码,用新的程序执行。
  • pipe(): 创建一个管道。
  • chdir(): 改变当前工作目录。
  • getcwd(): 获取当前工作目录。
  • access(): 检查文件是否存在及访问权限。
  • close(): 关闭文件描述符。
  • read(): 从文件描述符读取数据。
  • write(): 向文件描述符写入数据。
  • sleep(): 暂停当前进程一定时间。
  • exit(): 结束当前进程。
  • STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO: 标准输入、标准输出和标准错误的文件描述符。

需要注意的是,头文件中的函数和常量是基于POSIX标准的,因此在跨平台开发时需要特别注意兼容性。

是一个C语言标准库头文件,用于定义一些系统数据类型和结构体,通常用于系统编程和底层操作。该头文件是Unix和类Unix操作系统中广泛使用的标准头文件之一。

该头文件定义了一些与系统数据类型和结构体相关的类型和常量,常用的有:

  • pid_t: 进程ID类型
  • uid_t: 用户ID类型
  • gid_t: 组ID类型
  • mode_t: 文件权限类型
  • off_t: 文件偏移量类型
  • size_t: 无符号整数类型,通常用于表示内存块的大小等
  • time_t: 时间类型
  • struct timeval: 时间结构体
  • struct timespec: 时间结构体,用于支持纳秒级别的时间计算

除此之外,该头文件还定义了一些常用的系统调用的函数原型,比如open()read()write()等等。

需要注意的是,由于是Unix和类Unix操作系统中广泛使用的标准头文件之一,因此在跨平台开发时需要特别注意兼容性。

是C和C++语言中的一个头文件,它包含了在系统级进程控制中使用的函数和宏。该头文件定义了一些用于等待进程状态改变的函数,如wait()waitpid()等。在Unix和类Unix系统中,这些函数是非常有用的,可以用于父进程等待子进程结束,获取子进程的退出状态、资源使用情况等信息。

该头文件的主要内容如下:

  1. 函数wait():在子进程结束后等待其状态的改变,并返回子进程的PID以及退出状态。
  2. 函数waitpid():等待指定PID的子进程结束或者改变状态,并返回子进程的PID以及退出状态。
  3. 宏WIFEXITED():用于测试wait()/waitpid()返回的子进程状态是否是正常终止的状态。
  4. 宏WEXITSTATUS():用于获取进程正常退出的返回值。
  5. 宏WIFSIGNALED():用于测试wait()/waitpid()返回的子进程状态是否是被信号终止的状态。
  6. 宏WTERMSIG():用于获取导致进程终止的信号编号。
  7. 宏WIFSTOPPED():用于测试wait()/waitpid()返回的子进程状态是否是被暂停的状态。
  8. 宏WSTOPSIG():用于获取导致进程被暂停的信号编号。

你可能感兴趣的:(unix,linux,笔记)