UNIX系统编程(1)

标签: unix 系统 编程 | 发表时间:2013-02-12 21:37 | 作者:DLUTBruceZhang
出处:http://blog.csdn.net


注:本文来自“网易”博主,仅阅读,学习


第一章:什么是系统编程 
UNIX系统编程,简单的说就是“C语言+系统调用(system call)”,学会了C语言再知道一些系统调用的方法,其实就可以进行UNIX系统编程了。那什么又是系统调用呢,其实初学者就把它看当成是函数用就可以了。这些“函数”是干什么用的呢,大家知道操作系统内核管理着我们的计算机资源,比如CPU,内存,硬盘等等。应用程序是无法直接访问到它们的。那我们想利用这些资源怎么办呢,内核就给我们提供了一个接口,我们可以利用这个接口来进行计算机资源的使用。内核也通过接口来判断我们的使用请求是否合法,合法的的提供资源,不合法的给与干掉。就好比是金库,银行和储户。金库里有要多地人民币,这就是资源。然而我们储户却无法直接接触到这些可爱的人民币,因为它们是通过银行来管理的,银行就好比是内核。但我们怎么样才能从金库里取出钱来呢,我们可以去银行窗口办理存款取款手续,这就是系统调用。当然,每个人的取款限额都不一样,大款的存款多,他可以取几百万甚至更多,而我存款少,取出一万块就已经不错了。这就是用户的系统调用权限不同。还有就是银行行长,他对这个金库的权限更大(root),当然他的责任也更大,他的一个错误决定有可能导致银行破产。这事可就大了。还有一种情况,一个在银行里没有什么存款的人,却通过一些技术手段,得到了一个大款的密码甚至是伪装成银行行长,把金库里的钱全提走了,这就是黑客。想想这种感觉你就知道为什么世界上有这么多黑客乐此不疲了。还有一些人没有什么“技术含量”直接“抢银行”,把你的计算机都抱走了。那你只有哭了,金库里的钱丢了,好在“房子”还在呀,这回连“房子”也丢了。依照这个比喻,那木马是什么呢?对,就是你的银行职员里出了内奸了。哈哈。好了不胡扯了,我了这么多例子就是想告诉大家,银行(内核)本身来说还是十分坚固稳定的,问题出在如何通过窗口(系统调用)安全地使用它。这也是学习UNIX系统编程是应该注意的问题。从下一个帖子开始说说说进程(process)和如何生成一个进程。
第二章 进程的生成(1) 
    先说说什么是进程?假设你编好了一个程序,在它没有被调用之前,它只是乖乖地躺在你的硬盘上,什么事情都不干。好不容易编出来的不干活,这是我们不能容忍的。所以我们要把它调到内存里,然后通过CPU去执行它。所以说,进程就是一个在执行状态下的程序。 
我们可以通过 

$ps –e


命令来查看一下,计算机里所运行的进程有哪些。 
那我们计算机里这么多的进程的又是从哪里来的呢,我们可以通过 

$ps –axwf


来看到关于进程的一张家谱。其实,系统中的所有进程都是通过另一个进程生成的(除了0号进程以外),如果,A进程生成了B进程,那么可以把A进程叫做父进程,把B进程叫做A进程的子进程。也就是说,UNIX系统所有的进程都与其它进程保持着父子关系。 

下面我们来看一个简单的例子 

#include<stdio.h>

#include<sys/types.h>

#include<unistd.h>



int main( int argc , char *argv[])

{

        int time;

        time = atoi( argv[1] )*60 ;       //将参数中的分钟换成秒数

        if( fork()==0 )       //生成子进程,括号里是子进程的代码

        {

                sleep( time );

                fprintf( stderr , "it is time to alarm!\n");

        }

        return 0;

}


执行时输入 

$./a.out 2


这是一个简单的闹钟程序。你把它执行后,看似系统没有什么反映,其实不是,在后台你已经生成了一个进程,来监视时间。如果你用ps命令查看就能看到它。这时你可以接着干你自己的事情。等到了你设定的时间之后这个进程会提示你时间已经到了。这个程序虽不完善(没有进行输入参数的检查),但可以简单的告诉大家如何生成一个进程。 
    为了生成一个新的进程,这里使用了 fork() 这个系统调用。它的作用是将父进程的各个变量的值复制给子进程,也就是说当你调用了fork()的那一刻,系统就为你生成了一个和父进程完全一样的进程。当然我们不想要一个父亲一样的孩子,孩子要有自己的个性,那我们如何来赋予孩子自己的个性呢?让我们先来看看fork()这个系统调用的概要。 

头文件       #include <sys/types.h>

                    #include <unistd.h>

形式       pid_t fork(void);

返回值          成功时:        父进程中:子进程的进程号(>0)

                                子进程中:=0

                   失败时:        -1

                           
根据上面fork()的特性,我们可以通过fork()的返回值区分父进程要做的事和子进程要做的事。例如, 

pid_t pid ;

pid=fork();

if( !pid)

{

        //子进程要做的事

}else if(pid >0)

{

       //子进程生成失败时,父进程要做的事

}else  //pid<0

{

 //子进程生成失败时,父进程要做的事

}



好,我们现在已经学会了生成一个子进程了。但它还是遗传了许多父进程的特性,有可能大家会想,能不能用我们生成的子进程来执行另一个与父进程没有任何关系的程序呢。当然是可以的,比如我们常说的shell就是就是这个样子。shell本身也是一个进程当你输入命令回车以后,shell会生成一个子进程来执行你的命令,这条命令可以和shell没有丝毫关系。为了更好的说明问题我们先来做一个简单的shell。当然是最简单的那种。 

#include<stdio.h>

#include<sys/types.h>

#include<unistd.h>

int main()

{

        static      char prompt[64]="> ";

        char    command[256];

        int   st;

        fprintf(stderr,"%s",prompt);    //  屏幕上的输出提示符

        while(gets(command)!=NULL)  //  取得键盘输入

        {

                if(fork()==0)     //  生成子进程

                {         //  子进程要做的事

                       execl(command,command,(char *)0)==-1 //执行所输入的命令

                }

                else

                {         //  父进程要做的事

                        wait(&st);  //  等待子进程结束

                        fprintf(stderr,"%s",prompt);  //  输出提示符,等待命令

                 }

        }

        return 0;

}


好了我们保存,编译,执行以下看看 

$./a.out

>/bin/ls  //这里必须输入命令的完全路径

        当前目录下文件名

>Ctrl+D 退出程序

$


这样我们的一个最初级shell就做好了。虽然它还很弱,还有着安全上的漏洞(使用了gets()),甚至连自己退出都不能,但起码可以让我们看到一个shell是如何执行的了。其实,一个复杂的shell最基本的东西也就使这些。大家要是有兴趣的话可以将gets()换掉,再加上退出功能。 

我们再说说程序中出现的一个新的函数execl()。其实它是exec函数组中的一个。这组函数有: 

int   execl( path , arg0 , arg1 , ... , argn , (char *)0 );

int   execv( path , argv );

int   execle( path , arg0 , arg1 , ... , argn , (char *)0 , envp );

int   execve( path , argv , envp );

int   execlp( file , arg0 , arg1 , ... , argn , (char *)0 );

int   execvp( file , argv );


 

参数定义如下:

char *path;

char *file;

char *arg0 , *arg1 , ... , *argn;

char *argv[];

char *envp[];

返回值:        成功时:所执行的命令将会覆盖现有的进程,所以无返回值

                 失败时:-1


比如说我们在shell里执行 

$ /bin/ls –l


这个命令,实际上shell调用的是 

execl( "/bin/ls" , "/bin/ls" , "-l"  , (char *)0 );


的一个系统调用 
这个函数组函数有6个,用法就不一一说明了,大家可以参看一下其它资料。这里只告诉大家它们的用处,exec函数组就是用来调用一个可执行程序。还有一点很重要,一但进程调用了exec函数那么写在exec函数后面的进程代码将会被覆盖,变成无效的代码了。 
例如下面一段代码,我们想在execl执行后输出一段文字列,这是办不到的。 

 if(fork()==0)     //  生成子进程

                {         //  子进程要做的事

                       execl(command,command,(char *)0)==-1 //执行所输入的命令

        fprinf(stderr,"lalalalalalalalala!");  //

                }

                else

                {         //  父进程要做的事

                        wait(&st);  //  等待子进程结束

                        fprintf(stderr,"%s",prompt);  //  输出提示符,等待命令

                 }


还有一个函数wait(),它的概要是 

#include <sys/types.h>

pid_t wait(int *status);


返回值就是子进程的进程号 
它的参数是个指针。C语言里讲过,一个函数想有一个以上的返回值时,你可以将想返回的变量的地址作为函数的参数。比如说将数组地址作为函数的参数等等。其实这里的status就是这个道理,它的值与子进程的结束方式有关系。当你的子进程以exit()方式结束的话,status所指向的地址的前8位将会是exit()的参数的后8位,而status所指向的地址的后8位是0。例如子进程是exit(1);那status所指向的地址的内容应该是0000 0001 0000 0000。还有如果子进程是通过信号(signal)终止的(信号我们以后再讲),那么我们也可以通过status的值来判断是哪一个信号终止了这个子进程。(详见man) 
我们为什么还要在父进程中调用wait(),这涉及到进程状态的概念,我们稍候再说。

作者:DLUTBruceZhang 发表于2013-2-12 21:37:47 原文链接
阅读:103 评论:0 查看评论

相关 [unix 系统 编程] 推荐:

UNIX系统编程(2)

- - CSDN博客架构设计推荐文章
这回我们来说一下UNIX的文件系统. 由于一般情况下UNIX机的硬盘会很大,所以一般你可以给它分成几个区,而每个分区又都可以有独立的文件系统. 如果你是UNIX系统,你有可能看见 . 如果你是linux你有可能看见 . 这些文件,每一个就可以看成是一个分区. 当计算机启动时,系统先找到/(根目录),然后系统就像挖土豆子一样找到一个带出来一串似的,顺着根目录一带就带出来挂载在根目录下的所有目录(详细的请查一下/etc/fstab这个文件).

UNIX系统编程(1)

- - CSDN博客架构设计推荐文章
注:本文来自“网易”博主,仅阅读,学习. 第一章:什么是系统编程 . UNIX系统编程,简单的说就是“C语言+系统调用(system call)”,学会了C语言再知道一些系统调用的方法,其实就可以进行UNIX系统编程了. 那什么又是系统调用呢,其实初学者就把它看当成是函数用就可以了. 这些“函数”是干什么用的呢,大家知道操作系统内核管理着我们的计算机资源,比如CPU,内存,硬盘等等.

如何选择服务器操作系统(Linux/Unix)

- - 标点符
Linux的发行版有上百种,如何选择也是一种难题. 一、Ubuntu Desktop 和 Ubuntu Server. 这个是最方便,也是最快捷的方式. 如果刚开始使用Ubuntu,且不太熟悉命令行的可以选用Ubuntu Desktop来学习. 继而转为Ubuntu Server. 对于初学者,Ubuntu可以更好地上手,并且提供了很好提供了apt软件管理方式(这个是目前最好的软件管理方式),使用起来非常方便,同事Ubuntu的社区也非常的活跃,使用的人数也较多.

工具推荐:HardeningONE(类Unix系统安全检查脚本)

- - FreeBuf.COM | 关注黑客与极客
hardeningone是一款类Unix系统安全检查bash shell脚本,适合信安审计人员或信安测试人员使用. 作者写这个检测脚本的初衷是为了做系统安全审计、网络安全配置和系统安全维护之用. 该脚本主要包括如下功能内容:. (4)运行情况监控与报告. (5)其他的一些还在调试的功能. hardeningone无需进行安装,仅仅只需你通过github下载到系统中,在文件目录里面运行即可(注意文件运行权限).

现代计算机操作系统 Unix、C 语言之父 Dennis Ritchie 逝世

- tossking - 爱范儿 · Beats of Bits
令人悲伤的消息不止一个,现代计算机操作系统 Unix 之父、C 语言之父 Dennis Ritchie 辞世,IT 产业连续失去了两名重要的人物. 根据 Ritchie 的前同事 Rob Pike 在 Google+ 透露的消息,Ritchie  10 月 9 日病逝,享年 70 岁. 1941 年,Ritchie 出生在纽约 Bronxville,后来到哈佛大学学习应用数学与物理学毕业,1967 年他进入贝尔实验室.

UNIX传奇

- dayu - 桃源
了解过去,我们才能知其然,更知所以然. 总结过去,我们才会知道我们明天该如何去规划,该如何去走. 在时间的滚轮中,许许多的东西就像流星一样一闪而逝,而有些东西却能经受着时间的考验散发着经久的魅力,让人津津乐道,流传至今. 要知道明天怎么去选择,怎么去做,不是盲目地跟从今天各种各样琳琅满目前沿技术,而应该是去 —— 认认真真地了解和回顾历史.

C语言之父和Unix系统共同研制者丹尼斯•里奇去世

- kof2002 - 译言-每日精品译文推荐
莱纳斯·托瓦尔兹曾经就Linux的发展说过,他“[他自己]早就被托在巨人的肩膀上. ”丹尼斯·里奇(亦称dmr)很可能是这些巨人中间最高大的. C程序设计语言的创始人和Unix操作系统的共同研制者里奇于10月8日去世,终年70岁,他的遗产投下很长很长的影子. 由于里奇在Unix神怪般的分时系统方面的成果,我开始从事技术工作.

Unix调试的瑞士军刀:lsof

- Ran - 黑客志
这是“你应该知道的Unix和Linux命令”系列的第三篇,在这篇文章中,我会介绍lsof这个工具,如果说netcat是进行网络诊断的瑞士军刀,那么lsof就是Unix调试的瑞士军刀. Lsof是遵从Unix哲学的典范,它只做一件事情,并且做的相当完美——它可以列出某个进程打开的所有文件信息. 打开的文件可能是普通的文件,目录,NFS文件,块文件,字符文件,共享库,常规管道,明明管道,符号链接,Socket流,网络Socket,UNIX域Socket,以及其它更多.

24 个很酷的 Linux/Unix 工具

- will - LinuxTOY
KKovacs 收集了 24 个 Linux/Unix 平台上的工具,既有熟悉的,也有鲜为人知的. 这些运行于终端的工具都很酷,很有用. iostat、vmstat、ifstat 等. 进程、内存、以及 io 监视. 通过 SSH 保持文件系统同步. 在分隔的窗口中查看你的日志. socket 输入、输出更容易.

Unix/Linux常用的一些命令

- 白开水易拉罐 - BlogJava-首页技术区
Unix/Linux 的常用命令:. 这是我在学完Unix后归纳的一些觉得有用的命令:. ps:查看进程,也可以观察当前shell. ps -ef:查看和终端有关的所有进程. df -k:查看当前文件系统使用情况. passwd-d  user:清除用户的口令. more   :查看文件内容   可以分屏显示.