linux c

gcc编译

安装build-essential软件包
$sudo apt-get install build-essential

编辑了一个c文件后,开始编译

gcc 文件名 -o 执行名字

./执行名字

gcc -c 表示只进行编译,不链接可执行


例子,编写实验。

c源码:

shu.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<stdio.h>
#include "jisuan.h"
#include<string.h>
#include<stdlib.h>
int main()
{
char a[100];
int i,s=0,sum;
printf("please input tow numbers,first smaller than second:\n");
scanf("%s",a);
for(i=0;i<strlen(a);i++)
{
if(a[i]==' ')
s++;
if(a[i]<'0'||a[i]>'9')
{
printf("输入的不为数字!bye!\n");
exit(0);
}
}
if(s!=1)
{
printf("输入太多了!猪头!\n");
exit(0);
}
else
{
sum=jisuan(a);
printf("the sum is %d\n",sum);
}
return 0;
}
jisuan.h
int jisuan(char a[]);
jisuan.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int jisuan(char a[])
{
int i,flag,a1=0,a2=0;
for(i=0;i<strlen(a);i++)
{
if(a[i]=='/')
flag=i;
}
for(i=0;i<flag;i++)
a1=a1*10+a[i]-48;
for(i=flag+1;i<strlen(a);i++)
a2=a2*10+a[i]-48;
return a1+a2;
}


make相关

被引用程序需要写.h文件。

1
2
3
4
make -f makefile文件名 /指定makefile文件名
-i /忽略错误
-s /表示执行但是不显示执行状况
-d /Debug模式,显示文件详细信息和检测时间

makefile大致文件内容:

1
2
3
4
5
6
7
8
shu:shu.o jisuan.o
gcc shu.o jisuan.o shu
shu.o:shu.c jisuan.h
gcc -c shu.c
jisuan.o:jisuan.c jisuan.h
gcc -c jisuan.c
clean:
rm -f shu*.o

makefile规则:

1
2
3
4
5
1)如果这个工程没有编译过,那么我们的所有c文件都要编译并被链接。
2)如果这个工程的某几个c文件被修改,那么我们只编译被修改的c文件,并链接目标程序。
3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的c文件,并链接目标程序。

文件操作
文件描述符,文件的标识符。0表示标准输入文件,1表示标准输出文件,2表示标准错误输出文件。

1、open函数
三个参数:路径名或文件名;打开方式,包括O_RDONLY,O_WRONLY,O_RDWR;
打开失败返回-1
管道方法(个人认为比较有用的):O_TRUNC、O_CREAT
头文件:#include #include #include

2、close函数
参数为fd,文件描述符
关闭文件,成功返回0,失败返回-1
头文件:#include

3、read函数
第一个参数文件描述符,第二个参数输入缓冲区指针(将指定文件内容输出到这里)相当于一个char类型的数组,第三个参数要输入的字节数。
成功时返回读取的字节数,失败返回-1
头文件:#include

4、write函数
第一个参数文件描述符,第二个参数(要写入文件的缓冲区),第三个参数一次写入的字节数。
成功时返回字节数,错误时返回-1
头文件:#include

5、creat函数
个人觉得并没有用…

6、lseek函数
第一个参数文件描述符,第二个参数偏移量,第三个参数是当前位置的基点。
SEEK_SET:开头
SEEK_CUR:当前
SEEK_END:末尾
头文件:#include #include
成功返回从文件开始到当前的偏移量,失败返回-1

7、ftruncate或者truncate函数
第一个参数文件名或路径或文件描述符(看是哪个函数)
成功返回0,失败返回-1
头文件:#include

注:argc与argv[]是有关命令行参数

以下是有关stat的详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
stat(取得文件状态)
相关函数 fstat,lstat,chmod,chown,readlink,utime
表头文件 #include <sys/stat.h>
#include <unistd.h>
定义函数 int stat(const char * file_name,struct stat *buf);
函数说明 stat()用来将参数file_name所指的文件状态,复制到参数buf所指的结构中。
下面是struct stat内各参数的说明
struct stat
{
dev_t st_dev; /*device*/
ino_t st_ino; /*inode*/
mode_t st_mode; /*protection*/
nlink_t st_nlink; /*number of hard links */
uid_t st_uid; /*user ID of owner*/
gid_t st_gid; /*group ID of owner*/
dev_t st_rdev; /*device type */
off_t st_size; /*total size, in bytes*/
unsigned long st_blksize; /*blocksize for filesystem I/O */
unsigned long st_blocks; /*number of blocks allocated*/
time_t st_atime; /* time of lastaccess*/
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last change */
};
st_dev 文件的设备编号
st_ino 文件的i-node
st_mode 文件的类型和存取的权限
st_nlink 连到该文件的硬连接数目,刚建立的文件值为1。
st_uid 文件所有者的用户识别码
st_gid 文件所有者的组识别码
st_rdev 若此文件为装置设备文件,则为其设备编号
st_size 文件大小,以字节计算
st_blksize 文件系统的I/O 缓冲区大小。
st_blcoks 占用文件区块的个数,每一区块大小为512 个字节。
st_atime 文件最近一次被存取或被执行的时间,一般只有在用mknod、utime、read、write与tructate时改变。
st_mtime 文件最后一次被修改的时间,一般只有在用mknod、utime和write时才会改变
st_ctime i-node最近一次被更改的时间,此参数会在文件所有者、组、权限被更改时更新先前所描述的st_mode 则定义了下列数种情况
S_IFMT 0170000 文件类型的位遮罩
S_IFSOCK 0140000 scoket
S_IFLNK 0120000 符号连接
S_IFREG 0100000 一般文件
S_IFBLK 0060000 区块装置
S_IFDIR 0040000 目录
S_IFCHR 0020000 字符装置
S_IFIFO 0010000 先进先出
S_ISUID 04000 文件的(set user-id on execution)位
S_ISGID 02000 文件的(set group-id on execution)位
S_ISVTX 01000 文件的sticky位
S_IRUSR(S_IREAD) 00400 文件所有者具可读取权限
S_IWUSR(S_IWRITE)00200 文件所有者具可写入权限
S_IXUSR(S_IEXEC) 00100 文件所有者具可执行权限
S_IRGRP 00040 用户组具可读取权限
S_IWGRP 00020 用户组具可写入权限
S_IXGRP 00010 用户组具可执行权限
S_IROTH 00004 其他用户具可读取权限
S_IWOTH 00002 其他用户具可写入权限
S_IXOTH 00001 其他用户具可执行权限
上述的文件类型在POSIX 中定义了检查这些类型的宏定义
S_ISLNK (st_mode) 判断是否为符号连接
S_ISREG (st_mode) 是否为一般文件
S_ISDIR (st_mode)是否为目录
S_ISCHR (st_mode)是否为字符装置文件
S_ISBLK (s3e) 是否为先进先出
S_ISSOCK (st_mode) 是否为socket
若一目录具有sticky 位(S_ISVTX),则表示在此目录下的文件只能被该文件所有者、此目录所有者或root来删除或改名。
返回值 执行成功则返回0,失败返回-1,错误代码存于errno
错误代码 ENOENT 参数file_name指定的文件不存在
ENOTDIR 路径中的目录存在但却非真正的目录
ELOOP 欲打开的文件有过多符号连接问题,上限为16符号连接
EFAULT 参数buf为无效指针,指向无法存在的内存空间
EACCESS 存取文件时被拒绝
ENOMEM 核心内存不足
ENAMETOOLONG 参数file_name的路径名称太长

进程操作

进程的概念

具有一定功能的关于一个数据集合的一次运行活动,是处于活动状态的计算机程序。

linux的进程

每个进程用一个task_struct数据结构来表示,数组task包含指向系统所有task_struct结构的指针。最大512个。当前进程用current指针来指示。
每一个进程都要唯一的进程的识别号。(暂时不讲)关于进程调度我不知道有没有学的必要,先放一边。

进程控制

内核创建新进程:并不用fork,vfork调用,属于系统启动一部分。
一般来说,创建新进程,调用fork,vfork函数。

  • fork函数

    创建子进程,完全复制父进程,与其共享数据段和堆栈区域。创建成功返回0,失败返回-1.
    需要sys/types.h与unistd.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include<stdio.h>
    #include<sys/types.h>
    #include<unistd.h>
    int main()
    {
    pid_t pid;
    printf("start the testing");
    pid=fork();
    printf("create successfully!pid=%d",pid);
    return 0;
    }

以上例子创建了一个新的进程,并可看出,成功则返回0。pid是专门的 pid_t 类型,fork的返回值是 pid ,fork 无需参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define size 1024
#define key 99
int shmid;
int j=5;
int main()
{
int i,*pint;
pid_t pid;
char *addr;
i=10;
shmid=shmget(size,key, ipc_creat|0777);
pint=shmat(shmid,0,0);
*pint=100;
printf("start the test!\n");
pid=fork();
i++;
j++;
*pint+=1;
printf("pid=%d\n",pif);
printf("i=%d,j=%d\n",i,j);
printf("*pint=%d",*pint);
return 0;
}

子进程执行完之后pid返回0,接着执行父进程返回pid。

  • vfork函数
    也是创建新的子进程。不过主要目的就是用exec调用另外的程序。在vfork使用execexit函数之前,子进程与父进程是共享数据段的。而fork是复制父进程。vfork调用exec或exit之前,子进程先运行,父进程挂起,如果需要父进程的进一步活动,则会造成死锁

我感觉书上那个代码很弱智,就不写了。

  • exec函数
    用于系统调用,对进程执行新的目标程序。
    exec函数会取代执行它的进程, 也就是说, 一旦exec函数执行成功, 它就不会返回了, 进程结束. 但是如果exec函数执行失败, 它会返回失败的信息(即-1), 而且进程继续执行后面的代码!
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

exec函数分为三个部分,文件名或者文件路径部分,参数部分,环境变量部分。
以上的函数定义是不是看起来很烦,我找到有个大佬总结了下,还挺舒服。

1
2
3
4
5
6
7
e后续,参数必须带环境变量部分,环境变零部分参数会成为执行exec函数期间的环境变量, 比较少用
l 后续,命令参数部分必须以"," 相隔,最后1个命令参数必须是NULL
v 后续,命令参数部分必须是1个以NULL结尾的字符串指针数组的头部指针.例如char * pstr就是1个字符串的指针,char * pstr[] 就是数组了,分别指向各个字符串.
p后续,执行文件部分可以不带路径, exec函数会在$PATH中找

如果还是看着烦,就看例子吧。

execv()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int childpid;
int i;
if (fork() == 0)
{
//child process
char * execv_str[] = {"echo", "executed by execv",NULL};
if (execv("/usr/bin/echo",execv_str) <0 )
{
perror("error on exec");
exit(0);
}
}
else
{
/parent process
wait(&childpid);
printf("execv done\n\n");
}
}

execv(“/usr/bin/echo”,execv_str)可以看出只需要两个参数,一个路径名,一个数组,但是这个程序不要轻易尝试,原因试试就知道了。

execvp()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (fork() == 0)
{
//child process
char * execvp_str[] = {"echo", "executed by execvp",">>", "~/abc.txt",NULL};
if (execvp("echo",execvp_str) <0 )
{
perror("error on exec");
exit(0);
}
}
else
{
//parent process
wait(&childpid);
printf("execvp done\n\n");
}

execvp(“echo”,execvp_str) 文件名加数组

举两个例子吧,大概能记住了吧。


fork()函数与exec()函数的联合使用

这个书上的例子还不错,我就把他打印下来。这是一个文件监控程序,修改后就建立一个副本,我觉得还是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
int main(int argc,char *argv[])
{
int fd;
int stat,pid;
struct stat stbuf;
time_t old time=0;
if(argc!=3)
{
fprintf(stderr,"参数错误!");
return 1;
}
if((fd=open(argv[1],O_RDONLY))==-1)
{
fprintf(stderr,"无法打开文件!");
}
fstat(fd,&stbuf);
old_time=stbuf.st_mtime;
for(;;)
{
fstat(fd,&stbuf);
if(old_time!=stbuf.st_mtime)
{
while(pid=fork()==-1);
if(pid==0)
{
execl("/bin/cp","/bin/cp",argv[1],argv[2],0);
return 3;
}
wait(&stat);
old_time=stbuf.st_mtime;
}
else
sleep(30);
}
}
后台执行:./文件名 file file.bak&
  • system()函数

    参数为一个指针。
    指针为空返回1;
    exec错误返回退出时的返回值;
    调用成功返回shell的结束状态。

例子不举了,因为我要去洗头了。。


进程结束

三种正常结束:return exit _exit
两种异常结束

进程等待

就是将子进程挂起或者立即结束返回状态,若无子进程返回-1。

进程的用户标识号和进程标识号等之后再看吧,内容不多,暂时搁浅。