以下介绍几个linux环境中可能会用到的进程控制和线程控制的函数,其它查找资料学习。
(一)进程控制
1. fork()
fork()是实现进程创建的系统调用,父进程利用它来创建新进程。父进程创建子进程时,把自己的地址空间制作一个副本,其中包括user结构、正文段、数据段、用户栈和系统栈。子进程是父进程的完整复制,子进程继承父进程的一切资源与现场,包括程序代码、程序计数器等所有寄存器的值,除了进程号和进程状态不同。
系统调用格式:
pid=fork()
参数定义:
int fork()
fork()返回值意义如下:
0:在子进程中,pid变量保存的fork返回值为0,表示当前进程是子进程。
>0:在父进程中,pid变量保存的fork返回值为子进程的PID值(进程唯一标识符)。
-1:创建失败。
如果fork调用成功,它向父进程返回子进程的PID,并向子进程返回0,即fork( )被调用了一次,但返回了两次。此时OS在内存中建立一个新进程,所建的新进程是调用父进程(parent process)的副本,称为子进程(child process)。子进程继承了父进程的许多特性,父进程与子进程并发执行,二者都可以继续执行fork系统调用之后的指令。
2. sleep()
sleep()是实现进程阻塞的系统调用,它使进程挂起指定的时间,直至指定时间用完或收到信号后被唤醒。
使用格式: sleep();
在linux中,sleep()是以秒为单位的,sleep(1)就是休眠1秒。
sleep把进程的运行状态改为睡眠,将其从系统可执行队列去掉,这样系统就不会调度到该进程,不会分配CPU时间片。同时根据该进程的睡眠时间,将进程挂入相应的定时器队列中。同时内核维持一个定时器队列,每一次时钟中断处理,都把当前到期的队列中的进程唤醒,加入到可运行进程队列中。
3. wait()
wait()是用来实现进程等待的系统调用,父进程可以使用它等待一个子进程的结束。
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程(说明:僵尸进程是指进程由于某些原因被终止,但该进程的控制结构task_struct仍保留着), wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,就会一直阻塞在这里,直到有一个出现为止。
wait的参数用来保存被收集进程退出时的状态,如果不关心状态可以用wait(NULL)。
4. exit()
exit()是实现进程终止的系统调用。
当程序执行到exit时,系统无条件的停止所有剩下的操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。exit中的参数为0代表进程正常终止,若为其他值表示程序执行过程中有错误发生。
5.execl()
系统调用execl()可以将新程序加载到某一进程的内存空间,该进程会从新程序的main()函数开始执行。声明在unistd.h头文件中。
函数原型:
int execl (const char * pathname, const char * arg0,…/*(char*)0*/)
pathname是要加载程序的全路径名,arg0及以后为参数列表。
进程的用户内存空间被替换,而进程的其他属性基本不改变。进程完整的内存空间:正文区、堆区、栈区、数据区都被替换,原内容不存在了。代码替换完后,在execl()后面的代码毫无意义。
(二)线程控制
pthread_create()、pthread_join()、pthread_exit()函数不是内核代码,它们是POSIX标准线程库中的函数,声明在头文件pthread.h头文件中,是用户级线程的管理方式,用于在C语言中创建线程、等待线程和结束线程。
它们并不直接涉及到操作系统的内核,但在管理线程过程中会涉及到内核的支持。比如:在Linux系统中,pthread_create函数是通过调用内核提供的clone系统调用来实现的,但这个调用发生在库函数内部,对用户来说,pthread_create提供了一种高级别的接口来创建和管理线程,而不需要直接操作内核级别的细节。实际上,Linux系统并不区分进程和线程,无论是通过fork创建进程还是通过pthread_create创建线程,它们在底层实现上都可能调用到同一个内核函数clone。
1.pthread_create()
作用:创建一个线程。
函数原型为:
int pthread_create (pthread_t * tidp, const pthread_attr_t * attr, void * (*start_rtn)(void*), void *arg);
pthread_create的功能是确定调用线程函数的入口点,并在线程创建后开始运行相关的线程函数。pthread_create函数的参数说明如下:
第一个参数是一个指向pthread_t类型的指针,用于存储新创建线程的线程ID。如果线程创建成功,由该指针指向的内存单元被设置为新创建线程的线程ID。
第二个参数用于设置线程属性,是一个指向pthread_attr_t类型的指针。这个参数可以用来定制各种不同的线程属性,如线程的调用策略、线程所能使用的栈内存的大小等。在大多数情况下,将此参数设为NULL,让pthread_create使用系统默认的属性值创建线程。
第三个参数是一个函数指针,指向新创建线程要运行的函数。这个函数只有一个参数,其类型为void*,可以接收任意类型的数据。如果需要向这个函数传递参数,可以通过将参数封装在一个结构体中,并将结构体的地址作为第三个参数传递。
第四个参数是传递给线程函数的参数,它可以是任何类型的数据,只要它能被正确地传递给线程函数。如果不需要传递参数,可以将此参数设为NULL。如果需要向start_rtn函数传递多个参数,应当使用结构体将这些参数打包,并将结构体的地址作为arg参数传递。
pthread_create函数的返回值表示线程创建的成功或失败状态:若成功,返回0;若出错,返回出错编号。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。
2.pthread_join()
作用:等待一个线程的结束。这是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。
函数原型为:
int pthread_join(pthread_t thread, void **retval);
第一个参数为被等待的线程标识符,第二个参数是指向被等待线程的返回码的指针的指针,它可以用来存储被等待线程的返回值。
pthrad_join()有两种作用:
(1)用于等待其他线程结束。当调用phread_join()时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会被重新开始执行。
(2)对线程的资源进行回收。如果一个线程是非分离的(默认情况下创建的线程都是非分离的)并且没有对该线程使用pthread_join()的话,该线程结束后并不会释放其内存空间,这会导致该线程变成“僵尸线程”。
另外,在很多情况下,主线程创建了子线程,如果子线程要进行大量的耗时运算,主线程往往会在子线程之前结束,所以通常会使用pthread_join()让主线程阻塞,等子线程结束。
3.pthread_exit()
作用:结束线程。
函数原型:
void pthread_exit(void *retval);
在线程中禁止调用exit函数,否则会导致整个进程退出,取而代之的是调用pthread_exit函数,这个函数只会使一个线程退出,如果主线程使用pthread_exit函数也不会使整个进程退出,不会影响其他线程的执行。retval通常传NULL。
(三)信号量的操作函数
1.sem_init() 函数
对信号量进行初始化,原型:
int sem_init(sem_t *sem,int pshared,unsigned int value);
该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1。
2.sem_wait() 函数
该函数用于以原子操作的方式将信号量的值减1。相当于P操作。
函数原型:
int sem_wait(sem_t *sem);
等待信号量,如果信号量的值大于0,将信号量的值减1,立即返回。如果信号量的值为0,则线程阻塞。成功返回0,失败返回-1。sem指向的对象是由sem_init调用初始化的信号量。
注意:会先等待该信号量为一个非0值时才做减1操作。如果对一个值为2的信号量调用sem_wait(),线程将会继续执行,将信号量的值减到1。如果对一个值为0的信号量调用sem_wait(),它会阻塞等待,但先不做减1。
sem_trywait(sem_t *sem)是函数sem_wait的非阻塞版,它直接将信号量sem减1,同时返回错误代码。
3.sem_post() 函数
该函数用于以原子操作的方式将信号量的值加1。相当于V操作。
函数原型:
int sem_post(sem_t *sem);
释放信号量,用于唤醒等待信号量的进程。当某个进程释放资源或完成其任务时,它会调用sem_post来增加信号量的值。这个操作会唤醒一个或多个由于等待信号量而阻塞的进程。这些被唤醒的进程可以继续执行并尝试获取资源。sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1。
4.sem_destroy() 函数
该函数用于对用完的信号量的清理。
函数原型:
int sem_destroy(sem_t *sem);
成功时返回0,失败时返回-1。
5.semget() 函数
semget函数用于创建或获取一个信号量集的标识符。信号量集是一个包含若干个信号量的数据结构,每个信号量都是一个整型值。
函数原型:
int semget(key_t key, int nsems, int semflg);
参数含义如下:
- key:一个整型值,用于标识信号量集。如果key为0,则系统会自动生成一个唯一的key值。
- nsems:表示信号量集中包含的信号量个数。
- semflg:表示创建信号量集的权限和行为。可以使用以下值:
-
- IPC_CREAT:如果信号量集不存在,则创建一个新的信号量集;如果信号量集已存在,则获取它的标识符。
- IPC_EXCL:与IPC_CREAT一起使用时,如果信号量集已存在,则调用semget()函数失败,并返回错误。
-
返回值:
-
-
- 成功:返回信号量集的标识符。
- 失败:返回-1,并设置errno为相应的错误码。
-
6.semop() 函数
System V信号量是通过 semop 函数和 sembuf 结构体的数据结构来进行PV操作,实现对信号量进行加减。semop 函数调用成功返回0,失败返回-1。
函数原型:
int semop(int semid,struct sembuf *sops,size_t nsops);
参数说明:
第1个参数 semid:为信号量集的标识符,可通过semget()获取。
第3个参数 nsops:指出将要进行操作的信号的个数,恒大于等于1。
第2个参数 sops:指向存储信号操作结构的数组指针,指向首地址,每个sembuf结构体对应一个特定信号的操作。信号操作结构的原型如下:
struct sembuf{
unsigned short sem_num; //信号在信号集中的索引,0代表第一个信号,1代表第二个信号。除非需要使用一组信号量,否则它的取值一般为0。
short sem_op; //操作类型,也是信号量在一次操作中需要改变的数值。通常用到两个值,一个是-1,即P操作,一个是+1,即V操作。
short sem_flg; //操作标志,通常为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放该信号量而终止时,操作系统释放信号量
};
- sem_op 参数:
若sem_op > 0,则进行V操作,即加1操作,释放控制的资源。
若sem_op < 0,则进行P操作,即减1操作,调用进程阻 塞,直到资源可用。
若 sem_op = 0,如果没有设置 IPC_NOWAIT,则调用进程进入睡眠状态,直到信号量的值为0;否则进程不会睡眠,直接返回错误 EAGAIN。
- sem_flg 参数:
该参数可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。
IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
SEM_UNDO:使得操作系统跟踪当前进程对这个信号量的修改情况。程序结束时(不论正常或不正常),如果没有释放该信号量,操作系统将自动释放该进程持有的信号量,保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
举例,下面是对一个信号量集中的某个信号进行 PV 操作的函数实现:
//P操作函数
int sem_p( int semid, int index )
{
struct sembuf buf = { 0, -1, IPC_NOWAIT};
if ( index < 0 )
{
perror ( "index of array cannot equals a minus value!\n" );
return -1;
}
buf.sem_num = index;
if ( semop ( semid, &buf, 1) == -1)
{
perroe ( " a wrong operation to semaphore occurred!\n" );
return -1;
}
return 0;
}
//V操作函数
int sem_p( int semid, int index )
{
struct sembuf buf = { 0, 1, IPC_NOWAIT};
if ( index < 0 )
{
perror ( "index of array cannot equals a minus value!\n" );
return -1;
}
buf.sem_num = index;
if ( semop ( semid, &buf, 1) == -1)
{
perroe ( " a wrong operation to semaphore occurred!\n" );
return -1;
}
return 0;
}
7.semctl() 函数
该函数用来直接控制信号量信息,可以设置信号量集的值、权限和其他属性,也可以用来查询信号量集的信息。
原型为:
int semctl(int sem_id, int sem_num, int command, ...);
前两个参数与前面一个函数中的一样,command通常是下面两个值中的一个:
SETVAL:用来把信号量初始化为一个已知的值。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。