操作系统系列:关于Posix线程的系统调用

1 Posix线程系统调用

Posix 标准定义了许多线程系统调用。

1.1 pthread_create

在同一进程中创建新线程的 posix 函数具有以下函数原型:

#include <pthread.h>

int pthread_create(pthread_t *thread,  const  pthread_attr_t
*attr, void *(*start_routine, void*),void *arg);

该系统调用有四个参数,第一个 *thread 是指向线程 ID 号的指针(pthread_t 类型是 int),调用程序后续需要该值来进行线程同步。
数据类型 pthread_attr_t 允许调用程序设置线程的一些属性,例如堆栈的大小,默认设置为NULL即可。
第三个参数 start_routine 是新创建的线程将启动的函数的名称。 该函数必须具有以下原型:void *start_routine(void *arg),(start_routine 替换为实际使用的函数名称),该函数要使用一个参数,最后一个参数就是指向该参数的指针。
通常情况下,如果 pthread_create 调用成功创建新线程,则返回零;如果失败,则返回负值;如果失败,外部全局变量 errno 将被设置为指示失败原因的值。
所有线程都同时开始运行,所以无法保证哪个线程将首先运行,在竞争条件下,两个或多个事件几乎同时发生,并且不能保证一个事件会在另一个事件之前发生,事件的顺序是不确定的。
有一种可能性是主调用线程可以在所有线程终止之前终止。 由于所有线程都在同一个进程空间中运行,因此当主线程终止时,整个进程就会死亡。 而且,任何线程如果调用exit(),整个进程都会立即终止,这意味着某些线程可能在完成其工作之前就终止了。

1.2 pthread_exit

还有另外两个 posix 线程系统调用可以帮助解决这个问题。 要终止单个线程而不终止整个进程,请使用系统调用

void pthread_exit(void *value_ptr);

参数是指向返回值的指针,可以将其设置为 NULL。

1.3 pthread_join

调用线程可以等待特定线程随着调用而终止:

int pthread_join(pthread_t thread, void **value_ptr); 

第一个参数是进程正在等待的线程 ID;
第二个参数是指向线程传回的参数的指针,可以将其设置为 NULL。
该函数将阻塞,直到线程终止。 请注意该函数与 wait 函数在功能上的相似之处,后者等待子进程死亡。

1.4 示例1

这是一些非常简单的线程代码:

/* Alert: link to the pthreads library by appending
   -lpthread to the compile statement */
#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h> /* for sleep */
#include <stdlib.h> /* for malloc */

#define NUM_THREADS 5

void *sleeping(void *);   /* thread routine */
pthread_t tid[NUM_THREADS];      /* array of thread IDs */

int main()
{
  int *sleeptime, i, retval;
  for ( i = 0; i < NUM_THREADS; i++) {
     sleeptime = (int *)malloc(sizeof(int));
     *sleeptime = (i+1)*2;
     retval =pthread_create(&tid[i], NULL, sleeping, 
             (void *)sleeptime);
     if (retval != 0) {
         perror("Error, could not create thread");
     }
  }
  for ( i = 0; i < NUM_THREADS; i++)
      pthread_join(tid[i], NULL);
  printf("main() reporting that all %d threads have terminated\n", i);
  return (0);
}  /* main */

void *sleeping(void *arg)
{
    int sleep_time = *(int *)arg;
    printf("thread %d sleeping %d seconds ...\n", 
           pthread_self(), sleep_time);
    sleep(sleep_time);
    printf("\nthread %d awakening\n", pthread_self());
    return (NULL);
}

将参数传递给线程例程需要特别注意,参数是一个指针。
重要的是,通常每个线程的参数指向不同的内存,这就是为什么调用线程的循环每次都使用 malloc 分配新内存。
另外,请确保调用函数在创建线程后不会更改 arg 指向的内存值。 因为调用函数和新创建的线程之间存在竞争条件,所以我们不知道线程还是调用函数会先运行。

如果您以明显(但错误)的方式编写循环,如下所示:

  for (i=0;i<5;i++) {
    retval = pthread_create(&threadIds[i], NULL,
             ThreadRoutine, &i);
    ...

这段程序也会运行,而不会出现错误,但所有五个线程的参数可能都是相同的,而不是每个线程都不同,因为您每次传递相同的地址。

使用 pthread_exit() 从将死的线程返回一个值会考验开发者的指针技巧,该函数的参数是一个 void 指针,void 指针可以指向任何数据类型。
函数 pthread_join 的第二个参数是一个指向 void 的指针,它也可以指向任何特定的数据类型(大概与 pthread_exit() 的参数类型相同。当对 pthread_join 的调用返回时,第二个参数将指向 为该线程的 pthread_exit 返回的参数。

1.5 示例2

这是一个制作文件的副本简短的程序。

  • 要复制的文件的名称作为参数传递给 main(最多 10 个),为每个文件创建一个单独的线程;
  • 复制文件的名称是通过在文件名前添加字符串 Copy_Of_ 来形成的(例如,如果文件名是 myfile,则副本将称为 Copy_Of_myfile);
  • 该线程返回复制的字节数,然后主线程显示所有文件中复制的字节总数。
/* A program that copies files in parallel
   using pthreads */
/* Alert: link to the pthreads library by appending
   -lpthread to the compile statement */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

/* pthread_t copy_tid;*/

extern int errno;
#define BUFSIZE   256

void *copy_file(void *arg)
{
    int infile, outfile;
    int bytes_read = 0;
    int bytes_written = 0;
    char buffer[BUFSIZE];
    char outfilename[128];
    int *ret;

    ret = (int *)malloc(sizeof(int));
    *ret = 0;
    infile = open(arg,O_RDONLY);
    if (infile < 0)  {
        fprintf(stderr,"Error opening file %s: ", 
             (char *)arg);
        fprintf(stderr,"%s\n",strerror(errno));
        pthread_exit(ret);
    }
    strcpy(outfilename,"Copy_Of_");
    strcat(outfilename,arg); 
    outfile = open(outfilename,O_WRONLY | O_CREAT | O_TRUNC, 
             S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if (outfile < 0) {
        fprintf(stderr,"Error opening file %s",outfilename);
        fprintf(stderr,"%s\n",strerror(errno));
        pthread_exit(ret);
    }
    while (1) {
         bytes_read = read(infile, buffer, BUFSIZE);
         if (bytes_read == 0) 
            break;
	  else if (bytes_read < 0) {
		  perror("reading");
                  pthread_exit(ret);
	  }
	  bytes_written = write(outfile, buffer, bytes_read);
          if (bytes_written != bytes_read) {
	         perror("writing");
                 pthread_exit(ret);
	  }
          *ret += bytes_written;
    }
    close(infile);
    close(outfile);
    pthread_exit(ret);
    return ret; /* we never get here, but the compiler
                      likes to see this line */
}

int main(int argc, char *argv[])
{
    pthread_t tid[10]; /* max of 10 possible threads */

    int total_bytes_copied = 0;
    int *bytes_copied_p;
    int i;
    int ret;

    for (i=1;i < argc;i++) {
      ret = pthread_create(&tid[i], NULL, copy_file, 
               (void *)argv[i]);
      if (ret != 0) {
           fprintf(stderr,"Could not create thread %d: %s\n",
            i, strerror(errno)); 
           fprintf(stderr,"ret is %d\n",ret);
      }
    }
    /* wait for copies to complete */

    for (i=1;i < argc;i++) {
      ret = pthread_join(tid[i],(void **)&(bytes_copied_p));
      if (ret != 0) {
         fprintf(stderr,"No thread %d to join: %s\n",
                 i, strerror(errno));
      }
      else {
         printf("Thread %d copied %d bytes from %s\n",
               i, *bytes_copied_p, argv[i]);
         total_bytes_copied += *bytes_copied_p;
      }
    }
    printf("Total bytes copied = %d\n",total_bytes_copied);
    return 0;
}

请特别注意 pthread_exit 将值(本例为整数)传递回主线程的方式; 并注意主线程如何使用 pthread_join 的第二个参数来访问该值。
您可能会发现这个语法 (void ** )&(bytes_copied_p) 有点怪,变量 bytes_copied_p 是一个指向整数的指针。 通过在其前面加上一个 amersand (&),我们将其地址传递给该函数。 这将允许函数更改它所指向的内容,这是一件好事,因为当您调用该函数时,它并不指向任何地方。 该函数更改其值,使其指向 thread_exit 的参数。 (void **) 将此值转换为指向指针的指针,符合编译器要求。

1.6 示例3

使用线程的程序的典型设计是有一个主线程,该主线程创建一定数量的子线程,然后等待它们终止,上面的两个例子都采用了这种设计。 但实际上,任何线程都可以创建新线程,并且父线程不必等待子线程终止。

如果一个线程想要返回多个值,您可以创建一个包含所有返回值的结构,并返回一个指向该结构实例的指针。 请看这段代码:

/* On some systems you will need to link to the pthread library by
   appending -l pthread to the compile line */

#include 
#include 
#include 

struct thedata {
  int x;
  char s[32];
};
 
void *threadroutine(void *arg)
{
  struct thedata *t = (struct thedata *)malloc(sizeof(struct thedata));
  t->x = 17;
  strcpy(t->s,"Hello world!");
  pthread_exit(t);
}

int main()
{
  struct thedata *q;
  pthread_t n;
  int retval;

  retval = pthread_create(&n,NULL,threadroutine,NULL);
  if (retval < 0) {
    printf("error, could not create thread\n");
    exit(0);
  }
  /***************************************************
       write code here to get the return value from the
       thread and display the results.  Make sure that
       your code does routine error checking
   *****************************************************/
}
  • 44
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论
实验一 linux 内核编译及添加系统调用 设计目的 Linux 是开源操作系统,用户可以根据自身系统需要裁剪、修改内核,定制出功能更加 合适、运行效率更高的系统,因此,编译 linux 内核是进行内核开发的必要基本功。 在系统中根据需要添加新的系统调用是修改内核的一种常用手段,通过本次实验,读 者应理解 linux 系统处理系统调用的流程以及增加系统调用的方法。 内容要求 (1) 添加一个系统调用,实现对指定进程的 nice 值的修改或读取功能,并返回进程最 新的 nice 值及优先级 prio。建议调用原型为: int mysetnice(pid_t pid, int flag, int nicevalue, void __user * prio, void __user * nice); 参数含义: pid:进程 ID。 flag:若值为 0,表示读取 nice 值;若值为 1,表示修改 nice 值。 Prio、nice:进程当前优先级及 nice 值。 返回值:系统调用成功时返回 0,失败时返回错误码 EFAULT。 (2) 写一个简单的应用程序测试(1)中添加的系统调用。 (3) 若程序中调用了 linux 的内核函数,要求深入阅读相关函数源码。 实验二 linux 内核模块编程 设计目的 Linux 提供的模块机制能动态扩充 linux 功能而无需重新编译内核,已经广泛应用在 linux 内核的许多功能的实现中。在本实验中将学习模块的基本概念、原理及实现技术,然后利 用内核模块编程访问进程的基本信息,从而加深对进程概念的理解、对模块编程技术的掌 握。 内容要求 (1) 设计一个模块,要求列出系统中所有内核线程的程序名、PID 号、进程状态及 进程优先级。 (2) 设计一个带参数的模块,其参数为某个进程的 PID 号,该模块的功能是列出该 进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID 号。 (3) 请根据自身情况,进一步阅读分析程序中用到的相关内核函数的源码实现。 实验四 linux 进程管理 设计目的 (1) 熟悉 linux 的命令接口。 (2) 通过对 linux 进程控制的相关系统调用的编程应用,进一步加深对进程概念的理解, 明确进程和程序的联系和区别,理解进程并发执行的具体含义。 (3) 通过 Linux 管道通信机制、消息队列通信机制、共享内存通信机制的使用,加深 对不同类型的进程通信方式的理解。 (4) 通过对 linux 的 Posix 信号量的应用,加深对信号量同步机制的理解。 (5)请根据自身情况,进一步阅读分析相关系统调用的内核源码实现。 设计内容 (1)熟悉 linux 常用命令:pwd,useradd,passwd, who, ps, pstree, kill, top, ls, cd, mkdir, rmdir, cp, rm, mv, cat, more, grep 等。 (2) 实现一个模拟的 shell: 编写三个不同的程序 cmd1.c,cmd2.c,cmd3.c,每个程序的功能自定,分别编译成可执 行文件 cmd1,cmd2,cmd3。然后再编写一个程序,模拟 shell 程序的功能,能根据用户输 入的字符串(表示相应的命令名),去为相应的命令创建子进程并让它去执行相应的程序,而父进程则等待子进程结束,然后再等待接收下一条命令。如果接收到的命令为 exit,则父 进程结束;如果接收到的命令是无效命令,则显示“Command not found”,继续等待。 (3) 实现一个管道通信程序: 由父进程创建一个管道,然后再创建 3 个子进程,并由这三个子进程利用管道与父进程 之间进行通信:子进程发送信息,父进程等三个子进程全部发完消息后再接收信息。通信的 具体内容可根据自己的需要随意设计,要求能试验阻塞型读写过程中的各种情况,测试管道 的默认大小,并且要求利用 Posix 信号量机制实现进程间对管道的互斥访问。运行程序,观 察各种情况下,进程实际读写的字节数以及进程阻塞唤醒的情况。 (4) 利用 linux 的消息队列通信机制实现两个线程间的通信: 编写程序创建两个线程:sender 线程和 receive 线程,其中 sender 线程运行函数 sender(), 它创建一个消息队列,然后,循环等待用户通过终端输入一串字符,将这串字符通过消息队 列发送给 receiver 线程,直到用户输入“exit”为止;最后,它向 receiver 线程发送消息“end”, 并且等待 receiver 的应答,等到应答消息后,将接收到的应答信息显示在终端屏幕上,删除 相关消息队列,结束程序的运行。Receiver 线程运行 rece

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

elsa_balabala

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值