Linux编程库简介

 
    1. Linux编程库简介

所谓Linux编程库就是指始终可以被多个Linux软件项目重复使用的代码集。以C语言为例,它包含了几百个可以重复使用的例程和调试程序的工具代码,其中包括函数。如果每次编写新程序都要重新写这些代码会非常不方便,使用编程库有两个主要优点:

  • 可以简化编程,实现代码重复使用,进而减小应用程序的大小;

  • 可以直接使用比较稳定的代码。

Linux下的库文件分为共享库和静态库2大类,它们两者之间的差别仅在程序执行时所需的代码是在运行时动态加载的,还是在编译时竟态加载的。此外,通常共享库以.soShare Objece)结尾,静态链接库通常以.a结尾(Archive)。在终端下查看库的内容,通常共享库为绿色,静态库为黑色。

Linux库一般在/lib/usr/lib目录下。它主要存放系统得链接库文件,没有该目录则系统无法正常运行。/lib目录存储着程序运行时使用的共享库。通过共享库,许多程序可以重复使用相同的代码,因此可以有效减小应用程序的大小。下表部分列出了一些Linux下常用到的编程库。

常用到的Linux编程库

库 名 称

说 明

libc.so

标准的C

libdl.so

可使用库的源代码而无需静态编译库

libglib.so

Glib

libm.so

标准数学库

libGL.so

OpenGL的接口

libcom_err.so

常用的出错例程集合

libdb.so

创建和操作数据库

libgthread.so

Glib线程支持

libgtk.so

GIMP下的X

libz.so

压缩例程库

libvga.so

Linux下的VGASVGA图形库

libresolve.so

提供使用因特网域名服务器接口

libpthread.so

Linux多线程库

libdm.so

GNU数据库管理器


    1. Linux系统调用

从字面意思上理解,系统调用说的是操作系统提供给用户程序调用的一组“特殊”接口。Linux中用于创建进程的fork()函数本身就是一个系统调用,使用系统主要目的是使得用户可以使用操作系统提供的有关设备管理、输入/输出系统、文件系统和进程控制、通信以及存储管理等方面的功能,而不必了解系统程序的内部结构和有关硬件细节,从而起到减轻用户负担和保护系统以及提高资源利用率的作用。

Linux的运行空间划分为用户空间和内核空间,它们各自运行在不同的级别中,所以用户进程通常情况下不允许访问内核,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间函数。这样做的目的是为了对系统作必要的“保护”措施,但是使用系统调用可以最大程度地解决这一问题。其具体的措施是进程先用适当的值填充寄存器,然后调用一个特殊的指令,这个指令会跳到一个事先定义的内核中的一个位置(当然,这个位置是用户进程可读但是不可写的)。硬件知道一旦用户进程跳到这个位置,则认为该用户就不是在限制模式下运行的用户,而作为操作系统的内核。当然,用户访问内核的路径是事先规定好的,只能从规定位置进入内核,而不允许任意跳入内核。

Linux系统有200多个系统调用,这些调用按照功能大致可以分为下面几类:

  • 进程控制

  • 文件系统控制

  • 内存管理

  • 网络管理

  • Socket控制

  • 用户管理

  • 进程间通信

类似于在Windows下进行win32编程,windows会提供APIApplication Programming

Interface)接口函数作为为windows操作系统提供给程序员的系统调用接口。同样的,Linux作为一个操作系统也有它自己的系统调用,用户可以根据特定的方法来添加需要的系统调用。LinuxAPI接口遵循POSIX标准,这套标准定义了一系列API。在Linux中,这些API主要是通过C库(libc)实现的。下面通过举例来说明在Linux下添加新的系统调用的几个步骤。

1)修改kernel/sys.c,增加服务例程代码。首先编写添加到内核中的源程序。即要添加的服务,所以函数的名称应该是新的系统调用名称前面加上sys_标志。例如新加的系统调用为mysyscallint number),那么就应该在系统的/usr/linux/kernel/sys.c文件中添加相应的源代码,如下所示。

asmlinkage int sys_mysyscall (int number)

{

printk(“This is a example of systemcall/n”);

return number;

}

为了说明问题,仅仅是一个返回一个值的简单例子。

………………………………………………………………………………………………………

注意:系统调用函数通常在成功时返回0值,不成功时返回非零值

………………………………………………………………………………………………………..

2)添加新的系统调用后,为了从已有的内核程序中增加到新的函数的连接,需要编辑以下2个文件。

1/usr/src/linux/include/asm-i386/unistd.h

2/usr/src/linux/arch/i386/kernel/syscall_table.S

第一个文件中定义了每个系统调用的中断号,可以打开文件/usr/src/linux/include/ asm-i386/unistd.h来查看,该文件中包含了系统调用清单,用来给每个系统调用分配一个唯一的号码,部分内容如下。

………..

#define __NR_add_key 286

#define __NR_request_key 287

#define __NR_keyctl 288

#define __NR_ioprio_set 289

#define __NR_ioprio_get 290

#define __NR_inotify_init 291

#define __NR_inotify_add_watch 292

#define __NR_inotify_rm_watch 293

#define __NR_syscalls 294

…………………………..

文件的每一行各格式如下。

#define __NR_systemcallname N

systemcallname为系统调用名,而N则是该系统调用对应的中断号,每个系统调用都有唯一的中断号。应该将新的系统调用名称加到清单的最后,并给它分配号码序列中下一个可用的系统调用号。在该文件中的最后一句:#define __NR_stemcall 294

NR_stemcall表示系统调用的个数,294表示有294个系统调用,从标号0开始,所以最后一个系统调用号是293,那么如果新添加一个系统调用其中断号就是294

例如可以在该文件中这样定义一个系统调用:

#define __NR_mysyscall 294

如果还需要添加另外的系统调用,可以以此类推将中断号以此递增。此外需要注意的是,重新添加系统调用之后,应该在/usr/src/linux/include/asm-i386/unistd.h文件中的#define __NR_stemcall语句重新指定新的编号N,例如上面添加一个新的系统调用之后,该语句应该为:

#define __NR_stemcall 295

2个要编辑的文件是:/usr/src/linux/arch/i386/kernel/syscall_table.S。该文件中定义了系统调用列表。在该文件中有以下类似的内容。

.data

ENTRY(sys_call_table)

.long sys_restart_syscall /*0 – old “setup()” system call, used for restarting*/

.long sys_exit

.long sys_fork

.long sys_read

.long sys_write

.long sys_open

.long sys_close

.long sys_waitpid

.long sys_creat

.long sys_link

.long sys_unlink /* 10 */

……………………

在该文件中添加新的系统调用

.long sys_mysyscall


3)重新编译内核。添加好系统调用之后,需要重新编译内核,并且用新的内核来启动,此时,系统调用就添加好了。

4)测试新的系统调用,编辑程序test_call.c如下。

#include <linux/unistd.h>

#include <stdio.h>

#include <errno.h>

_syscall1(int, mysyscall, int, num); /*系统调用宏定义*/

int main (void)

{

int n;

n = mysyscall(10); /*执行系统调用*/

print(“n=%d/n”,n);

return 0;

}

编译并执行该程序。

# gcc –o test_call -I/usr/src/linux/include test_call.c

# ./test_call

n=0

输出值正确,说明系统调用成功。


3 Linux线程库

简单的讲,进程是资源管理的最小单位,线程是程序执行的最小单位。一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如CPU、内存、文件等),而将线程分配到某个CPU上执行。一个进程当然可以拥有多个线程。

Linux是一个多用户多任务的操作系统。多用户是指多个用户可以在同一时间使用计算机系统;多任务是指Linux可以同时执行几个任务,它可以在还未执行完一个任务时又执行另一项任务。在操作系统设计上,从进程演化出线程,最主要的目的就是更好地支持多处理器以及减小(进程/线程)上下文切换开销。

现在多线程技术已经被许多操作系统所支持,包括Windows/Linux。现在有3种不同标准的线程库:win32OS/2POSIX。其中前两种只能在它们各自的平台上(win32线程仅能运行于windows平台,OS/2仅能运行于OS/2平台上)。POSIXPortable Operating System Interface Standard,可移植操作系统接口标准)规范则是适用于各种平台,而且已经或正在所有主要的Unix/Linux系统上实现。

Linux系统下的多线程遵循POSIX接口,称为pthreadPOSIX标准由IEEE制定,并由国际标准化组织接受为国际标准。在Linux2.6内核版本之前,LinuxThreads是现有Linux平台上使用最为广泛的线程库,它由Xavier Leroy负责开发完成,并已绑定在Glib中发行。LinuxThreads是一种面向LinuxPOSIX 1003.lc-pthread标准接口。它所实现的就是基于核心轻量级进程的“一对一”线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。使用LinuxThreads线程库创建和管理线程常常用到下面几个函数。

  • pthread_creat() 创建新的线程

pthread_creat()函数类似于fork()函数,完整的函数形式如下。

int pthread_creat (pthread_t thread, const pthread_attr_t *attr, void*(*func)

(void*), void *arg)

1个参数是一个pthread_t型的指针用于保存线程ID,以后对该线程的操作都要用ID来表示。每个LinuxThreads线程都同时具有线程ID和进程ID,其中进程ID就是内核所维护的进程号,而线程ID则由LinuxThreads分配和维护。

2个参数是一个pthread_attr_t的指针用于说明要创建的线程的属性,使用NULL表示要使用缺省的属性。

3个参数指明了线程运行函数的起始地址,是一个只有一个(void *)参数的函数。

4个参数说明了运行函数的参数,参数arg一般指向一个结构,

函数的返回值类型为整数,当创建线程成功时,函数返回0,若不为0则说明创建线程失败。创建线程成功以后,新创建的线程运行参数3和参数4确定的函数,原来的线程则继续运行下一行代码。

  • pthread_join() 等待线程结束

pthread_join()函数用来挂起当前线程直到由参数thread指定的线程终止为止,完整地函数形式如下。

int pthread_join( pthread_t thread,void* *status)

1个参数为被等待的线程的标示符,第2个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。

函数返回值类型为整数,成功返回0,错误返回非0值。

  • pthread_self() 获取线程ID

函数原型

pthread_t pthread_self(void)

函数返回本线程ID

  • pthread_detach() 用于让线程脱离

pthread_detach()函数用于将处于连接状态的线程变为脱离状态,函数完整地形式如下。

int pthread_detach (pthread_t thread)

函数返回值类型为整数,如果成功将线程转换为脱离状态时返回0,否则返回非0值。

  • pthread_exit() 终止线程

pthread_exit(void *status)函数用来终止线程,函数完整形式如下。

pthread_exit(void *status)

参数status是指向线程返回值的指针。

下面通过一个简单的例子来介绍基于POSIX线程接口的Linux多线程编程,编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。以下是一个简单的例子。

/*mypthread.c*/

#include <pthread.h>

#include <stdlib.h>

#include <unistd.h>

void *thread_function(void *arg)

{

int i;

for(i=0; i<20; i++)

{

printf(“This is a thread!/n”);

}

return NULL;

}

int main (void)

{

pthread_t mythread;

if (pthread_creat (&mythread, NULL, thread_function, NULL) )

{

printf(“error creating thread.”);

abort ();

}

printf(“This is main process!/n”);

if ( pthread_join (mythread, NULL) )

{

printf(“error joining thread.”);

abort();

}

exit(0);

}

编译并执行该程序:

#gcc –lpthread –o mypthread mythread.c

./mypthread

This is mian process!

This is a thread!

一个线程实际上就是一个函数,创建后,该线程立即被执行。在上面的例程中,系统创建了一个主线程,又用pthread_creat函数创建了一个新的子线程。

事实上,在Linux2.6内核以前,Linux把进程当作其调度实体,内核并不真正支持线程(轻量线程实现)。它提供了一个clone()系统调用来创建一个调用线程的拷贝,这个拷贝与调用者共享地址空间。LinuxThreads项目就是利用这个系统调用,完全在用户级模拟了线程。LinuxThreads线程库目前存在一些不足之处,比如在信号处理、任务调度以及进程间同步原语等方面。在Linux2.6内核中,Linux内核的调度性能得到了很大改进。Linux重写了其线程库,使用NPTLNative Posix Thread Library)来取代受争议的LinuxThreads线程库,成为glibc的首选线程库,与此同时,最新发布的glibc2.4版本正式采用NPTL作为pthread实现。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值